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:34 UTC

svn commit: r1881663 - in /pdfbox/trunk: examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java

Author: tilman
Date: Sat Sep 12 12:31:33 2020
New Revision: 1881663

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

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

Modified: pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java?rev=1881663&r1=1881662&r2=1881663&view=diff
==============================================================================
--- pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java (original)
+++ pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/pdmodel/TestCreateSignature.java Sat Sep 12 12:31:33 2020
@@ -597,7 +597,7 @@ public class TestCreateSignature
     @Test
     public void testSaveIncrementalAfterSign() throws Exception
     {
-        BufferedImage oldImage, expectedImage1, actualImage1;
+        BufferedImage oldImage, expectedImage1, actualImage1, expectedImage2, actualImage2;
 
         CreateSimpleForm.main(new String[0]); // creates "target/SimpleForm.pdf"
 
@@ -607,6 +607,7 @@ public class TestCreateSignature
 
         final String fileNameSigned = getOutputFileName("SimpleForm_signed{0}.pdf");
         final String fileNameResaved1 = getOutputFileName("SimpleForm_signed{0}_incrementallyresaved1.pdf");
+        final String fileNameResaved2 = getOutputFileName("SimpleForm_signed{0}_incrementallyresaved2.pdf");
         signing.signDetached(new File("target/SimpleForm.pdf"), new File(OUT_DIR + fileNameSigned));
 
         checkSignature(new File("target/SimpleForm.pdf"), new File(OUT_DIR, fileNameSigned), false);
@@ -659,6 +660,44 @@ public class TestCreateSignature
             DataBufferInt actualData = (DataBufferInt) actualImage1.getRaster().getDataBuffer();
             Assert.assertArrayEquals(expectedData.getData(), actualData.getData());
         }
+
+        try (PDDocument doc = Loader.loadPDF(new File(OUT_DIR, fileNameSigned)))
+        {
+            FileOutputStream fileOutputStream = new FileOutputStream(new File(OUT_DIR, fileNameResaved2));
+            PDField 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());
+            DataBufferInt expectedData = (DataBufferInt) oldImage.getRaster().getDataBuffer();
+            DataBufferInt 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<>();
+            objectsToWrite.add(field.getCOSObject());
+            objectsToWrite.add(field.getWidgets().get(0).getAppearance().getCOSObject());
+            objectsToWrite.add(field.getWidgets().get(0).getAppearance().getNormalAppearance().getCOSObject());
+            doc.saveIncremental(fileOutputStream, objectsToWrite);
+        }
+        checkSignature(new File("target/SimpleForm.pdf"), new File(OUT_DIR, fileNameResaved2), false);
+        try (PDDocument doc = Loader.loadPDF(new File(OUT_DIR, fileNameResaved2)))
+        {
+            PDField 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());
+            DataBufferInt expectedData = (DataBufferInt) expectedImage2.getRaster().getDataBuffer();
+            DataBufferInt actualData = (DataBufferInt) actualImage2.getRaster().getDataBuffer();
+            Assert.assertArrayEquals(expectedData.getData(), actualData.getData());
+        }
     }
 
     @Test

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java?rev=1881663&r1=1881662&r2=1881663&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java Sat Sep 12 12:31:33 2020
@@ -254,6 +254,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)
     {
         if (doc != null)

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java?rev=1881663&r1=1881662&r2=1881663&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java Sat Sep 12 12:31:33 2020
@@ -955,6 +955,40 @@ 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");
+        }
+        
+        try (COSWriter writer = new COSWriter(output, pdfSource, objectsToWrite))
+        {
+            writer.write(this, signInterface);
+        }
+    }
+
+    /**
      * Save PDF incrementally without closing for external signature creation scenario. The general
      * sequence is:
      * <pre>