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