Java tutorial
/** * * Copyright 2009-2014 Jayway Products AB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package se.streamsource.streamflow.web.application.pdf; import org.apache.pdfbox.cos.*; import org.apache.pdfbox.pdfparser.PDFParser; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.COSStreamArray; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.edit.PDPageContentStream; import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm; import org.apache.pdfbox.util.MapUtil; import java.io.*; import java.util.*; /** * Underlay one document with a template, mainly used to apply header/footer to an existing document * How it (should) work:<br> * If the document has 10 pages, and the layout 2 the following is the result:<br> * <pre> * Document: 1234567890 * Layout : 1212121212 * </pre> * <br> * <p/> * This is based on the org.apache.pdfbox.Overlay class but has been modified to work properly by * * @author Henrik Reinhold (henrik.reinhold@jayway.com) * <p/> * Original authors: * @author Mario Ivankovits (mario@ops.co.at) * @author <a href="ben@benlitchfield.com">Ben Litchfield</a> */ public class Underlay { private List layoutPages = new ArrayList(10); private PDDocument pdfUnderlay; private PDDocument pdfDocument; private int pageCount = 0; private COSStream saveGraphicsStateStream; private COSStream restoreGraphicsStateStream; private static PDDocument getDocument(String filename) throws IOException { FileInputStream input = null; PDFParser parser = null; PDDocument result = null; try { input = new FileInputStream(filename); parser = new PDFParser(input); parser.parse(); result = parser.getPDDocument(); } finally { if (input != null) { input.close(); } } return result; } /** * Private class. */ private static class LayoutPage { private final COSBase contents; private final COSDictionary res; private final Map objectNameMap; /** * Constructor. * * @param contentsValue The contents. * @param resValue The resource dictionary * @param objectNameMapValue The map */ public LayoutPage(COSBase contentsValue, COSDictionary resValue, Map objectNameMapValue) { contents = contentsValue; res = resValue; objectNameMap = objectNameMapValue; } } /** * This will underlay two documents onto each other. The underlay document is * repeatedly underlayed under the destination document for every page in the * destination. */ public PDDocument underlay(PDDocument destination, InputStream templateStream) throws IOException { pdfDocument = destination; try { PDFParser parser = new PDFParser(templateStream); parser.parse(); pdfUnderlay = parser.getPDDocument(); overlayWithDarkenBlendMode(pdfDocument, pdfUnderlay); } finally { if (templateStream != null) { templateStream.close(); } } return pdfDocument; } private void overlayWithDarkenBlendMode(PDDocument document, PDDocument overlay) throws IOException { PDXObjectForm xobject = importAsXObject(document, (PDPage) overlay.getDocumentCatalog().getAllPages().get(0)); PDExtendedGraphicsState darken = new PDExtendedGraphicsState(); darken.getCOSDictionary().setName("BM", "Darken"); List<PDPage> pages = document.getDocumentCatalog().getAllPages(); for (PDPage page : pages) { Map<String, PDExtendedGraphicsState> states = page.getResources().getGraphicsStates(); if (states == null) states = new HashMap<>(); String darkenKey = MapUtil.getNextUniqueKey(states, "Dkn"); states.put(darkenKey, darken); page.getResources().setGraphicsStates(states); PDPageContentStream stream = new PDPageContentStream(document, page, true, false, true); stream.appendRawCommands(String.format("/%s gs ", darkenKey)); stream.drawXObject(xobject, 0, 0, 1, 1); stream.close(); } } private PDXObjectForm importAsXObject(PDDocument target, PDPage page) throws IOException { final PDStream xobjectStream = new PDStream(target, page.getContents().createInputStream(), false); final PDXObjectForm xobject = new PDXObjectForm(xobjectStream); xobject.setResources(page.findResources()); xobject.setBBox(page.findCropBox()); COSDictionary group = new COSDictionary(); group.setName("S", "Transparency"); group.setBoolean(COSName.getPDFName("K"), true); xobject.getCOSStream().setItem(COSName.getPDFName("Group"), group); return xobject; } private COSStream makeUniqObjectNames(Map objectNameMap, COSStream stream) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(10240); byte[] buf = new byte[10240]; int read; InputStream is = stream.getUnfilteredStream(); while ((read = is.read(buf)) > -1) { baos.write(buf, 0, read); } buf = baos.toByteArray(); baos = new ByteArrayOutputStream(buf.length + 100); StringBuffer sbObjectName = new StringBuffer(10); boolean bInObjectIdent = false; boolean bInText = false; boolean bInEscape = false; for (int i = 0; i < buf.length; i++) { byte b = buf[i]; if (!bInEscape) { if (!bInText && b == '(') { bInText = true; } if (bInText && b == ')') { bInText = false; } if (b == '\\') { bInEscape = true; } if (!bInText && !bInEscape) { if (b == '/') { bInObjectIdent = true; } else if (bInObjectIdent && Character.isWhitespace((char) b)) { bInObjectIdent = false; String objectName = sbObjectName.toString().substring(1); String newObjectName = objectName + "overlay"; baos.write('/'); baos.write(newObjectName.getBytes()); objectNameMap.put(objectName, COSName.getPDFName(newObjectName)); sbObjectName.delete(0, sbObjectName.length()); } } if (bInObjectIdent) { sbObjectName.append((char) b); continue; } } else { bInEscape = false; } baos.write(b); } COSDictionary streamDict = new COSDictionary(); streamDict.setInt(COSName.LENGTH, baos.size()); COSStream output = new COSStream(streamDict, pdfDocument.getDocument().getScratchFile()); output.setFilters(stream.getFilters()); OutputStream os = output.createUnfilteredStream(); baos.writeTo(os); os.close(); return output; } private void processPages(List pages) throws IOException { Iterator pageIter = pages.iterator(); while (pageIter.hasNext()) { PDPage page = (PDPage) pageIter.next(); COSDictionary pageDictionary = page.getCOSDictionary(); COSBase contents = pageDictionary.getDictionaryObject(COSName.CONTENTS); if (contents instanceof COSStreamArray) { COSStreamArray cosStreamArray = (COSStreamArray) contents; COSArray array = new COSArray(); for (int i = 0; i < cosStreamArray.getStreamCount(); i++) { array.add(cosStreamArray.get(i)); } mergePage(array, page); pageDictionary.setItem(COSName.CONTENTS, array); } else if (contents instanceof COSStream) { COSStream contentsStream = (COSStream) contents; COSArray array = new COSArray(); array.add(contentsStream); mergePage(array, page); pageDictionary.setItem(COSName.CONTENTS, array); } else if (contents instanceof COSArray) { COSArray contentsArray = (COSArray) contents; mergePage(contentsArray, page); } else { throw new IOException("Contents are unknown type:" + contents.getClass().getName()); } pageCount++; } } private void mergePage(COSArray array, PDPage page) { int layoutPageNum = pageCount % layoutPages.size(); LayoutPage layoutPage = (LayoutPage) layoutPages.get(layoutPageNum); PDResources resources = page.findResources(); if (resources == null) { resources = new PDResources(); page.setResources(resources); } COSDictionary docResDict = resources.getCOSDictionary(); COSDictionary layoutResDict = layoutPage.res; mergeArray(COSName.PROC_SET, docResDict, layoutResDict); mergeDictionary(COSName.COLORSPACE, docResDict, layoutResDict, layoutPage.objectNameMap); mergeDictionary(COSName.FONT, docResDict, layoutResDict, layoutPage.objectNameMap); mergeDictionary(COSName.XOBJECT, docResDict, layoutResDict, layoutPage.objectNameMap); mergeDictionary(COSName.EXT_G_STATE, docResDict, layoutResDict, layoutPage.objectNameMap); array.add(0, layoutPage.contents); } /** * merges two dictionaries. * * @param dest * @param source */ private void mergeDictionary(COSName name, COSDictionary dest, COSDictionary source, Map objectNameMap) { COSDictionary destDict = (COSDictionary) dest.getDictionaryObject(name); COSDictionary sourceDict = (COSDictionary) source.getDictionaryObject(name); if (destDict == null) { destDict = new COSDictionary(); dest.setItem(name, destDict); } if (sourceDict != null) { for (Map.Entry<COSName, COSBase> entry : sourceDict.entrySet()) { COSName mappedKey = (COSName) objectNameMap.get(entry.getKey().getName()); if (mappedKey != null) { destDict.setItem(mappedKey, entry.getValue()); } } } } /** * merges two arrays. * * @param dest * @param source */ private void mergeArray(COSName name, COSDictionary dest, COSDictionary source) { COSArray destDict = (COSArray) dest.getDictionaryObject(name); COSArray sourceDict = (COSArray) source.getDictionaryObject(name); if (destDict == null) { destDict = new COSArray(); dest.setItem(name, destDict); } for (int sourceDictIdx = 0; sourceDict != null && sourceDictIdx < sourceDict.size(); sourceDictIdx++) { COSBase key = sourceDict.get(sourceDictIdx); if (key instanceof COSName) { COSName keyname = (COSName) key; boolean bFound = false; for (int destDictIdx = 0; destDictIdx < destDict.size(); destDictIdx++) { COSBase destkey = destDict.get(destDictIdx); if (destkey instanceof COSName) { COSName destkeyname = (COSName) destkey; if (destkeyname.equals(keyname)) { bFound = true; break; } } } if (!bFound) { destDict.add(keyname); } } } } }