001/* 002 * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Thibaud Arguillere 018 * Michael Vachette 019 */ 020package org.nuxeo.ecm.platform.pdf.service; 021 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025import org.apache.pdfbox.Overlay; 026import org.apache.pdfbox.exceptions.COSVisitorException; 027import org.apache.pdfbox.pdmodel.PDDocument; 028import org.apache.pdfbox.pdmodel.PDPage; 029import org.apache.pdfbox.pdmodel.PDResources; 030import org.apache.pdfbox.pdmodel.common.PDRectangle; 031import org.apache.pdfbox.pdmodel.edit.PDPageContentStream; 032import org.apache.pdfbox.pdmodel.font.PDFont; 033import org.apache.pdfbox.pdmodel.font.PDType1Font; 034import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState; 035import org.apache.pdfbox.pdmodel.graphics.xobject.PDPixelMap; 036import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage; 037import org.nuxeo.ecm.core.api.Blob; 038import org.nuxeo.ecm.core.api.Blobs; 039import org.nuxeo.ecm.core.api.NuxeoException; 040import org.nuxeo.ecm.platform.pdf.PDFUtils; 041import org.nuxeo.ecm.platform.pdf.service.watermark.WatermarkProperties; 042import org.nuxeo.runtime.model.DefaultComponent; 043 044import javax.imageio.ImageIO; 045import java.awt.*; 046import java.awt.geom.AffineTransform; 047import java.awt.geom.Point2D; 048import java.awt.geom.Rectangle2D; 049import java.awt.image.BufferedImage; 050import java.io.IOException; 051import java.util.HashMap; 052import java.util.List; 053import java.util.Map; 054 055/** 056 * @since 8.10 057 */ 058public class PDFTransformationServiceImpl extends DefaultComponent 059 implements PDFTransformationService { 060 061 protected static final Log log = LogFactory.getLog(PDFTransformationServiceImpl.class); 062 063 protected static final String MIME_TYPE = "application/pdf"; 064 065 @Override 066 public WatermarkProperties getDefaultProperties() { 067 return new WatermarkProperties(); 068 } 069 070 @Override 071 public Blob applyTextWatermark(Blob input, String text, WatermarkProperties properties) { 072 073 // Set up the graphic state to handle transparency 074 // Define a new extended graphic state 075 PDExtendedGraphicsState extendedGraphicsState = new PDExtendedGraphicsState(); 076 // Set the transparency/opacity 077 extendedGraphicsState.setNonStrokingAlphaConstant((float) properties.getAlphaColor()); 078 079 try (PDDocument pdfDoc = PDDocument.load(input.getStream())) { 080 081 PDFont font = PDType1Font.getStandardFont(properties.getFontFamily()); 082 float watermarkWidth = (float) (font.getStringWidth(text) * properties.getFontSize() 083 / 1000f); 084 int[] rgb = PDFUtils.hex255ToRGB(properties.getHex255Color()); 085 086 for (PDPage page : (List<PDPage>)pdfDoc.getDocumentCatalog().getAllPages()) { 087 PDRectangle pageSize = page.findMediaBox(); 088 PDResources resources = page.findResources(); 089 090 // Get the defined graphic states. 091 Map<String, PDExtendedGraphicsState> graphicsStates = resources.getGraphicsStates(); 092 if (graphicsStates == null) { 093 graphicsStates = new HashMap<>(); 094 } 095 graphicsStates.put("TransparentState", extendedGraphicsState); 096 resources.setGraphicsStates(graphicsStates); 097 098 try (PDPageContentStream contentStream = 099 new PDPageContentStream(pdfDoc, page, true, true, true)) { 100 contentStream.beginText(); 101 contentStream.setFont(font, (float) properties.getFontSize()); 102 contentStream.appendRawCommands("/TransparentState gs\n"); 103 contentStream.setNonStrokingColor(rgb[0], rgb[1], rgb[2]); 104 Point2D position = computeTranslationVector( 105 pageSize.getWidth(),watermarkWidth, 106 pageSize.getHeight(),properties.getFontSize(),properties); 107 contentStream.setTextRotation( 108 Math.toRadians(properties.getRotation()), 109 position.getX(), 110 position.getY()); 111 contentStream.drawString(text); 112 contentStream.endText(); 113 } 114 } 115 return saveInTempFile(pdfDoc); 116 117 } catch (IOException | COSVisitorException e) { 118 throw new NuxeoException(e); 119 } 120 } 121 122 @Override 123 public Blob applyImageWatermark(Blob input, Blob watermark, WatermarkProperties properties) { 124 125 // Set up the graphic state to handle transparency 126 // Define a new extended graphic state 127 PDExtendedGraphicsState extendedGraphicsState = new PDExtendedGraphicsState(); 128 // Set the transparency/opacity 129 extendedGraphicsState.setNonStrokingAlphaConstant((float) properties.getAlphaColor()); 130 131 try (PDDocument pdfDoc = PDDocument.load(input.getStream())){ 132 BufferedImage image = ImageIO.read(watermark.getStream()); 133 PDXObjectImage ximage = new PDPixelMap(pdfDoc, image); 134 135 for (PDPage page : (List<PDPage>)pdfDoc.getDocumentCatalog().getAllPages()) { 136 PDRectangle pageSize = page.findMediaBox(); 137 PDResources resources = page.findResources(); 138 139 // Get the defined graphic states. 140 // Get the defined graphic states. 141 Map<String, PDExtendedGraphicsState> graphicsStates = resources.getGraphicsStates(); 142 if (graphicsStates == null) { 143 graphicsStates = new HashMap<>(); 144 } 145 graphicsStates.put("TransparentState", extendedGraphicsState); 146 resources.setGraphicsStates(graphicsStates); 147 148 try (PDPageContentStream contentStream = new PDPageContentStream(pdfDoc, page, true, true)) { 149 contentStream.appendRawCommands("/TransparentState gs\n"); 150 contentStream.endMarkedContentSequence(); 151 152 double watermarkWidth = ximage.getWidth()*properties.getScale(); 153 double watermarkHeight = ximage.getHeight()*properties.getScale(); 154 155 Point2D position = computeTranslationVector( 156 pageSize.getWidth(),watermarkWidth, 157 pageSize.getHeight(),watermarkHeight,properties); 158 159 contentStream.drawXObject( 160 ximage, 161 (float)position.getX(), 162 (float)position.getY(), 163 (float)watermarkWidth, 164 (float)watermarkHeight); 165 } 166 } 167 return saveInTempFile(pdfDoc); 168 } catch (COSVisitorException | IOException e) { 169 throw new NuxeoException(e); 170 } 171 } 172 173 @Override 174 public Blob overlayPDF(Blob input, Blob overlayBlob) { 175 try (PDDocument pdfDoc = PDDocument.load(input.getStream()); 176 PDDocument pdfOverlayDoc = PDDocument.load(overlayBlob.getStream())) { 177 Overlay overlay = new Overlay(); 178 overlay.overlay(pdfOverlayDoc, pdfDoc); 179 return saveInTempFile(pdfDoc); 180 } catch (IOException | COSVisitorException e) { 181 throw new NuxeoException(e); 182 } 183 } 184 185 public Point2D computeTranslationVector(double pageWidth, double watermarkWidth, 186 double pageHeight, double watermarkHeight, 187 WatermarkProperties properties) { 188 double xRotationOffset = 0; 189 double yRotationOffset = 0; 190 191 if (properties.getRotation() != 0) { 192 Rectangle2D rectangle2D = 193 new Rectangle2D.Double( 194 0, -watermarkHeight, watermarkWidth, watermarkHeight); 195 AffineTransform at = AffineTransform.getRotateInstance( 196 -Math.toRadians(properties.getRotation()), 0, 0); 197 Shape shape = at.createTransformedShape(rectangle2D); 198 Rectangle2D rotated = shape.getBounds2D(); 199 200 watermarkWidth = rotated.getWidth(); 201 if (!properties.isInvertX() || properties.isRelativeCoordinates()) { 202 xRotationOffset = -rotated.getX(); 203 } else { 204 xRotationOffset = rotated.getX(); 205 } 206 207 watermarkHeight = rotated.getHeight(); 208 if (!properties.isInvertY() || properties.isRelativeCoordinates()) { 209 yRotationOffset = rotated.getY()+rotated.getHeight(); 210 } else { 211 yRotationOffset = -(rotated.getY()+rotated.getHeight()); 212 } 213 } 214 215 double xTranslation; 216 double yTranslation; 217 218 if (properties.isRelativeCoordinates()) { 219 xTranslation = (pageWidth - watermarkWidth ) * properties.getxPosition() + xRotationOffset; 220 yTranslation = (pageHeight - watermarkHeight ) * properties.getyPosition() + yRotationOffset; 221 } else { 222 xTranslation = properties.getxPosition() + xRotationOffset; 223 yTranslation = properties.getyPosition() + yRotationOffset; 224 if (properties.isInvertX()) { 225 xTranslation = pageWidth - watermarkWidth - xTranslation; 226 } 227 if (properties.isInvertY()) { 228 yTranslation = pageHeight - watermarkHeight - yTranslation; 229 } 230 } 231 return new Point2D.Double(xTranslation, yTranslation); 232 } 233 234 protected Blob saveInTempFile(PDDocument PdfDoc) throws IOException, COSVisitorException { 235 Blob blob = Blobs.createBlobWithExtension(".pdf"); // creates a tracked temporary file 236 blob.setMimeType(MIME_TYPE); 237 PdfDoc.save(blob.getFile()); 238 return blob; 239 } 240 241}