Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a watermark (pdf Optional Content) that shows only when printing using PDFBox

I have come across many examples that use the PDFBox Layer Utility's appendFormAsLayer method as shown below:

 /**
 * Places the given form over the existing content of the indicated page (like an overlay).
 * The form is enveloped in a marked content section to indicate that it's part of an
 * optional content group (OCG), here used as a layer. This optional group is returned and
 * can be enabled and disabled through methods on {@link PDOptionalContentProperties}.
 * @param targetPage the target page
 * @param form the form to place
 * @param transform the transformation matrix that controls the placement
 * @param layerName the name for the layer/OCG to produce
 * @return the optional content group that was generated for the form usage
 * @throws IOException if an I/O error occurs
 */
public PDOptionalContentGroup appendFormAsLayer(PDPage targetPage,
        PDXObjectForm form, AffineTransform transform,
        String layerName) throws IOException
{
    PDDocumentCatalog catalog = targetDoc.getDocumentCatalog();
    PDOptionalContentProperties ocprops = catalog.getOCProperties();
    if (ocprops == null)
    {
        ocprops = new PDOptionalContentProperties();
        catalog.setOCProperties(ocprops);
    }
    if (ocprops.hasGroup(layerName))
    {
        throw new IllegalArgumentException("Optional group (layer) already exists: " + layerName);
    }

    PDOptionalContentGroup layer = new PDOptionalContentGroup(layerName);
    ocprops.addGroup(layer);

    PDResources resources = targetPage.findResources();
    PDPropertyList props = resources.getProperties();
    if (props == null)
    {
        props = new PDPropertyList();
        resources.setProperties(props);
    }

    //Find first free resource name with the pattern "MC<index>"
    int index = 0;
    PDOptionalContentGroup ocg;
    COSName resourceName;
    do
    {
        resourceName = COSName.getPDFName("MC" + index);
        ocg = props.getOptionalContentGroup(resourceName);
        index++;
    } while (ocg != null);
    //Put mapping for our new layer/OCG
    props.putMapping(resourceName, layer);

    PDPageContentStream contentStream = new PDPageContentStream(
            targetDoc, targetPage, true, !DEBUG);
    contentStream.beginMarkedContentSequence(COSName.OC, resourceName);
    contentStream.drawXObject(form, transform);
    contentStream.endMarkedContentSequence();
    contentStream.close();

    return layer;
}

What is the significance of "MC" in the getPDFName call in the previous code?

I have written the following code to insert the watermark on each page of an existing pdf and enable each group of optional content.

    LayerUtility layerUtility = new LayerUtility(document);
    PDXObjectForm form = layerUtility.importPageAsForm(overlayDoc, 0);
    for (int i = 0; i < document.getDocumentCatalog().getAllPages().size(); i++) {
        PDPage page = (PDPage) document.getDocumentCatalog().getAllPages().get(i);
        PDOptionalContentGroup ocGroup = layerUtility.appendFormAsLayer(page, form, new AffineTransform(), "watermark" + i);
    }



    PDOptionalContentProperties ocprops = document.getDocumentCatalog().getOCProperties();

    for (String groupName : ocprops.getGroupNames()) {
        if (groupName.startsWith("watermark")) {
            ocprops.setGroupEnabled(groupName, true);
        }
    }

Setting the group to enabled or disabled "setGroupEnabled(groupName, true)" causes it to show for both displaying and printing. According to other information I have researched on this subject it is possible to more finely tune when optional content is visible, which indicates the existence of onScreen and onPrint Boolean attributes that can be set to determine the visibility of the content. See https://acrobatusers.com/tutorials/watermarking-a-pdf-with-javascript

Is there a way using PDFBox to cause a watermark to be visible when printed but not when displayed? If not, any suggestions of alternate solutions would be appreciated.

Here is additional code to create a watermark pdf from a string (createOverlay) and a function (addWatermark) that calls the LayerUtility passing the watermark document. All that is required is to create a PDDocument from any existing pdf file and pass it with the watermark string.

public PDDocument addWatermark(PDDocument document, String text) throws IOException {
    PDDocument overlayDoc = createOverlay(text);
    //  Create the watermark in an optional content group
    LayerUtility layerUtility = new LayerUtility(document);
    PDXObjectForm form = layerUtility.importPageAsForm(overlayDoc, 0);
    for (int i = 0; i < document.getDocumentCatalog().getAllPages().size(); i++) {
        PDPage page = (PDPage) document.getDocumentCatalog().getAllPages().get(i);
        layerUtility.appendFormAsLayer(page, form, new AffineTransform(), "watermark" + i);
    }
    return document;        
}

