Author: msahyoun Date: Sat Apr 14 16:54:48 2018 New Revision: 1829151 URL: http://svn.apache.org/viewvc?rev=1829151&view=rev Log: PDFBOX-3809: flatten only specified fields
Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java?rev=1829151&r1=1829150&r2=1829151&view=diff ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java (original) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java Sat Apr 14 16:54:48 2018 @@ -251,6 +251,11 @@ public final class PDAcroForm implements */ public void flatten(List<PDField> fields, boolean refreshAppearances) throws IOException { + // Nothing to flatten if there are no fields provided + if (fields.isEmpty()) { + return; + } + // for dynamic XFA forms there is no flatten as this would mean to do a rendering // from the XFA content into a static PDF. if (xfaIsDynamic()) @@ -271,17 +276,21 @@ public final class PDAcroForm implements // the content stream to write to PDPageContentStream contentStream; + + // get the widgets per page + Map<COSDictionary,Map<COSDictionary,PDAnnotationWidget>> pagesWidgetsMap = buildPagesWidgetsMap(fields); // preserve all non widget annotations for (PDPage page : document.getPages()) { + Map<COSDictionary,PDAnnotationWidget> widgetsForPageMap = pagesWidgetsMap.get(page.getCOSObject()); isContentStreamWrapped = false; List<PDAnnotation> annotations = new ArrayList<PDAnnotation>(); for (PDAnnotation annotation: page.getAnnotations()) { - if (!(annotation instanceof PDAnnotationWidget)) + if (widgetsForPageMap != null && widgetsForPageMap.get(annotation.getCOSObject()) == null) { annotations.add(annotation); } @@ -350,7 +359,7 @@ public final class PDAcroForm implements } // remove the fields - setFields(Collections.<PDField>emptyList()); + removeFields(fields); // remove XFA for hybrid forms dictionary.removeItem(COSName.XFA); @@ -703,25 +712,6 @@ public final class PDAcroForm implements dictionary.setFlag(COSName.SIG_FLAGS, FLAG_APPEND_ONLY, appendOnly); } - private Map<COSDictionary, Integer> buildAnnotationToPageRef() { - Map<COSDictionary, Integer> annotationToPageRef = new HashMap<COSDictionary, Integer>(); - - int idx = 0; - for (PDPage page : document.getPages()) { - try { - for (PDAnnotation annotation : page.getAnnotations()) { - if (annotation instanceof PDAnnotationWidget) { - annotationToPageRef.put(annotation.getCOSObject(), idx); - } - } - } catch (IOException e) { - LOG.warn("Can't retriev annotations for page " + idx); - } - idx++; - } - return annotationToPageRef; - } - /** * Check if there is a translation needed to place the annotations content. * @@ -780,4 +770,75 @@ public final class PDAcroForm implements PDResources resources = appearanceStream.getResources(); return resources != null && resources.getXObjectNames().iterator().hasNext(); } + + private Map<COSDictionary,Map<COSDictionary,PDAnnotationWidget>> buildPagesWidgetsMap(List<PDField> fields) + { + Map<COSDictionary,Map<COSDictionary,PDAnnotationWidget>> pagesAnnotationsMap = new HashMap<COSDictionary,Map<COSDictionary,PDAnnotationWidget>>(); + boolean hasMissingPageRef = false; + + for (PDField field : fields) + { + List<PDAnnotationWidget> widgets = field.getWidgets(); + for (PDAnnotationWidget widget : widgets) + { + PDPage pageForWidget = widget.getPage(); + if (pageForWidget != null) + { + if (pagesAnnotationsMap.get(pageForWidget.getCOSObject()) == null) + { + Map<COSDictionary,PDAnnotationWidget> widgetsForPage = new HashMap<COSDictionary,PDAnnotationWidget>(); + widgetsForPage.put(widget.getCOSObject(), widget); + pagesAnnotationsMap.put(pageForWidget.getCOSObject(), widgetsForPage); + } + else + { + Map<COSDictionary,PDAnnotationWidget> widgetsForPage = pagesAnnotationsMap.get(pageForWidget.getCOSObject()); + widgetsForPage.put(widget.getCOSObject(), widget); + } + } + else + { + hasMissingPageRef = true; + } + } + } + + // TODO: if there is a widget with a missing page reference + // we'd need to build the map reverse i.e. form the annotations to the + // widget. But this will be much slower so will be omitted for now. + if (hasMissingPageRef) + { + LOG.warn("There has been a widget with a missing page reference. Please report to the PDFBox project"); + } + + return pagesAnnotationsMap; + } + + private void removeFields(List<PDField> fields) + { + for (PDField field : fields) { + if (field.getParent() == null) + { + COSArray cosFields = (COSArray) dictionary.getDictionaryObject(COSName.FIELDS); + for (int i=0; i<cosFields.size(); i++) + { + COSDictionary element = (COSDictionary) cosFields.getObject(i); + if (field.getCOSObject().equals(element)) { + cosFields.remove(i); + } + } + } + else + { + COSArray kids = (COSArray) field.getParent().getCOSObject().getDictionaryObject(COSName.KIDS); + for (int i=0; i<kids.size(); i++) + { + COSDictionary element = (COSDictionary) kids.getObject(i); + if (field.getCOSObject().equals(element)) { + kids.remove(i); + } + } + } + } + } } Modified: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java?rev=1829151&r1=1829150&r2=1829151&view=diff ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java (original) +++ pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java Sat Apr 14 16:54:48 2018 @@ -24,14 +24,18 @@ import static org.junit.Assert.assertTru import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.io.IOUtils; 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.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.rendering.TestPDFToImage; import org.junit.After; @@ -135,6 +139,41 @@ public class PDAcroFormTest System.out.println("Rendering of " + file + " failed or is not identical to expected rendering in " + IN_DIR + " directory"); } } + + @Test + public void testFlattenSpecificFieldsOnly() throws IOException + { + File file = new File(OUT_DIR, "AlignmentTests-flattened-specificFields.pdf"); + + List<PDField> fieldsToFlatten = new ArrayList<PDField>(); + + PDDocument testPdf = null; + try + { + testPdf = PDDocument.load(new File(IN_DIR, "AlignmentTests.pdf")); + PDAcroForm acroFormToFlatten = testPdf.getDocumentCatalog().getAcroForm(); + int numFieldsBeforeFlatten = acroFormToFlatten.getFields().size(); + int numWidgetsBeforeFlatten = countWidgets(testPdf); + + fieldsToFlatten.add(acroFormToFlatten.getField("AlignLeft-Border_Small-Filled")); + fieldsToFlatten.add(acroFormToFlatten.getField("AlignLeft-Border_Medium-Filled")); + fieldsToFlatten.add(acroFormToFlatten.getField("AlignLeft-Border_Wide-Filled")); + fieldsToFlatten.add(acroFormToFlatten.getField("AlignLeft-Border_Wide_Clipped-Filled")); + + acroFormToFlatten.flatten(fieldsToFlatten, true); + int numFieldsAfterFlatten = acroFormToFlatten.getFields().size(); + int numWidgetsAfterFlatten = countWidgets(testPdf); + + assertEquals(numFieldsBeforeFlatten, numFieldsAfterFlatten + fieldsToFlatten.size()); + assertEquals(numWidgetsBeforeFlatten, numWidgetsAfterFlatten + fieldsToFlatten.size()); + + testPdf.save(file); + } + finally + { + IOUtils.closeQuietly(testPdf); + } + } /* * Test that we do not modify an AcroForm with missing resource information @@ -239,6 +278,28 @@ public class PDAcroFormTest document.close(); return baos.toByteArray(); } - + + private int countWidgets(PDDocument documentToTest) + { + int count = 0; + for (PDPage page : documentToTest.getPages()) + { + try + { + for (PDAnnotation annotation : page.getAnnotations()) + { + if (annotation instanceof PDAnnotationWidget) + { + count ++; + } + } + } + catch (IOException e) + { + // ignoring + } + } + return count; + } }