You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2013/02/10 18:01:20 UTC

svn commit: r1444565 - /pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/OverlayPDF.java

Author: lehmi
Date: Sun Feb 10 17:01:20 2013
New Revision: 1444565

URL: http://svn.apache.org/r1444565
Log:
PDFBOX-1514: added a new overlay command line tool based on the proposal of Balazs Jerk.

Added:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/OverlayPDF.java   (with props)

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/OverlayPDF.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/OverlayPDF.java?rev=1444565&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/OverlayPDF.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/OverlayPDF.java Sun Feb 10 17:01:20 2013
@@ -0,0 +1,588 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.pdfbox;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccessBuffer;
+import org.apache.pdfbox.pdfparser.BaseParser;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.util.MapUtil;
+
+/**
+ * Adds an overlay to an existing PDF document.
+ *  
+ * Based on code contributed by Balazs Jerk. 
+ * 
+ */
+public class OverlayPDF 
+{
+    /**
+     *   Possible loacation of the overlayed pages: foreground or background.
+     */
+    private enum Position 
+    { 
+        FOREGROUND, BACKGROUND 
+    };
+
+    private static final Log LOG = LogFactory.getLog(BaseParser.class);
+
+    private static final String XOBJECT_PREFIX = "OL";
+    
+    private LayoutPage defaultOverlayPage;
+    private LayoutPage firstPageOverlayPage;
+    private LayoutPage lastPageOverlayPage;
+    private LayoutPage oddPageOverlayPage;
+    private LayoutPage evenPageOverlayPage;
+
+    private static Map<Integer, PDDocument> specificPageOverlay = new HashMap<Integer, PDDocument>();
+    private static Map<Integer, LayoutPage> specificPageOverlayPage = new HashMap<Integer, LayoutPage>();
+
+    private static Position overlayPosition = Position.BACKGROUND;
+
+    private static boolean useNonSeqParser = false;
+    private static String inputFile = null;
+    private static String outputFile = null;
+    private static String defaultOverlayFile = null;
+    private static String firstPageOverlayFile = null;
+    private static String lastPageOverlayFile = null;
+    private static String oddPageOverlayFile = null;
+    private static String evenPageOverlayFile = null;
+    
+    // Command line options
+    private static final String POSITION = "-position";
+    private static final String ODD = "-odd";
+    private static final String EVEN = "-even";
+    private static final String FIRST = "-first";
+    private static final String LAST = "-last";
+    private static final String PAGE = "-page";
+    private static final String NONSEQ = "-nonSeq";
+
+    /**
+     * This will overlay a document and write out the results.
+     *
+     * @param args command line arguments
+     * @throws Exception if something went wrong
+     * @see #USAGE
+     */
+    public static void main(final String[] args) throws Exception 
+    {
+        Map<Integer, String> specificPageOverlayFile = new HashMap<Integer, String>();
+        
+        // input arguments
+        for (int i = 0; i < args.length; i++) 
+        {
+            String arg = args[i].trim();
+            if (i == 0) 
+            {
+                inputFile = arg;
+            } 
+            else if (i == (args.length - 1)) 
+            {
+                outputFile = arg;
+            } 
+            else if (arg.equals(POSITION) && ((i + 1) < args.length)) 
+            {
+                if (Position.FOREGROUND.toString().equalsIgnoreCase(args[i + 1].trim())) 
+                {
+                    overlayPosition = Position.FOREGROUND;
+                }
+                else if (Position.BACKGROUND.toString().equalsIgnoreCase(args[i + 1].trim())) 
+                {
+                    overlayPosition = Position.BACKGROUND;
+                }
+                else
+                {
+                    usage();
+                }
+                i += 1;
+            } 
+            else if (arg.equals(ODD) && ((i + 1) < args.length)) 
+            {
+                oddPageOverlayFile = args[i + 1].trim();
+                i += 1;
+            } 
+            else if (arg.equals(EVEN) && ((i + 1) < args.length)) 
+            {
+                evenPageOverlayFile = args[i + 1].trim();
+                i += 1;
+            } 
+            else if (arg.equals(FIRST) && ((i + 1) < args.length)) 
+            {
+                firstPageOverlayFile = args[i + 1].trim();
+                i += 1;
+            } 
+            else if (arg.equals(LAST) && ((i + 1) < args.length)) 
+            {
+                lastPageOverlayFile = args[i + 1].trim();
+                i += 1;
+            } 
+            else if (arg.equals(PAGE) && ((i + 2) < args.length) && (isInteger(args[i + 1].trim()))) 
+            {
+                specificPageOverlayFile.put(Integer.parseInt(args[i + 1].trim()), args[i + 2].trim());
+                i += 2;
+            } 
+            else if( args[i].equals( NONSEQ ) )
+            {
+                useNonSeqParser = true;
+            }
+            else if (defaultOverlayFile == null) 
+            {
+                defaultOverlayFile = arg;
+            } 
+            else 
+            {
+                usage();
+            }
+        }
+        
+        if ((inputFile == null) || (outputFile == null)) 
+        {
+            usage();
+        }
+        
+        try 
+        {
+            OverlayPDF overlayer = new OverlayPDF();
+            overlayer.overlay(specificPageOverlayFile);
+        } 
+        catch (Exception e) 
+        {
+            LOG.error("Overlay failed: " + e.getMessage(), e);
+            throw e;
+        }
+    }
+
+    private static void usage()
+    {
+        StringBuilder message = new StringBuilder();
+        message.append("usage: java -jar pdfbox-app-x.y.z.jar OverlayPDF <input.pdf> [OPTIONS] <output.pdf>\n");
+        message.append("  <input.pdf>                                        input file\n");
+        message.append("  <defaultOverlay.pdf>                               default overlay file\n");
+        message.append("  -odd <oddPageOverlay.pdf>                          overlay file used for odd pages\n");
+        message.append("  -even <evenPageOverlay.pdf>                        overlay file used for even pages\n");
+        message.append("  -first <firstPageOverlay.pdf>                      overlay file used for the first page\n");
+        message.append("  -last <lastPageOverlay.pdf>                        overlay file used for the last page\n");
+        message.append("  -page <pageNumber> <specificPageOverlay.pdf>       overlay file used for " +
+                "the given page number, may occur more than once\n");
+        message.append("  -position foreground|background                    where to put the overlay " +
+                "file: foreground or background\n");
+        message.append("  -nonSeq                                            enables the new non-sequential parser\n");
+        message.append("  <output.pdf>                                       output file\n");
+        System.err.println(message.toString());
+        System.exit( 1 );
+    }
+
+    /**
+     * This will add overlays to a documents.
+     *
+     * @param specificPageOverlayFile map of overlay files for specific pages
+     * @throws IOException exception
+     * @throws COSVisitorException exception
+     */
+    public void overlay(Map<Integer, String> specificPageOverlayFile) throws IOException, COSVisitorException 
+    {
+        PDDocument sourcePDFDocument = null;
+        PDDocument defaultOverlay = null;
+        PDDocument firstPageOverlay = null;
+        PDDocument lastPageOverlay = null;
+        PDDocument oddPageOverlay = null;
+        PDDocument evenPageOverlay = null;
+        try 
+        {
+            sourcePDFDocument = PDDocument.load(inputFile);
+            if (defaultOverlayFile != null) 
+            {
+                if (useNonSeqParser)
+                {
+                    defaultOverlay = PDDocument.loadNonSeq(new File(defaultOverlayFile), null);
+                }
+                else
+                {
+                    defaultOverlay = PDDocument.load(defaultOverlayFile);
+                }
+                defaultOverlayPage = getLayoutPage(defaultOverlay); 
+            }
+            if (firstPageOverlayFile != null) 
+            {
+                if (useNonSeqParser)
+                {
+                    firstPageOverlay = PDDocument.loadNonSeq(new File(firstPageOverlayFile), null);
+                }
+                else
+                {
+                    firstPageOverlay = PDDocument.load(firstPageOverlayFile);
+                }
+                firstPageOverlayPage = getLayoutPage(firstPageOverlay); 
+            }
+            if (lastPageOverlayFile != null) 
+            {
+                if (useNonSeqParser)
+                {
+                    lastPageOverlay = PDDocument.loadNonSeq(new File(lastPageOverlayFile), null);
+                }
+                else
+                {
+                    lastPageOverlay = PDDocument.load(lastPageOverlayFile);
+                }
+                lastPageOverlayPage = getLayoutPage(lastPageOverlay); 
+            }
+            if (oddPageOverlayFile != null) 
+            {
+                if (useNonSeqParser)
+                {
+                    oddPageOverlay = PDDocument.loadNonSeq(new File(oddPageOverlayFile), null);
+                }
+                else
+                {
+                    oddPageOverlay = PDDocument.load(oddPageOverlayFile);
+                }
+                oddPageOverlayPage = getLayoutPage(oddPageOverlay); 
+            }
+            if (evenPageOverlayFile != null) 
+            {
+                if (useNonSeqParser)
+                {
+                    evenPageOverlay = PDDocument.loadNonSeq(new File(evenPageOverlayFile), null);
+                }
+                else
+                {
+                    evenPageOverlay = PDDocument.load(evenPageOverlayFile);
+                }
+                evenPageOverlayPage = getLayoutPage(evenPageOverlay); 
+            }
+            for (Map.Entry<Integer, String> e : specificPageOverlayFile.entrySet()) 
+            {
+                PDDocument doc = null;
+                if (useNonSeqParser)
+                {
+                    doc = PDDocument.loadNonSeq(new File(e.getValue()), null);
+                }
+                else
+                {
+                    doc = PDDocument.load(e.getValue());
+                }
+                specificPageOverlay.put(e.getKey(), doc);
+                specificPageOverlayPage.put(e.getKey(), getLayoutPage(doc)); 
+            }
+            PDDocumentCatalog pdfCatalog = sourcePDFDocument.getDocumentCatalog();
+            processPages(pdfCatalog.getAllPages());
+
+            sourcePDFDocument.save(outputFile);
+        } 
+        finally 
+        {
+            if (sourcePDFDocument != null) 
+            {
+                sourcePDFDocument.close();
+            }
+            if (defaultOverlay != null) 
+            {
+                defaultOverlay.close();
+            }
+            if (firstPageOverlay != null) 
+            {
+                firstPageOverlay.close();
+            }
+            if (lastPageOverlay != null) 
+            {
+                lastPageOverlay.close();
+            }
+            if (oddPageOverlay != null) 
+            {
+                oddPageOverlay.close();
+            }
+            if (evenPageOverlay != null) 
+            {
+                evenPageOverlay.close();
+            }
+            for (Map.Entry<Integer, PDDocument> e : specificPageOverlay.entrySet()) 
+            {
+                e.getValue().close();
+            }
+            specificPageOverlay.clear();
+            specificPageOverlayPage.clear();
+        }
+    }
+
+    private static boolean isInteger(String str) 
+    {
+        try 
+        {
+            Integer.parseInt(str);
+        } 
+        catch (NumberFormatException nfe) 
+        {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Stores the overlay page information.
+     */
+    private static class LayoutPage 
+    {
+        private final PDRectangle overlayMediaBox;
+        private final COSStream overlayContentStream;
+        private final COSDictionary overlayResources;
+
+        private LayoutPage(PDRectangle mediaBox, COSStream contentStream, COSDictionary resources) 
+        {
+            overlayMediaBox = mediaBox;
+            overlayContentStream = contentStream;
+            overlayResources = resources;
+        }
+    }
+
+    private LayoutPage getLayoutPage(PDDocument doc) throws IOException 
+    {
+        PDDocumentCatalog catalog = doc.getDocumentCatalog();
+        PDPage page = (PDPage)catalog.getAllPages().get(0);
+        COSBase contents = page.getCOSDictionary().getDictionaryObject(COSName.CONTENTS);
+        PDResources resources = page.findResources();
+        if (resources == null) 
+        {
+            resources = new PDResources();
+        }
+        return new LayoutPage(page.getMediaBox(), createContentStream(contents), resources.getCOSDictionary());
+    }
+    
+    private COSStream createContentStream(COSBase contents) throws IOException 
+    {
+        List<COSStream> contentStreams = createContentStreamList(contents);
+        // concatenate streams
+        COSStream concatStream = new COSStream(new RandomAccessBuffer());
+        OutputStream out = concatStream.createUnfilteredStream();
+        for (COSStream contentStream : contentStreams) 
+        {
+            InputStream in = contentStream.getUnfilteredStream();
+            byte[] buf = new byte[2048];
+            int n;
+            while ((n = in.read(buf)) > 0) 
+            {
+                out.write(buf, 0, n);
+            }
+            out.flush();
+        }
+        out.close();
+        concatStream.setFilters(COSName.FLATE_DECODE);
+        return concatStream;
+    }
+    
+    private List<COSStream> createContentStreamList(COSBase contents) throws IOException 
+    {
+        List<COSStream> contentStreams = new ArrayList<COSStream>();
+        if (contents instanceof COSStream) 
+        {
+            contentStreams.add((COSStream) contents);
+        } 
+        else if (contents instanceof COSArray) 
+        {
+            for (COSBase item : (COSArray) contents) 
+            {
+                contentStreams.addAll(createContentStreamList(item));
+            }
+        } 
+        else if (contents instanceof COSObject) 
+        {
+            contentStreams.addAll(createContentStreamList(((COSObject) contents).getObject()));
+        } 
+        else 
+        {
+            throw new IOException("Contents are unknown type:" + contents.getClass().getName());
+        }
+        return contentStreams;
+    }
+    
+    private void processPages(List<?> pages) throws IOException 
+    {
+        int pageCount = 0;
+        for (Object pageObject : pages) 
+        {
+            PDPage page = (PDPage) pageObject;
+            COSDictionary pageDictionary = page.getCOSDictionary();
+            COSBase contents = pageDictionary.getDictionaryObject(COSName.CONTENTS);
+            COSArray contentArray = new COSArray();
+            switch (overlayPosition) 
+            {
+            case FOREGROUND:
+                // save state
+                contentArray.add(createStream("q\n"));
+                // original content
+                if (contents instanceof COSStream) 
+                {
+                    contentArray.add(contents);
+                } 
+                else if (contents instanceof COSArray) 
+                {
+                    contentArray.addAll((COSArray) contents);
+                } 
+                else 
+                {
+                    throw new IOException("Unknown content type:" + contents.getClass().getName());
+                }
+                // restore state
+                contentArray.add(createStream("Q\n"));
+                // overlay content
+                overlayPage(contentArray, page, pageCount + 1, pages.size());
+                break;
+            case BACKGROUND:
+                // overlay content
+                overlayPage(contentArray, page, pageCount + 1, pages.size());
+                // original content
+                if (contents instanceof COSStream) 
+                {
+                    contentArray.add(contents);
+                } 
+                else if (contents instanceof COSArray) 
+                {
+                    contentArray.addAll((COSArray) contents);
+                } 
+                else 
+                {
+                    throw new IOException("Unknown content type:" + contents.getClass().getName());
+                }
+                break;
+            default:
+                throw new IOException("Unknown type of position:" + overlayPosition);
+            }
+            pageDictionary.setItem(COSName.CONTENTS, contentArray);
+            pageCount++;
+        }
+    }
+
+    private void overlayPage(COSArray array, PDPage page, int pageNumber, int numberOfPages) throws IOException 
+    {
+        LayoutPage layoutPage = null;
+        if (specificPageOverlayPage.containsKey(pageNumber)) 
+        {
+            layoutPage = specificPageOverlayPage.get(pageNumber);
+        } 
+        else if ((pageNumber == 1) && (firstPageOverlayPage != null)) 
+        {
+            layoutPage = firstPageOverlayPage;
+        } 
+        else if ((pageNumber == numberOfPages) && (lastPageOverlayPage != null)) 
+        {
+            layoutPage = lastPageOverlayPage;
+        } 
+        else if ((pageNumber % 2 == 1) && (oddPageOverlayPage != null)) 
+        {
+            layoutPage = oddPageOverlayPage;
+        } 
+        else if ((pageNumber % 2 == 0) && (evenPageOverlayPage != null)) 
+        {
+            layoutPage = evenPageOverlayPage;
+        } 
+        else if (defaultOverlayPage != null) 
+        {
+            layoutPage = defaultOverlayPage;
+        }
+        if (layoutPage != null) 
+        {
+            PDResources resources = page.findResources();
+            if (resources == null) 
+            {
+                resources = new PDResources();
+                page.setResources(resources);
+            }
+            String xObjectId = createOverlayXObject(page, layoutPage, layoutPage.overlayContentStream);
+            array.add(createOverlayStream(page, layoutPage, xObjectId));
+        }
+    }
+    
+    private String createOverlayXObject(PDPage page, LayoutPage layoutPage, COSStream contentStream) 
+    {
+        PDResources resources = page.findResources();
+        // determine new ID
+        COSDictionary dict = (COSDictionary) resources.getCOSDictionary().getDictionaryObject(COSName.XOBJECT);
+        if (dict == null) 
+        {
+            dict = new COSDictionary();
+            resources.getCOSDictionary().setItem(COSName.XOBJECT, dict);
+        }
+        String xObjectId = MapUtil.getNextUniqueKey( resources.getXObjects(), XOBJECT_PREFIX );
+
+        // wrap the layout content in a BBox and add it to page
+        COSStream xobj = contentStream;
+        xobj.setItem(COSName.RESOURCES, layoutPage.overlayResources);
+        xobj.setItem(COSName.TYPE, COSName.XOBJECT);
+        xobj.setItem(COSName.SUBTYPE, COSName.FORM);
+        xobj.setInt(COSName.FORMTYPE, 1);
+        COSArray matrix = new COSArray();
+        matrix.add(COSInteger.get(1));
+        matrix.add(COSInteger.get(0));
+        matrix.add(COSInteger.get(0));
+        matrix.add(COSInteger.get(1));
+        matrix.add(COSInteger.get(0));
+        matrix.add(COSInteger.get(0));
+        xobj.setItem(COSName.MATRIX, matrix);
+        COSArray bbox = new COSArray();
+        bbox.add(COSInteger.get(0));
+        bbox.add(COSInteger.get(0));
+        bbox.add(COSInteger.get((int) layoutPage.overlayMediaBox.getWidth()));
+        bbox.add(COSInteger.get((int) layoutPage.overlayMediaBox.getHeight()));
+        xobj.setItem(COSName.BBOX, bbox);
+        dict.setItem(xObjectId, xobj);
+        
+        return xObjectId;
+    }
+
+    private COSStream createOverlayStream(PDPage page, LayoutPage layoutPage, String xObjectId) throws IOException 
+    {
+        // create a new content stream that executes the XObject content
+        PDRectangle pageMediaBox = page.getMediaBox();
+        float scale = 1;
+        float hShift = (pageMediaBox.getWidth() - layoutPage.overlayMediaBox.getWidth()) / 2.0f;
+        float vShift = (pageMediaBox.getHeight() - layoutPage.overlayMediaBox.getHeight()) / 2.0f;
+        return createStream("q\nq " + scale + " 0 0 " + scale + " " + hShift + " " + vShift + " cm /" 
+                + xObjectId + " Do Q\nQ\n");
+    }
+
+    private COSStream createStream(String content) throws IOException 
+    {
+        COSStream stream = new COSStream(new RandomAccessBuffer());
+        OutputStream out = stream.createUnfilteredStream();
+        out.write(content.getBytes("ISO-8859-1"));
+        out.close();
+        stream.setFilters(COSName.FLATE_DECODE);
+        return stream;
+    }
+    
+}

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/OverlayPDF.java
------------------------------------------------------------------------------
    svn:eol-style = native