Author: tilman Date: Mon Mar 23 15:49:34 2026 New Revision: 1932479 Log: PDFBOX-6179: add example for field removal + test
Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/interactive/form/FieldRemover.java (contents, props changed) pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/interactive/form/TestFieldRemover.java (contents, props changed) Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/interactive/form/FieldRemover.java ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/interactive/form/FieldRemover.java Mon Mar 23 15:49:34 2026 (r1932479) @@ -0,0 +1,160 @@ +/* + * Copyright 2026 The Apache Software Foundation. + * + * Licensed 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.examples.interactive.form; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.apache.pdfbox.pdmodel.interactive.form.PDNonTerminalField; + +/** + * Remove an AcroForm field. Use this example as a starting point if you are writing your own + * field editor. + * + * @author Tilman Hausherr + */ +public class FieldRemover +{ + public FieldRemover() + { + } + + public static void main(String[] args) throws IOException + { + if (args.length != 3) + { + usage(); + } + else + { + FieldRemover fieldRemover = new FieldRemover(); + fieldRemover.remove(args[0], args[1], args[2]); + } + } + + /** + * Remove the field by going through the field tree. + * + * @param fields list of child fields. + * @param field the specific field to be removed. + * @return + */ + private boolean removeRecursive(List<PDField> fields, PDField field) + { + // search the tree + for (PDField fieldItem : fields) + { + if (fieldItem instanceof PDNonTerminalField) + { + PDNonTerminalField ntField = (PDNonTerminalField) fieldItem; + List<PDField> children = ntField.getChildren(); + if (children.remove(field)) + { + ntField.setChildren(children); + return true; + } + if (removeRecursive(children, field)) + { + return true; + } + } + } + return false; + } + + /** + * Remove the first field with a matching fully qualified name from the acroform field tree and + * its widgets from the page annotations. + * + * @param src Source file name. + * @param dst Destination file name. + * @param fullyQualifiedFieldname fully qualified field name. + * @throws IOException + */ + public void remove(String src, String dst, String fullyQualifiedFieldname) throws IOException + { + try (PDDocument doc = Loader.loadPDF(new File(src))) + { + Set<PDAnnotationWidget> widgetSet = new HashSet<>(); + PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); + PDField field = acroForm.getField(fullyQualifiedFieldname); + if (field == null) + { + System.out.println("field '" + fullyQualifiedFieldname + "' not found"); + return; + } + List<PDField> fields = acroForm.getFields(); // returns only top level + boolean removed = fields.remove(field); + if (!removed) + { + removed = removeRecursive(fields, field); + } + if (removed) + { + // find page(s) + List<PDAnnotationWidget> widgets = field.getWidgets(); + for (PDAnnotationWidget widget : widgets) + { + PDPage page = widget.getPage(); // not always set - we may need to go through all pages + if (page != null) + { + List<PDAnnotation> annotations = page.getAnnotations(); + removed = annotations.remove(widget); + System.out.println("page widget removed? " + removed); + } + else + { + widgetSet.add(widget); + } + } + } + if (!widgetSet.isEmpty()) + { + for (PDPage page : doc.getPages()) + { + List<PDAnnotation> annotations = page.getAnnotations(); + widgetSet.forEach(widget -> annotations.remove(widget)); + } + } + System.out.println("field removed? " + removed); + if (removed) + { + doc.setAllSecurityToBeRemoved(true); // if encrypted + doc.getDocumentCatalog().getCOSObject().removeItem(COSName.PERMS); // UR3 perms + + doc.save(new File(dst)); + } + } + } + + /** + * This will print out a message telling how to use this example. + */ + private static void usage() + { + System.err.println("usage: org.apache.pdfbox.examples.interactive.form.RemoveField <pdf-file> <saved-pdf-file> <fully-qualified-field-name>"); + } +} Added: pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/interactive/form/TestFieldRemover.java ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ pdfbox/trunk/examples/src/test/java/org/apache/pdfbox/examples/interactive/form/TestFieldRemover.java Mon Mar 23 15:49:34 2026 (r1932479) @@ -0,0 +1,65 @@ +/* + * Copyright 2026 The Apache Software Foundation. + * + * Licensed 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.examples.interactive.form; + +import java.io.File; +import java.io.IOException; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.Test; + +/** + * + * @author Tilman Hausherr + */ +public class TestFieldRemover +{ + + @Test + void TestFieldRemoval() throws IOException + { + String inPath = "target/pdfs/PDFBOX-2469-1-AcroForm-AES128.pdf"; + String outPath = "target/test-output/PDFBOX-2469-1-AcroForm-saved.pdf"; + String fullyQualifiedFieldName = "form1[0].#subform[3].city[0]"; + + try (PDDocument doc = Loader.loadPDF(new File(inPath))) + { + PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); + PDField field = acroForm.getField("form1[0].#subform[3].city[0]"); + assertNotNull(field); + PDPage page4 = doc.getPage(3); + assertEquals(48, page4.getAnnotations().size()); + } + + FieldRemover fieldRemover = new FieldRemover(); + fieldRemover.remove(inPath, outPath, fullyQualifiedFieldName); + + try (PDDocument doc = Loader.loadPDF(new File(outPath))) + { + PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); + PDField field = acroForm.getField("form1[0].#subform[3].city[0]"); + assertNull(field); + PDPage page4 = doc.getPage(3); + assertEquals(47, page4.getAnnotations().size()); + } + } +}
