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;
}
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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With