001/* 002 * (C) Copyright 2016-2018 Nuxeo (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 * Miguel Nixo 019 */ 020package org.nuxeo.ecm.platform.pdf; 021 022import java.io.File; 023import java.io.IOException; 024import java.util.List; 025 026import org.apache.commons.lang3.StringUtils; 027import org.apache.pdfbox.exceptions.COSVisitorException; 028import org.apache.pdfbox.pdmodel.PDDocument; 029import org.apache.pdfbox.pdmodel.PDPage; 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.nuxeo.ecm.core.api.Blob; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.NuxeoException; 037import org.nuxeo.ecm.core.api.impl.blob.FileBlob; 038import org.nuxeo.runtime.api.Framework; 039 040/** 041 * Add page numbers to a PDF, with misc parameters (font, size, color, position). 042 * 043 * @since 8.10 044 */ 045public class PDFPageNumbering { 046 047 public static final float DEFAULT_FONT_SIZE = 16.0f; 048 049 private Blob blob; 050 051 private String password; 052 053 public enum PAGE_NUMBER_POSITION { 054 BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT, TOP_LEFT, TOP_CENTER, TOP_RIGHT 055 } 056 057 public PDFPageNumbering(Blob inBlob) { 058 blob = inBlob; 059 } 060 061 public PDFPageNumbering(DocumentModel inDoc, String inXPath) { 062 if (StringUtils.isBlank(inXPath)) { 063 inXPath = "file:content"; 064 } 065 blob = (Blob) inDoc.getPropertyValue(inXPath); 066 } 067 068 /** 069 * Adds page numbers and returns a <i>new</i> Blob. Original blob is not modified. This code assumes: 070 * <ul> 071 * <li>There is no page numbers already (it will always draw the numbers).</li> 072 * <li>The PDF is not rotated.</li> 073 * <li>Default values apply: 074 * <ul> 075 * <li><code>inStartAtPage</code> and <code>inStartAtNumber</code> are set to 1 if they are passed as < 1.</li> 076 * <li><code>inStartAtPage</code> is set to 1 if it is > number of pages.</li> 077 * <li><code>inFontName</code> is set to "Helvetica" if "" or null.</li> 078 * <li><code>inFontSize</code> is set to 16 if it is <= 0.</li> 079 * <li><code>inHex255Color</code> is set to black if "", null or if its length is < 6. Expected format is 080 * <code>0xrrggbb</code>, <code>#rrggbb</code> or just <code>rrggbb</code>.</li> 081 * <li><code>inPosition</code> is set to <code>BOTTOM_RIGHT</code> if null.</li> 082 * </ul> 083 * </li> 084 * </ul> 085 * 086 * @param inStartAtPage Number of the first page to be numbered. 087 * @param inStartAtNumber Starting number for the page numbering. 088 * @param inFontName Name of the font to be used in the numbering. 089 * @param inFontSize Size of the font to be used in the numbering. 090 * @param inHex255Color Color of the font to be used in the numbering. 091 * @param inPosition Page positioning of the numbering. 092 * @return Blob 093 */ 094 public Blob addPageNumbers(int inStartAtPage, int inStartAtNumber, String inFontName, float inFontSize, 095 String inHex255Color, PAGE_NUMBER_POSITION inPosition) throws NuxeoException { 096 Blob result; 097 inStartAtPage = inStartAtPage < 1 ? 1 : inStartAtPage; 098 int pageNumber = inStartAtNumber < 1 ? 1 : inStartAtNumber; 099 inFontSize = inFontSize <= 0 ? DEFAULT_FONT_SIZE : inFontSize; 100 int[] rgb = PDFUtils.hex255ToRGB(inHex255Color); 101 try (PDDocument doc = PDFUtils.load(blob, password)) { 102 List allPages; 103 PDFont font; 104 int max; 105 if (StringUtils.isBlank(inFontName)) { 106 font = PDType1Font.HELVETICA; 107 } else { 108 font = PDType1Font.getStandardFont(inFontName); 109 if (font == null) { 110 font = new PDType1Font(inFontName); 111 } 112 } 113 allPages = doc.getDocumentCatalog().getAllPages(); 114 max = allPages.size(); 115 inStartAtPage = inStartAtPage > max ? 1 : inStartAtPage; 116 for (int i = inStartAtPage; i <= max; i++) { 117 String pageNumAsStr = Integer.toString(pageNumber); 118 pageNumber += 1; 119 PDPage page = (PDPage) allPages.get(i - 1); 120 PDPageContentStream footercontentStream = new PDPageContentStream(doc, page, true, true); 121 float stringWidth = font.getStringWidth(pageNumAsStr) * inFontSize / 1000f; 122 float stringHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() * inFontSize / 1000; 123 PDRectangle pageRect = page.findMediaBox(); 124 float xMoveAmount, yMoveAmount; 125 if (inPosition == null) { 126 inPosition = PAGE_NUMBER_POSITION.BOTTOM_RIGHT; 127 } 128 switch (inPosition) { 129 case BOTTOM_LEFT: 130 xMoveAmount = 10; 131 yMoveAmount = pageRect.getLowerLeftY() + 10; 132 break; 133 case BOTTOM_CENTER: 134 xMoveAmount = (pageRect.getUpperRightX() / 2) - (stringWidth / 2); 135 yMoveAmount = pageRect.getLowerLeftY() + 10; 136 break; 137 case TOP_LEFT: 138 xMoveAmount = 10; 139 yMoveAmount = pageRect.getHeight() - stringHeight - 10; 140 break; 141 case TOP_CENTER: 142 xMoveAmount = (pageRect.getUpperRightX() / 2) - (stringWidth / 2); 143 yMoveAmount = pageRect.getHeight() - stringHeight - 10; 144 break; 145 case TOP_RIGHT: 146 xMoveAmount = pageRect.getUpperRightX() - 10 - stringWidth; 147 yMoveAmount = pageRect.getHeight() - stringHeight - 10; 148 break; 149 // Bottom-right is the default 150 default: 151 xMoveAmount = pageRect.getUpperRightX() - 10 - stringWidth; 152 yMoveAmount = pageRect.getLowerLeftY() + 10; 153 break; 154 } 155 footercontentStream.beginText(); 156 footercontentStream.setFont(font, inFontSize); 157 footercontentStream.moveTextPositionByAmount(xMoveAmount, yMoveAmount); 158 footercontentStream.setNonStrokingColor(rgb[0], rgb[1], rgb[2]); 159 footercontentStream.drawString(pageNumAsStr); 160 footercontentStream.endText(); 161 footercontentStream.close(); 162 } 163 File tempFile = File.createTempFile("pdfutils-", ".pdf"); 164 doc.save(tempFile); 165 result = new FileBlob(tempFile); 166 Framework.trackFile(tempFile, result); 167 } catch (IOException | COSVisitorException e) { 168 throw new NuxeoException("Failed to handle the pdf", e); 169 } 170 return result; 171 } 172 173 public void setPassword(String password) { 174 this.password = password; 175 } 176 177}