private PDDocument createOverlay(String text) throws IOException {
    // Create a document and add a page to it
    PDDocument document = new PDDocument();
    PDPage page = new PDPage();
    document.addPage(page);

    PDExtendedGraphicsState extendedGraphicsState = new PDExtendedGraphicsState();
     // Set the transparency/opacity
     extendedGraphicsState.setNonStrokingAlphaConstant(0.4f);
     if (page.findResources() == null) {
         page.setResources(new PDResources());
     }
     PDResources resources = page.findResources();// Get the page resources.
     // Get the defined graphic states.
     if (resources.getGraphicsStates() == null)
     {
         resources.setGraphicsStates(new HashMap<String, PDExtendedGraphicsState>());
     }
     Map<String, PDExtendedGraphicsState> graphicsStateDictionary = resources.getGraphicsStates();

      if (graphicsStateDictionary != null){ 
          graphicsStateDictionary.put("TransparentState", extendedGraphicsState); 
          resources.setGraphicsStates(graphicsStateDictionary); 
     }

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

    // Start a new content stream which will "hold" the to be created content
    PDPageContentStream contentStream = new PDPageContentStream(document, page, true, true);
      contentStream.appendRawCommands("/TransparentState gs\n");

    // Create the text and position it
    contentStream.beginText();
    contentStream.setFont(font, fontSize);
    contentStream.setTextRotation(Math.PI/4,page.getMediaBox().getWidth()/4,page.getMediaBox().getHeight()/4);
    contentStream.setNonStrokingColor(210,210,210); //light grey
    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;
}
like image 668
Scott G Avatar asked Jul 28 '15 17:07

Scott G


1 Answers

private void addWaterMark(PDDocument document) throws Exception
{
    PDDocumentCatalog catalog = document.getDocumentCatalog();
    PDOptionalContentProperties ocprops = catalog.getOCProperties();
    if (ocprops == null)
    {
        ocprops = new PDOptionalContentProperties();
        ocprops.setBaseState(BaseState.OFF);
        catalog.setOCProperties(ocprops);
    }
    String layerName = "conWatermark";
    PDOptionalContentGroup watermark = null;
    if (ocprops.hasGroup(layerName))
    {
        watermark = ocprops.getGroup(layerName);
    }
    else
    {
        watermark = new PDOptionalContentGroup(layerName);
        ocprops.addGroup(watermark);
    }

    COSDictionary watermarkDic = watermark.getCOSObject();
    COSDictionary printState = new COSDictionary();
    printState.setItem("PrintState", COSName.ON);
    COSDictionary print = new COSDictionary();
    print.setItem("Print", printState);
    watermarkDic.setItem("Usage", print);

    COSDictionary asPrint = new COSDictionary();
    asPrint.setItem("Event", COSName.getPDFName("Print"));
    COSArray category = new COSArray();
    category.add(COSName.getPDFName("Print"));
    asPrint.setItem("Category", category);
    COSArray ocgs = new COSArray();
    ocgs.add(watermarkDic);
    asPrint.setItem(COSName.OCGS, ocgs);
    COSArray as = new COSArray();
    as.add(asPrint);
    COSDictionary d = (COSDictionary) ((COSDictionary) ocprops.getCOSObject()).getDictionaryObject(COSName.D);
    d.setItem(COSName.AS, as);

    for (int n = 0; n < document.getNumberOfPages(); n++)
    {
        PDPage page = document.getPage(n);

        PDResources resources = page.getResources();
        if (resources == null)
        {
            resources = new PDResources();
            page.setResources(resources);
        }

        String text1 = "Confidential";
        String text2 = "Document ..."; //

        PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
        graphicsState.setNonStrokingAlphaConstant(0.08f);

        PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, true, true);
        contentStream.setGraphicsStateParameters(graphicsState);
        contentStream.setNonStrokingColor(Color.GRAY);

        contentStream.beginMarkedContent(COSName.OC, watermark);

        contentStream.beginText();

        PDRectangle pageSize = page.getBBox();
        float fontSize = this.getFittingFontSize(pageSize, text1);
        PDFont font = this.WATERMARK_FONT;
        contentStream.setFont(font, fontSize);
        float text1Width = this.getTextWidth(font, fontSize, text1);
        float rotation = (float) Math.atan(pageSize.getHeight() / pageSize.getWidth());
        Point p = this.getStartPoint(pageSize, fontSize, text1);
        AffineTransform at = new AffineTransform(1, 0, 0, 1, p.getX(), p.getY());
        at.rotate(rotation);
        Matrix matrix = new Matrix(at);
        contentStream.setTextMatrix(matrix);
        contentStream.showText(text1);

        fontSize = this.getFittingFontSize(pageSize, text2);
        contentStream.setFont(font, fontSize);
        contentStream.newLineAtOffset(-(this.getTextWidth(font, fontSize, text2) - text1Width) / 2, -fontSize * 1.2f);
        contentStream.showText(text2);
        contentStream.endMarkedContent();
        contentStream.endText();
        contentStream.close();
    }
}
like image 189
user3501373 Avatar answered Nov 15 '22 13:11

user3501373