Author: tilman
Date: Mon Mar 23 15:49:30 2026
New Revision: 1932478
Log:
PDFBOX-6179: add example for field removal + test
Added:
pdfbox/branches/3.0/examples/src/main/java/org/apache/pdfbox/examples/interactive/form/FieldRemover.java
(contents, props changed)
pdfbox/branches/3.0/examples/src/test/java/org/apache/pdfbox/examples/interactive/form/TestFieldRemover.java
(contents, props changed)
Added:
pdfbox/branches/3.0/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/branches/3.0/examples/src/main/java/org/apache/pdfbox/examples/interactive/form/FieldRemover.java
Mon Mar 23 15:49:30 2026 (r1932478)
@@ -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/branches/3.0/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/branches/3.0/examples/src/test/java/org/apache/pdfbox/examples/interactive/form/TestFieldRemover.java
Mon Mar 23 15:49:30 2026 (r1932478)
@@ -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());
+ }
+ }
+}