PDFBox 是否允许从 AcroForm 中删除一个字段?
Does PDFBox allow to remove one field from AcroForm?
我正在使用 Apache PDFBox 2.0.8 并尝试删除一个字段。但是找不到方法,就像我可以用 iText 做的那样:PdfStamper.getAcroFields().removeField("signature3")
.
我要做什么。最初我有带有 3 个数字签名的模板 PDF。在某些情况下,我只需要 2 个签名,所以在这种情况下,我需要从模板中删除第三个签名。似乎我不能用 PDFBox 做到这一点,我发现的最接近的东西是展平这个领域,但问题是如果展平特定的 PDField(不是整个表格,而只是一个领域)——所有其他签名都失去了它们的功能,看起来就像他们也变得扁平一样。
这是执行此操作的代码:
PDDocument document = PDDocument.load(file);
PDDocumentCatalog documentCatalog = document.getDocumentCatalog();
PDAcroForm acroForm = documentCatalog.getAcroForm();
List<PDField> flattenList = new ArrayList<>();
for (PDField field : acroForm.getFieldTree()) {
if (field instanceof PDSignatureField && "signature3".equals(field.getFullyQualifiedName())) {
flattenList.add(field);
}
}
acroForm.flatten(flattenList, true);
document.save(dest);
document.close();
正如 Tilman 在评论中提到的,PDFBox 没有从字段树中删除字段的方法。尽管如此,它有操作底层 PDF 结构的方法,因此可以自己编写这样的方法,例如像这样:
PDField removeField(PDDocument document, String fullFieldName) throws IOException {
PDDocumentCatalog documentCatalog = document.getDocumentCatalog();
PDAcroForm acroForm = documentCatalog.getAcroForm();
if (acroForm == null) {
System.out.println("No form defined.");
return null;
}
PDField targetField = null;
for (PDField field : acroForm.getFieldTree()) {
if (fullFieldName.equals(field.getFullyQualifiedName())) {
targetField = field;
break;
}
}
if (targetField == null) {
System.out.println("Form does not contain field with given name.");
return null;
}
PDNonTerminalField parentField = targetField.getParent();
if (parentField != null) {
List<PDField> childFields = parentField.getChildren();
boolean removed = false;
for (PDField field : childFields)
{
if (field.getCOSObject().equals(targetField.getCOSObject())) {
removed = childFields.remove(field);
parentField.setChildren(childFields);
break;
}
}
if (!removed)
System.out.println("Inconsistent form definition: Parent field does not reference the target field.");
} else {
List<PDField> rootFields = acroForm.getFields();
boolean removed = false;
for (PDField field : rootFields)
{
if (field.getCOSObject().equals(targetField.getCOSObject())) {
removed = rootFields.remove(field);
break;
}
}
if (!removed)
System.out.println("Inconsistent form definition: Root fields do not include the target field.");
}
removeWidgets(targetField);
return targetField;
}
void removeWidgets(PDField targetField) throws IOException {
if (targetField instanceof PDTerminalField) {
List<PDAnnotationWidget> widgets = ((PDTerminalField)targetField).getWidgets();
for (PDAnnotationWidget widget : widgets) {
PDPage page = widget.getPage();
if (page != null) {
List<PDAnnotation> annotations = page.getAnnotations();
boolean removed = false;
for (PDAnnotation annotation : annotations) {
if (annotation.getCOSObject().equals(widget.getCOSObject()))
{
removed = annotations.remove(annotation);
break;
}
}
if (!removed)
System.out.println("Inconsistent annotation definition: Page annotations do not include the target widget.");
} else {
System.out.println("Widget annotation does not have an associated page; cannot remove widget.");
// TODO: In this case iterate all pages and try to find and remove widget in all of them
}
}
} else if (targetField instanceof PDNonTerminalField) {
List<PDField> childFields = ((PDNonTerminalField)targetField).getChildren();
for (PDField field : childFields)
removeWidgets(field);
} else {
System.out.println("Target field is neither terminal nor non-terminal; cannot remove widgets.");
}
}
(RemoveField 辅助方法 removeField
和 removeWidgets
)
可以像这样将其应用于文档和字段:
PDDocument document = PDDocument.load(SOURCE_PDF);
PDField field = removeField(document, "Signature1");
Assert.assertNotNull("Field not found", field);
document.save(TARGET_PDF);
document.close();
(RemoveField 测试 testRemoveInvisibleSignature
)
PS: 我不确定 PDFBox 实际上缓存了多少表单相关信息。因此,我建议不要在同一个文档操作会话中进一步操作表单信息,至少在没有测试的情况下是这样。
PPS: 您在 removeWidgets
辅助方法中找到了一个 TODO。如果该方法输出 "Widget annotation does not have an associated page; cannot remove widget",您必须添加缺少的代码。
感谢@mkl,我设法通过使用 pdfbox-3.0.0-RC1 版本的较短实现来做到这一点。在这种情况下,隐藏按钮(选中):
var check = (PDPushButton) pdAcroForm.getField(name);
List<PDField> fields = pdAcroForm.getFields();
fields.removeIf(x -> x.getCOSObject().equals(check.getCOSObject()));
pdAcroForm.setFields(fields);
check.getWidgets().forEach(widget -> widget.setNoView(true));
我正在使用 Apache PDFBox 2.0.8 并尝试删除一个字段。但是找不到方法,就像我可以用 iText 做的那样:PdfStamper.getAcroFields().removeField("signature3")
.
我要做什么。最初我有带有 3 个数字签名的模板 PDF。在某些情况下,我只需要 2 个签名,所以在这种情况下,我需要从模板中删除第三个签名。似乎我不能用 PDFBox 做到这一点,我发现的最接近的东西是展平这个领域,但问题是如果展平特定的 PDField(不是整个表格,而只是一个领域)——所有其他签名都失去了它们的功能,看起来就像他们也变得扁平一样。 这是执行此操作的代码:
PDDocument document = PDDocument.load(file);
PDDocumentCatalog documentCatalog = document.getDocumentCatalog();
PDAcroForm acroForm = documentCatalog.getAcroForm();
List<PDField> flattenList = new ArrayList<>();
for (PDField field : acroForm.getFieldTree()) {
if (field instanceof PDSignatureField && "signature3".equals(field.getFullyQualifiedName())) {
flattenList.add(field);
}
}
acroForm.flatten(flattenList, true);
document.save(dest);
document.close();
正如 Tilman 在评论中提到的,PDFBox 没有从字段树中删除字段的方法。尽管如此,它有操作底层 PDF 结构的方法,因此可以自己编写这样的方法,例如像这样:
PDField removeField(PDDocument document, String fullFieldName) throws IOException {
PDDocumentCatalog documentCatalog = document.getDocumentCatalog();
PDAcroForm acroForm = documentCatalog.getAcroForm();
if (acroForm == null) {
System.out.println("No form defined.");
return null;
}
PDField targetField = null;
for (PDField field : acroForm.getFieldTree()) {
if (fullFieldName.equals(field.getFullyQualifiedName())) {
targetField = field;
break;
}
}
if (targetField == null) {
System.out.println("Form does not contain field with given name.");
return null;
}
PDNonTerminalField parentField = targetField.getParent();
if (parentField != null) {
List<PDField> childFields = parentField.getChildren();
boolean removed = false;
for (PDField field : childFields)
{
if (field.getCOSObject().equals(targetField.getCOSObject())) {
removed = childFields.remove(field);
parentField.setChildren(childFields);
break;
}
}
if (!removed)
System.out.println("Inconsistent form definition: Parent field does not reference the target field.");
} else {
List<PDField> rootFields = acroForm.getFields();
boolean removed = false;
for (PDField field : rootFields)
{
if (field.getCOSObject().equals(targetField.getCOSObject())) {
removed = rootFields.remove(field);
break;
}
}
if (!removed)
System.out.println("Inconsistent form definition: Root fields do not include the target field.");
}
removeWidgets(targetField);
return targetField;
}
void removeWidgets(PDField targetField) throws IOException {
if (targetField instanceof PDTerminalField) {
List<PDAnnotationWidget> widgets = ((PDTerminalField)targetField).getWidgets();
for (PDAnnotationWidget widget : widgets) {
PDPage page = widget.getPage();
if (page != null) {
List<PDAnnotation> annotations = page.getAnnotations();
boolean removed = false;
for (PDAnnotation annotation : annotations) {
if (annotation.getCOSObject().equals(widget.getCOSObject()))
{
removed = annotations.remove(annotation);
break;
}
}
if (!removed)
System.out.println("Inconsistent annotation definition: Page annotations do not include the target widget.");
} else {
System.out.println("Widget annotation does not have an associated page; cannot remove widget.");
// TODO: In this case iterate all pages and try to find and remove widget in all of them
}
}
} else if (targetField instanceof PDNonTerminalField) {
List<PDField> childFields = ((PDNonTerminalField)targetField).getChildren();
for (PDField field : childFields)
removeWidgets(field);
} else {
System.out.println("Target field is neither terminal nor non-terminal; cannot remove widgets.");
}
}
(RemoveField 辅助方法 removeField
和 removeWidgets
)
可以像这样将其应用于文档和字段:
PDDocument document = PDDocument.load(SOURCE_PDF);
PDField field = removeField(document, "Signature1");
Assert.assertNotNull("Field not found", field);
document.save(TARGET_PDF);
document.close();
(RemoveField 测试 testRemoveInvisibleSignature
)
PS: 我不确定 PDFBox 实际上缓存了多少表单相关信息。因此,我建议不要在同一个文档操作会话中进一步操作表单信息,至少在没有测试的情况下是这样。
PPS: 您在 removeWidgets
辅助方法中找到了一个 TODO。如果该方法输出 "Widget annotation does not have an associated page; cannot remove widget",您必须添加缺少的代码。
感谢@mkl,我设法通过使用 pdfbox-3.0.0-RC1 版本的较短实现来做到这一点。在这种情况下,隐藏按钮(选中):
var check = (PDPushButton) pdAcroForm.getField(name);
List<PDField> fields = pdAcroForm.getFields();
fields.removeIf(x -> x.getCOSObject().equals(check.getCOSObject()));
pdAcroForm.setFields(fields);
check.getWidgets().forEach(widget -> widget.setNoView(true));