Java tutorial
/* * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl-2.1.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Contributors: * Thibaud Arguillere */ package org.nuxeo.pdf; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; import org.apache.pdfbox.Overlay; import org.apache.pdfbox.exceptions.COSVisitorException; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.edit.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.graphics.xobject.PDPixelMap; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.impl.blob.FileBlob; import org.nuxeo.pdf.PDFUtils; import org.nuxeo.runtime.api.Framework; /** * This class adds a watermark to a Blob holding a PDF. It never changes the * original Blob: It returns a watermarked copy of it. * * Setters return the PDFWatermark object so they can be chained * * @since 6.0 */ public class PDFWatermarking { public static final String DEFAULT_FONT_FAMILY = "Helvetica"; public static final float DEFAULT_FONT_SIZE = 36f; public static final int DEFAULT_TEXT_ROTATION = 0; public static final String DEFAULT_TEXT_COLOR = "#000000"; public static final float DEFAULT_ALPHA = 0.5f; public static final float DEFAULT_X_POSITION = 0f; public static final float DEFAULT_Y_POSITION = 0f; public static final boolean DEFAULT_INVERT_Y = false; protected Blob blob = null; protected String text = null; protected String fontFamily = DEFAULT_FONT_FAMILY; protected float fontSize = DEFAULT_FONT_SIZE; protected int textRotation = DEFAULT_TEXT_ROTATION; protected String hex255Color = DEFAULT_TEXT_COLOR; protected float alphaColor = DEFAULT_ALPHA; protected float xPosition = DEFAULT_X_POSITION; protected float yPosition = DEFAULT_Y_POSITION; protected boolean invertY = DEFAULT_INVERT_Y; /** * Constructor * * @param inBlob */ public PDFWatermarking(Blob inBlob) { blob = inBlob; } /** * Constructor * * @param inDoc * @param inXPath */ public PDFWatermarking(DocumentModel inDoc, String inXPath) { blob = (Blob) inDoc.getPropertyValue(PDFUtils.checkXPath(inXPath)); } /** * Adds the watermark to the Blob passed to the constructor. * <p> * If no setter has been used, the DEFAULT_nnn values apply * <p> * If * <code>text</text> is empty or null, a simple copy of the original Blob is returned * <p> * With thanks to the sample code found at https://issues.apache.org/jira/browse/PDFBOX-1176 * and to Jack (https://jackson-brain.com/a-better-simple-pdf-stamper-in-java/) * * @return a new Blob with the watermark on each page * * @throws ClientException * * @since 6.0 */ public Blob watermark() throws ClientException { Blob result = null; PDDocument pdfDoc = null; PDPageContentStream contentStream = null; if (text == null || text.isEmpty()) { try { File tempFile = File.createTempFile("nuxeo-pdfwatermarking-", ".pdf"); blob.transferTo(tempFile); result = new FileBlob(tempFile); // Just duplicate the info result.setFilename(blob.getFilename()); result.setMimeType(blob.getMimeType()); result.setEncoding(blob.getEncoding()); Framework.trackFile(tempFile, result); return result; } catch (IOException e) { throw new ClientException(e); } } // Set up the graphic state to handle transparency // Define a new extended graphic state PDExtendedGraphicsState extendedGraphicsState = new PDExtendedGraphicsState(); // Set the transparency/opacity extendedGraphicsState.setNonStrokingAlphaConstant(alphaColor); try { pdfDoc = PDDocument.load(blob.getStream()); PDFont font = PDType1Font.getStandardFont(fontFamily); int[] rgb = PDFUtils.hex255ToRGB(hex255Color); List<?> allPages = pdfDoc.getDocumentCatalog().getAllPages(); int max = allPages.size(); for (int i = 0; i < max; i++) { contentStream = null; PDPage page = (PDPage) allPages.get(i); PDRectangle pageSize = page.findMediaBox(); PDResources resources = page.findResources(); // Get the defined graphic states. HashMap<String, PDExtendedGraphicsState> graphicsStateDictionary = (HashMap<String, PDExtendedGraphicsState>) resources .getGraphicsStates(); if (graphicsStateDictionary != null) { graphicsStateDictionary.put("TransparentState", extendedGraphicsState); resources.setGraphicsStates(graphicsStateDictionary); } else { Map<String, PDExtendedGraphicsState> m = new HashMap<>(); m.put("TransparentState", extendedGraphicsState); resources.setGraphicsStates(m); } if (invertY) { yPosition = pageSize.getHeight() - yPosition; } float stringWidth = font.getStringWidth(text) * fontSize / 1000f; int pageRot = page.findRotation(); boolean pageRotated = pageRot == 90 || pageRot == 270; boolean textRotated = textRotation != 0 && textRotation != 360; int totalRot = pageRot - textRotation; float pageWidth = pageRotated ? pageSize.getHeight() : pageSize.getWidth(); float pageHeight = pageRotated ? pageSize.getWidth() : pageSize.getHeight(); double centeredXPosition = pageRotated ? pageHeight / 2f : (pageWidth - stringWidth) / 2f; double centeredYPosition = pageRotated ? (pageWidth - stringWidth) / 2f : pageHeight / 2f; contentStream = new PDPageContentStream(pdfDoc, page, true, true, true); contentStream.beginText(); contentStream.setFont(font, fontSize); contentStream.appendRawCommands("/TransparentState gs\n"); contentStream.setNonStrokingColor(rgb[0], rgb[1], rgb[2]); if (pageRotated) { contentStream.setTextRotation(Math.toRadians(totalRot), centeredXPosition, centeredYPosition); } else if (textRotated) { contentStream.setTextRotation(Math.toRadians(textRotation), xPosition, yPosition); } else { contentStream.setTextTranslation(xPosition, yPosition); } contentStream.drawString(text); contentStream.endText(); contentStream.close(); contentStream = null; } result = PDFUtils.saveInTempFile(pdfDoc); } catch (IOException | COSVisitorException e) { throw new ClientException(e); } finally { if (contentStream != null) { try { contentStream.close(); } catch (IOException e) { // Ignore } } PDFUtils.closeSilently(pdfDoc); } return result; } public Blob watermarkWithPdf(Blob inBlob) throws ClientException { Blob result = null; PDDocument pdfDoc = null; PDDocument pdfOverlayDoc = null; try { pdfDoc = PDDocument.load(blob.getStream()); pdfOverlayDoc = PDDocument.load(inBlob.getStream()); Overlay overlay = new Overlay(); overlay.overlay(pdfOverlayDoc, pdfDoc); result = PDFUtils.saveInTempFile(pdfDoc); } catch (IOException | COSVisitorException e) { throw new ClientException(e); } finally { PDFUtils.closeSilently(pdfDoc, pdfOverlayDoc); } return result; } public Blob watermarkWithImage(Blob inBlob, int x, int y, float scale) throws ClientException { Blob result = null; PDDocument pdfDoc = null; PDPageContentStream contentStream = null; scale = (scale <= 0f) ? 1.0f : scale; try { BufferedImage tmp_image = ImageIO.read(inBlob.getStream()); pdfDoc = PDDocument.load(blob.getStream()); PDXObjectImage ximage = new PDPixelMap(pdfDoc, tmp_image); List<?> allPages = pdfDoc.getDocumentCatalog().getAllPages(); int max = allPages.size(); for (int i = 0; i < max; i++) { PDPage page = (PDPage) allPages.get(i); contentStream = new PDPageContentStream(pdfDoc, page, true, true); contentStream.endMarkedContentSequence(); contentStream.drawXObject(ximage, x, y, ximage.getWidth() * scale, ximage.getHeight() * scale); /* * AffineTransform at = new AffineTransform(ximage.getWidth() * * scale, 200, 200, ximage.getHeight() * scale, x, y); * at.rotate(Math.toRadians(90)); * contentStream.drawXObject(ximage, at); */ contentStream.close(); contentStream = null; } result = PDFUtils.saveInTempFile(pdfDoc); } catch (IOException | COSVisitorException e) { throw new ClientException(e); } finally { if (contentStream != null) { try { contentStream.close(); } catch (IOException e) { // Ignore } } PDFUtils.closeSilently(pdfDoc); } return result; } /* * Utilities to handle null in setProperties() */ protected float stringToFloat(String inValue) { if (inValue == null) { return 0f; } return Float.valueOf(inValue); } protected int stringToInt(String inValue) { if (inValue == null) { return 0; } return Integer.valueOf(inValue); } protected boolean stringToBoolean(String inValue) { if (inValue == null) { return false; } return Boolean.valueOf(inValue); } public PDFWatermarking setProperties(HashMap<String, String> inProps) { setFontFamily(inProps.get("fontFamily")); setFontSize(stringToFloat(inProps.get("fontSize"))); setTextRotation(stringToInt(inProps.get("textRotation"))); setTextColor(inProps.get("hex255Color")); setAlphaColor(stringToFloat(inProps.get("alphaColor"))); setXPosition(stringToInt(inProps.get("xPosition"))); setYPosition(stringToInt(inProps.get("yPosition"))); setInvertY(stringToBoolean(inProps.get("invertY"))); return this; } public String getText() { return text; } public PDFWatermarking setText(String inValue) { text = inValue; return this; } public String getFontFamily() { return fontFamily; } public PDFWatermarking setFontFamily(String inValue) { fontFamily = inValue == null || inValue.isEmpty() ? DEFAULT_FONT_FAMILY : inValue; return this; } public float getFontSize() { return fontSize; } public PDFWatermarking setFontSize(float inValue) { fontSize = inValue < 1f ? DEFAULT_FONT_SIZE : inValue; return this; } public int getTextRotation() { return textRotation; } public PDFWatermarking setTextRotation(int inValue) { textRotation = inValue < 1 ? DEFAULT_TEXT_ROTATION : inValue; return this; } public String getTextColor() { return hex255Color; } public PDFWatermarking setTextColor(String inValue) { hex255Color = inValue == null || inValue.isEmpty() ? DEFAULT_TEXT_COLOR : inValue; return this; } public float getAlphaColor() { return alphaColor; } public PDFWatermarking setAlphaColor(float inValue) { alphaColor = inValue < 0f || inValue > 1.0f ? DEFAULT_ALPHA : inValue; return this; } public float getXPosition() { return xPosition; } public PDFWatermarking setXPosition(float inValue) { xPosition = inValue < 0 ? DEFAULT_X_POSITION : inValue; return this; } public float getYPosition() { return yPosition; } public PDFWatermarking setYPosition(float inValue) { yPosition = inValue < 0 ? DEFAULT_Y_POSITION : inValue; return this; } public boolean isInvertY() { return invertY; } public PDFWatermarking setInvertY(boolean inValue) { invertY = inValue; return this; } }