Skip navigation

PDFbox is a fairly robust library for manipulating and extracting information from PDF documents. This is a short and simple Java class that utilizes PDFbox to stamp/watermark a PDF from the command line, be it a bash script or exec call. There isn’t much in the way of existing user documentation for this type of action using PDFbox, so I’m leaving it here in case it’s useful.

/*
 * A Simple PDF Stamper using PDFBox
 *
 * July, 2013
 * @author jack
 */
package simplestamper;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.apache.pdfbox.Overlay;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.exceptions.CryptographyException;
import org.apache.pdfbox.exceptions.InvalidPasswordException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;

/**
 *
 * @author jack
 */
public class SimpleStamper {

    private static PDDocument pdf;
    private static int numPages;
    private static Properties config = new Properties();
    private static String configFile;
    private static String outputFN = null;
    private static String inputFN = null;
    private static String stampString = null;
    private static String fontFamily = null;
    private static Float fontSize = null;

    /**
     * The main class. It's what plants crave.
     *
     * @param args the command line arguments
     *      args[0] -> The config file
     *      args[1] -> The PDF document to be stamped
     *      args[2] -> The string to stamp (optional, falls back to ss.text in the config file)
     *      args[3] -> The desired name and path of the stamped PDF (optional, also defined in config file as ss.outputFN)
     */
    public static void main(String[] args) throws IOException, CryptographyException, COSVisitorException {

        // if we don't have any parameters...
        if (args.length == 0) {
            usage();
        } else {

            // the config file path
            configFile = args[0];

            // load the config
            loadConfig(configFile, args);

        }
        // stamp
        stampPdf();
    }

    /**
     * Loads the configuration data.
     *
     * @param configFile
     */
    public static void loadConfig(String configFile, String[] args) {

        // the input stream
        InputStream is;

        try {

            is = new FileInputStream(configFile);

            try {
                // try to load the config file
                config.load(is);
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // the input PDF filename/path
        if (args.length > 1) {
            inputFN = args[1];
        }else{
            inputFN = config.getProperty("ss.inputFN");
        }

        // the string to be stamped
        if(args.length > 2){
            stampString = args[2];
        }else{
            stampString = config.getProperty("ss.text");
        }

        // the output PDF filename/path
        if(args.length > 3){
            outputFN = args[3];
        }else{
            outputFN = config.getProperty("ss.outputFN");
        }

        // make sure we have a font from the prop file
        try{
            fontFamily = config.getProperty("ss.fontFamily");
        } catch (NullPointerException e){
            System.err.println("You must specify a font in the properties file.");
        }

        // make sure we have a font size from the prop file
        try{
            fontSize = Float.parseFloat(config.getProperty("ss.fontSize"));
        } catch (NullPointerException e){
            System.err.println("You must specify a font size in the properties file.");
        }

    }

    /**
     * Coordinate the stamping procedure.
     *
     */
    public static void stampPdf() throws IOException, CryptographyException, COSVisitorException {

        // load the PDF to be stamped
        pdf = PDDocument.load(inputFN);

        if (pdf.isEncrypted()) {
            try {
                // try to open the encrypted PDF
                pdf.decrypt("");

            } catch (InvalidPasswordException e) {

                // This error message dictates that the document is encrypted and we have no password
                System.err.println("The document is encrypted or otherwise has prohibitive security settings..");
                System.exit(1);
            }
        }
        // create the overlay page with the text to be stamped
        PDDocument overlayDoc = createOverlay(stampString);

        // update the number of pages we have in the incoming pdf
        numPages = pdf.getPageCount();

        // do the overlay
        doOverlay(pdf, overlayDoc);

        // close
        pdf.close();
        overlayDoc.close();
    }

    /**
     * Creates the overlay PDF.
     *
     * @param text
     * @return PDDocument
     * @throws IOException
     * @throws COSVisitorException
     */
    public static PDDocument createOverlay(String text) throws IOException, COSVisitorException {

        // Create a document and add a page to it
        PDDocument document = new PDDocument();
        PDPage page = new PDPage();
        document.addPage(page);

        // the x/y coords
        Float xVal = Float.parseFloat(config.getProperty("ss.xVal"));
        Float yVal = Float.parseFloat(config.getProperty("ss.yVal"));

        // Create a new font object selecting one of the PDF base fonts
        PDFont font = PDType1Font.getStandardFont(fontFamily);

        // Start a new content stream which will "hold" the to be created content
        PDPageContentStream contentStream = new PDPageContentStream(document, page);

        // Create the text and position it
        contentStream.beginText();
        contentStream.setFont(font, fontSize);
        contentStream.moveTextPositionByAmount(xVal, yVal);
        contentStream.drawString(text);
        contentStream.endText();

        // Make sure that the content stream is closed:
        contentStream.close();

        //return the string doc
        return document;
    }

    /**
     * Performs the overlay of the two documents.
     *
     * @param basePDF
     * @param overlayDoc
     * @throws IOException
     * @throws COSVisitorException
     */
    private static void doOverlay(PDDocument basePDF, PDDocument overlayDoc) throws IOException, COSVisitorException {

        PDDocumentCatalog docCatalog = basePDF.getDocumentCatalog();

        // get the pages of the pdf
        List pages = docCatalog.getAllPages();
        Iterator pageIter = pages.iterator();

        while (pageIter.hasNext()) {
            PDPage page = (PDPage) pageIter.next();
        }

        Overlay overlay = new Overlay();
        PDDocument output = overlay.overlay(overlayDoc, basePDF);
        //overlay.overlay(overlayDoc, basePDF);

        // save the new doc to the file name specified in the config
        output.save(outputFN);

        // close, close
        overlayDoc.close();
        basePDF.close();
    }

    /**
     * Attempts to provide basic usage information.
     */
    private static void usage() {
        System.err.println("usage: java -jar simplestamper \"PdfToStamp.pdf\" \"The text to stamp, in parenthesis.\" \"Output.pdf\"");
    }
}

Leave a Reply