You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2020/09/12 12:31:39 UTC

svn commit: r1881664 - in /pdfbox/branches/2.0: examples/src/test/java/org/apache/pdfbox/examples/pdmodel/ pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/ pdfbox/src/main/java/org/apache/pdfbox/pdmodel/

Author: tilman
Date: Sat Sep 12 12:31:39 2020
New Revision: 1881664

URL: http://svn.apache.org/viewvc?rev=1881664&view=rev
Log:
PDFBOX-45: allow incremental save for specific dictionary objects

Modified:
    pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java

Modified: pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java?rev=1881664&r1=1881663&r2=1881664&view=diff
==============================================================================
--- pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java (original)
+++ pdfbox/branches/2.0/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java Sat Sep 12 12:31:39 2020
@@ -659,6 +659,45 @@ public class TestCreateSignature
         actualData = (DataBufferInt) actualImage1.getRaster().getDataBuffer();
         Assert.assertArrayEquals(expectedData.getData(), actualData.getData());
         doc.close();
+
+        doc = PDDocument.load(new File(outDir, fileNameSigned));
+
+        fileOutputStream = new FileOutputStream(new File(outDir, fileNameResaved2));
+        field = doc.getDocumentCatalog().getAcroForm().getField("SampleField");
+        field.setValue("New Value 2");
+        expectedImage2 = new PDFRenderer(doc).renderImage(0);
+
+        // compare images, image must has changed
+        Assert.assertEquals(oldImage.getWidth(), expectedImage2.getWidth());
+        Assert.assertEquals(oldImage.getHeight(), expectedImage2.getHeight());
+        Assert.assertEquals(oldImage.getType(), expectedImage2.getType());
+        expectedData = (DataBufferInt) oldImage.getRaster().getDataBuffer();
+        actualData = (DataBufferInt) expectedImage2.getRaster().getDataBuffer();
+        Assert.assertEquals(expectedData.getData().length, actualData.getData().length);
+        Assert.assertFalse(Arrays.equals(expectedData.getData(), actualData.getData()));
+
+        // new style incremental save: add only the objects that have changed
+        Set<COSDictionary> objectsToWrite = new HashSet<COSDictionary>();
+        objectsToWrite.add(field.getCOSObject());
+        objectsToWrite.add(field.getWidgets().get(0).getAppearance().getCOSObject());
+        objectsToWrite.add((COSDictionary) field.getWidgets().get(0).getAppearance().getNormalAppearance().getCOSObject());
+        doc.saveIncremental(fileOutputStream, objectsToWrite);
+        doc.close();
+
+        checkSignature(new File("target/SimpleForm.pdf"), new File(outDir, fileNameResaved2), false);
+        doc = PDDocument.load(new File(outDir, fileNameResaved2));
+
+        field = doc.getDocumentCatalog().getAcroForm().getField("SampleField");
+        Assert.assertEquals("New Value 2", field.getValueAsString());
+        actualImage2 = new PDFRenderer(doc).renderImage(0);
+        // compare images, equality proves that the appearance has been updated too
+        Assert.assertEquals(expectedImage2.getWidth(), actualImage2.getWidth());
+        Assert.assertEquals(expectedImage2.getHeight(), actualImage2.getHeight());
+        Assert.assertEquals(expectedImage2.getType(), actualImage2.getType());
+        expectedData = (DataBufferInt) expectedImage2.getRaster().getDataBuffer();
+        actualData = (DataBufferInt) actualImage2.getRaster().getDataBuffer();
+        Assert.assertArrayEquals(expectedData.getData(), actualData.getData());
+        doc.close();
     }
 
     @Test

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java?rev=1881664&r1=1881663&r2=1881664&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java Sat Sep 12 12:31:39 2020
@@ -258,6 +258,39 @@ public class COSWriter implements ICOSVi
         incrementalUpdate = true;
     }
 
+    /**
+     * Constructor for incremental updates with a list of objects to write. This allows to
+     * include objects even if there is no path of objects that have
+     * {@link COSUpdateInfo#isNeedToBeUpdated()} set so the incremental update gets smaller. Only
+     * dictionaries are supported; if you need to update other objects classes, then add their
+     * parent dictionary.
+     *
+     * @param outputStream output stream where the new PDF data will be written. It will be closed
+     * when this object is closed.
+     * @param inputData random access read containing source PDF data.
+     * @param objectsToWrite objects that <b>must</b> be part of the incremental saving.
+     * @throws IOException if something went wrong
+     */
+    public COSWriter(OutputStream outputStream, RandomAccessRead inputData,
+            Set<COSDictionary> objectsToWrite) throws IOException
+    {
+        // Implementation notes / summary of April 2019 comments in PDFBOX-45:
+        // we allow only COSDictionary in objectsToWrite because other types, 
+        // especially COSArray, are written directly. If we'd allow them with the current
+        // COSWriter implementation, they would be written twice,
+        // once directly and once indirectly as orphan.
+        // One could improve visitFromArray and visitFromDictionary (see commit 1856891)
+        // to handle arrays like dictionaries so that arrays are written indirectly,
+        // but this produces very inefficient files.
+        // If there is every a real need to update arrays, then a future implementation could
+        // recommit change 1856891 (also needs to move the byteRange position detection code)
+        // and also set isDirect in arrays to true by default, to avoid inefficient files.
+        // COSArray.setDirect(true) is called at some places in the current implementation for
+        // documentational purposes only.
+        this(outputStream, inputData);
+        this.objectsToWrite.addAll(objectsToWrite);
+    }
+
     private void prepareIncrement(PDDocument doc)
     {
       try

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java?rev=1881664&r1=1881663&r2=1881664&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java Sat Sep 12 12:31:39 2020
@@ -1401,6 +1401,48 @@ public class PDDocument implements Close
     }
 
     /**
+     * Save the PDF as an incremental update. This is only possible if the PDF was loaded from a
+     * file or a stream, not if the document was created in PDFBox itself. This allows to include
+     * objects even if there is no path of objects that have
+     * {@link COSUpdateInfo#isNeedToBeUpdated()} set so the incremental update gets smaller. Only
+     * dictionaries are supported; if you need to update other objects classes, then add their
+     * parent dictionary.
+     * <p>
+     * This method is for experienced users only. You will usually never need it. It is useful only
+     * if you are required to keep the current revision and append the changes. A typical use case
+     * is changing a signed file without invalidating the signature. To know which objects are
+     * getting changed, you need to have some understanding of the PDF specification, and look at
+     * the saved file with an editor to verify that you are updating the correct objects. You should
+     * also inspect the page and document structures of the file with PDFDebugger.
+     *
+     * @param output stream to write to. It will be closed when done. It
+     * <i><b>must never</b></i> point to the source file or that one will be harmed!
+     * @param objectsToWrite objects that <b>must</b> be part of the incremental saving.
+     * @throws IOException if the output could not be written
+     * @throws IllegalStateException if the document was not loaded from a file or a stream.
+     */
+    public void saveIncremental(OutputStream output, Set<COSDictionary> objectsToWrite) throws IOException
+    {
+        if (pdfSource == null)
+        {
+            throw new IllegalStateException("document was not loaded from a file or a stream");
+        }
+        COSWriter writer = null;
+        try
+        {
+            writer = new COSWriter(output, pdfSource, objectsToWrite);
+            writer.write(this, signInterface);
+        }
+        finally
+        {
+            if (writer != null)
+            {
+                writer.close();
+            }
+        }
+    }
+
+    /**
      * <p>
      * <b>(This is a new feature for 2.0.3. The API for external signing might change based on feedback after release!)</b>
      * <p>