001: /**
002: * Copyright (c) 2003, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox;
031:
032: import org.pdfbox.cos.COSArray;
033: import org.pdfbox.cos.COSBase;
034: import org.pdfbox.cos.COSDictionary;
035: import org.pdfbox.cos.COSName;
036: import org.pdfbox.cos.COSStream;
037: import org.pdfbox.cos.COSInteger;
038: import org.pdfbox.exceptions.COSVisitorException;
039: import org.pdfbox.pdfparser.PDFParser;
040: import org.pdfbox.pdfwriter.COSWriter;
041:
042: import org.pdfbox.pdmodel.PDDocument;
043: import org.pdfbox.pdmodel.PDDocumentCatalog;
044: import org.pdfbox.pdmodel.PDPage;
045: import org.pdfbox.pdmodel.PDResources;
046:
047: import java.io.FileInputStream;
048: import java.io.FileOutputStream;
049: import java.io.IOException;
050: import java.io.InputStream;
051: import java.io.ByteArrayOutputStream;
052: import java.io.OutputStream;
053:
054: import java.util.ArrayList;
055: import java.util.Iterator;
056: import java.util.List;
057: import java.util.TreeMap;
058: import java.util.Map;
059:
060: /**
061: * Overlay on document with another one.<br>
062: * e.g. Overlay an invoice with your company layout<br>
063: * <br>
064: * How it (should) work:<br>
065: * If the document has 10 pages, and the layout 2 the following is the result:<br>
066: * <pre>
067: * Document: 1234567890
068: * Layout : 1212121212
069: * </pre>
070: * <br>
071: *
072: * @author Mario Ivankovits (mario@ops.co.at)
073: * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
074: *
075: * @version $Revision: 1.6 $
076: */
077: public class Overlay {
078: /**
079: * COSName constant.
080: */
081: public static final COSName XOBJECT = COSName.getPDFName("XObject");
082: /**
083: * COSName constant.
084: */
085: public static final COSName PROC_SET = COSName
086: .getPDFName("ProcSet");
087: /**
088: * COSName constant.
089: */
090: public static final COSName EXT_G_STATE = COSName
091: .getPDFName("ExtGState");
092:
093: private List layoutPages = new ArrayList(10);
094:
095: private PDDocument pdfOverlay;
096: private PDDocument pdfDocument;
097: private int pageCount = 0;
098: private COSStream saveGraphicsStateStream;
099: private COSStream restoreGraphicsStateStream;
100:
101: /**
102: * This will overlay a document and write out the results.<br/><br/>
103: *
104: * usage: java org.pdfbox.Overlay <overlay.pdf> <document.pdf> <result.pdf>
105: *
106: * @param args The command line arguments.
107: *
108: * @throws IOException If there is an error reading/writing the document.
109: * @throws COSVisitorException If there is an error writing the document.
110: */
111: public static void main(String[] args) throws IOException,
112: COSVisitorException {
113: if (args.length != 3) {
114: usage();
115: System.exit(1);
116: } else {
117: PDDocument overlay = null;
118: PDDocument pdf = null;
119:
120: try {
121: overlay = getDocument(args[0]);
122: pdf = getDocument(args[1]);
123: Overlay overlayer = new Overlay();
124: overlayer.overlay(overlay, pdf);
125: writeDocument(pdf, args[2]);
126: } finally {
127: if (overlay != null) {
128: overlay.close();
129: }
130: if (pdf != null) {
131: pdf.close();
132: }
133: }
134: }
135: }
136:
137: private static void writeDocument(PDDocument pdf, String filename)
138: throws IOException, COSVisitorException {
139: FileOutputStream output = null;
140: COSWriter writer = null;
141: try {
142: output = new FileOutputStream(filename);
143: writer = new COSWriter(output);
144: writer.write(pdf);
145: } finally {
146: if (writer != null) {
147: writer.close();
148: }
149: if (output != null) {
150: output.close();
151: }
152: }
153: }
154:
155: private static PDDocument getDocument(String filename)
156: throws IOException {
157: FileInputStream input = null;
158: PDFParser parser = null;
159: PDDocument result = null;
160: try {
161: input = new FileInputStream(filename);
162: parser = new PDFParser(input);
163: parser.parse();
164: result = parser.getPDDocument();
165: } finally {
166: if (input != null) {
167: input.close();
168: }
169: }
170: return result;
171: }
172:
173: private static void usage() {
174: System.err.println("usage: java " + Overlay.class.getName()
175: + "<overlay.pdf> <document.pdf> <result.pdf>");
176: }
177:
178: /**
179: * Private class.
180: */
181: private static class LayoutPage {
182: private final COSBase contents;
183: private final COSDictionary res;
184: private final Map objectNameMap;
185:
186: /**
187: * Constructor.
188: *
189: * @param contentsValue The contents.
190: * @param resValue The resource dictionary
191: * @param objectNameMapValue The map
192: */
193: public LayoutPage(COSBase contentsValue,
194: COSDictionary resValue, Map objectNameMapValue) {
195: contents = contentsValue;
196: res = resValue;
197: objectNameMap = objectNameMapValue;
198: }
199: }
200:
201: /**
202: * This will overlay two documents onto each other. The overlay document is
203: * repeatedly overlayed onto the destination document for every page in the
204: * destination.
205: *
206: * @param overlay The document to copy onto the destination
207: * @param destination The file that the overlay should be placed on.
208: *
209: * @return The destination pdf, same as argument passed in.
210: *
211: * @throws IOException If there is an error accessing data.
212: */
213: public PDDocument overlay(PDDocument overlay, PDDocument destination)
214: throws IOException {
215: pdfOverlay = overlay;
216: pdfDocument = destination;
217:
218: PDDocumentCatalog overlayCatalog = pdfOverlay
219: .getDocumentCatalog();
220: collectLayoutPages(overlayCatalog.getAllPages());
221:
222: COSDictionary saveGraphicsStateDic = new COSDictionary();
223: saveGraphicsStateStream = new COSStream(saveGraphicsStateDic,
224: pdfDocument.getDocument().getScratchFile());
225: OutputStream saveStream = saveGraphicsStateStream
226: .createUnfilteredStream();
227: saveStream.write(" q\n".getBytes());
228: saveStream.flush();
229:
230: restoreGraphicsStateStream = new COSStream(
231: saveGraphicsStateDic, pdfDocument.getDocument()
232: .getScratchFile());
233: OutputStream restoreStream = restoreGraphicsStateStream
234: .createUnfilteredStream();
235: restoreStream.write(" Q\n".getBytes());
236: restoreStream.flush();
237:
238: PDDocumentCatalog pdfCatalog = pdfDocument.getDocumentCatalog();
239: processPages(pdfCatalog.getAllPages());
240:
241: return pdfDocument;
242: }
243:
244: private void collectLayoutPages(List pages) throws IOException {
245: Iterator pagesIter = pages.iterator();
246: while (pagesIter.hasNext()) {
247: PDPage page = (PDPage) pagesIter.next();
248: COSBase contents = page.getCOSDictionary()
249: .getDictionaryObject(COSName.CONTENTS);
250: PDResources resources = page.findResources();
251: if (resources == null) {
252: resources = new PDResources();
253: page.setResources(resources);
254: }
255: COSDictionary res = resources.getCOSDictionary();
256:
257: if (contents instanceof COSStream) {
258: COSStream stream = (COSStream) contents;
259: Map objectNameMap = new TreeMap();
260: stream = makeUniqObjectNames(objectNameMap, stream);
261:
262: layoutPages.add(new LayoutPage(stream, res,
263: objectNameMap));
264: } else if (contents instanceof COSArray) {
265: throw new UnsupportedOperationException(
266: "Layout pages with COSArray currently not supported.");
267: // layoutPages.add(new LayoutPage(contents, res));
268: } else {
269: throw new IOException("Contents are unknown type:"
270: + contents.getClass().getName());
271: }
272: }
273: }
274:
275: private COSStream makeUniqObjectNames(Map objectNameMap,
276: COSStream stream) throws IOException {
277: ByteArrayOutputStream baos = new ByteArrayOutputStream(10240);
278:
279: byte[] buf = new byte[10240];
280: int read;
281: InputStream is = stream.getUnfilteredStream();
282: while ((read = is.read(buf)) > -1) {
283: baos.write(buf, 0, read);
284: }
285:
286: buf = baos.toByteArray();
287: baos = new ByteArrayOutputStream(buf.length + 100);
288: StringBuffer sbObjectName = new StringBuffer(10);
289: boolean bInObjectIdent = false;
290: boolean bInText = false;
291: boolean bInEscape = false;
292: for (int i = 0; i < buf.length; i++) {
293: byte b = buf[i];
294:
295: if (!bInEscape) {
296: if (!bInText && b == '(') {
297: bInText = true;
298: }
299: if (bInText && b == ')') {
300: bInText = false;
301: }
302: if (b == '\\') {
303: bInEscape = true;
304: }
305:
306: if (!bInText && !bInEscape) {
307: if (b == '/') {
308: bInObjectIdent = true;
309: } else if (bInObjectIdent
310: && Character.isWhitespace((char) b)) {
311: bInObjectIdent = false;
312:
313: // System.err.println(sbObjectName);
314: // String object = sbObjectName.toString();
315:
316: String objectName = sbObjectName.toString()
317: .substring(1);
318: String newObjectName = objectName + "overlay";
319: baos.write('/');
320: baos.write(newObjectName.getBytes());
321:
322: objectNameMap.put(objectName, COSName
323: .getPDFName(newObjectName));
324:
325: sbObjectName.delete(0, sbObjectName.length());
326: }
327: }
328:
329: if (bInObjectIdent) {
330: sbObjectName.append((char) b);
331: continue;
332: }
333: } else {
334: bInEscape = false;
335: }
336:
337: baos.write(b);
338: }
339:
340: COSDictionary streamDict = new COSDictionary();
341: streamDict.setItem(COSName.LENGTH, new COSInteger(baos.size()));
342: COSStream output = new COSStream(streamDict, pdfDocument
343: .getDocument().getScratchFile());
344: output.setFilters(stream.getFilters());
345: OutputStream os = output.createUnfilteredStream();
346: baos.writeTo(os);
347: os.close();
348:
349: return output;
350: }
351:
352: private void processPages(List pages) throws IOException {
353: Iterator pageIter = pages.iterator();
354: while (pageIter.hasNext()) {
355: PDPage page = (PDPage) pageIter.next();
356: COSDictionary pageDictionary = page.getCOSDictionary();
357: COSBase contents = pageDictionary
358: .getDictionaryObject(COSName.CONTENTS);
359: if (contents instanceof COSStream) {
360: COSStream contentsStream = (COSStream) contents;
361: // System.err.println("stream");
362: pageCount++;
363:
364: COSArray array = new COSArray();
365:
366: array.add(contentsStream);
367:
368: mergePage(array, page);
369:
370: pageDictionary.setItem(COSName.CONTENTS, array);
371: } else if (contents instanceof COSArray) {
372: COSArray contentsArray = (COSArray) contents;
373:
374: mergePage(contentsArray, page);
375: } else {
376: throw new IOException("Contents are unknown type:"
377: + contents.getClass().getName());
378: }
379: }
380: }
381:
382: private void mergePage(COSArray array, PDPage page) {
383: int layoutPageNum = pageCount % layoutPages.size();
384: LayoutPage layoutPage = (LayoutPage) layoutPages
385: .get(layoutPageNum);
386: PDResources resources = page.findResources();
387: if (resources == null) {
388: resources = new PDResources();
389: page.setResources(resources);
390: }
391: COSDictionary docResDict = resources.getCOSDictionary();
392: COSDictionary layoutResDict = layoutPage.res;
393: mergeArray(PROC_SET, docResDict, layoutResDict);
394: mergeDictionary(COSName.FONT, docResDict, layoutResDict,
395: layoutPage.objectNameMap);
396: mergeDictionary(XOBJECT, docResDict, layoutResDict,
397: layoutPage.objectNameMap);
398: mergeDictionary(EXT_G_STATE, docResDict, layoutResDict,
399: layoutPage.objectNameMap);
400:
401: //we are going to wrap the existing content around some save/restore
402: //graphics state, so the result is
403: //
404: //<save graphics state>
405: //<all existing content streams>
406: //<restore graphics state>
407: //<overlay content>
408: array.add(0, saveGraphicsStateStream);
409: array.add(restoreGraphicsStateStream);
410: array.add(layoutPage.contents);
411: }
412:
413: /**
414: * merges two dictionaries.
415: *
416: * @param dest
417: * @param source
418: */
419: private void mergeDictionary(COSName name, COSDictionary dest,
420: COSDictionary source, Map objectNameMap) {
421: COSDictionary destDict = (COSDictionary) dest
422: .getDictionaryObject(name);
423: COSDictionary sourceDict = (COSDictionary) source
424: .getDictionaryObject(name);
425:
426: if (destDict == null) {
427: destDict = new COSDictionary();
428: dest.setItem(name, destDict);
429: }
430: if (sourceDict != null) {
431:
432: Iterator iterKeys = sourceDict.keyList().iterator();
433: while (iterKeys.hasNext()) {
434: COSName key = (COSName) iterKeys.next();
435: COSName mappedKey = (COSName) objectNameMap.get(key
436: .getName());
437: if (mappedKey == null) {
438: // object not needet
439: continue;
440: }
441:
442: destDict.setItem(mappedKey, sourceDict.getItem(key));
443: }
444: }
445: }
446:
447: /**
448: * merges two arrays.
449: *
450: * @param dest
451: * @param source
452: */
453: private void mergeArray(COSName name, COSDictionary dest,
454: COSDictionary source) {
455: COSArray destDict = (COSArray) dest.getDictionaryObject(name);
456: COSArray sourceDict = (COSArray) source
457: .getDictionaryObject(name);
458:
459: if (destDict == null) {
460: destDict = new COSArray();
461: dest.setItem(name, destDict);
462: }
463:
464: for (int sourceDictIdx = 0; sourceDict != null
465: && sourceDictIdx < sourceDict.size(); sourceDictIdx++) {
466: COSBase key = sourceDict.get(sourceDictIdx);
467: if (key instanceof COSName) {
468: COSName keyname = (COSName) key;
469:
470: boolean bFound = false;
471: for (int destDictIdx = 0; destDictIdx < destDict.size(); destDictIdx++) {
472: COSBase destkey = destDict.get(destDictIdx);
473: if (destkey instanceof COSName) {
474: COSName destkeyname = (COSName) destkey;
475: if (destkeyname.equals(keyname)) {
476: bFound = true;
477: break;
478: }
479: }
480: }
481: if (!bFound) {
482: destDict.add(keyname);
483: }
484: }
485: }
486: }
487: }
|