问题
I have a simple PDF with a button field that I would like to fill with an image.
The button field appears multiple times in the document. I would like to set the image once at the field level, so that the image appears for every occurrence of the field.
The PDF that I use for testing.
The code that is failing:
ByteArrayOutputStream outStr = new ByteArrayOutputStream();
PdfDocument pdfDocument = new PdfDocument(new PdfReader(pdfStr), new PdfWriter(outStr));
PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);
PdfButtonFormField button = (PdfButtonFormField) acroForm.getField("image");
button.setImage("src/test/resources/my_image.png");
acroForm.flattenFields();
pdfDocument.close();
The exception that is thrown:
com.example.documents.DocumentServiceException: Fail to render form
at com.example.ITEXTTests.test(ITEXTTests.java:100)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.IllegalArgumentException
at com.itextpdf.layout.element.Text.<init>(Text.java:69)
at com.itextpdf.layout.element.Paragraph.<init>(Paragraph.java:80)
at com.itextpdf.forms.fields.PdfFormField.drawButton(PdfFormField.java:3226)
at com.itextpdf.forms.fields.PdfFormField.drawPushButtonAppearance(PdfFormField.java:3199)
at com.itextpdf.forms.fields.PdfFormField.regenerateField(PdfFormField.java:2106)
at com.itextpdf.forms.PdfAcroForm.flattenFields(PdfAcroForm.java:661)
at com.example.ITEXTTests.test(ITEXTTests.java:98)
... 23 more
I'm using iText 7.1.4 (last 7.x release).
回答1:
The actual issue is not the flattening (during which the exception occurs in 7.1.4; this exception does not occur anymore in the current 7.1.5-SNAPSHOT), already the value setting fails (both in 7.1.4 and 7.1.5-SNAPSHOT as of now); you can verify this by executing your code without the acroForm.flattenFields() line and inspecting the output.
Actually the current implementation of PdfFormField.regenerateField() (which is called while setting the value of a field) does not appear to support fields with multiple widgets, in particular in the context of push button fields (but also for other field types) it assumes the abstract form field object and its widget to be merged (something that is only possible for fields with merely a single widget), both when retrieving information from it and when adding the generated appearance to it.
Thus, your use case
The button field appears multiple times in the document. I would like to set the image once at the field level, so that the image appears for every occurrence of the field.
is not yet supported by iText 7, at least not in the current 7.1.5-SNAPSHOT version.
A work-around would be to replace
button.setImage("src/test/resources/my_image.png");
by
ImageData img = ImageDataFactory.create("src/test/resources/my_image.png");
PdfImageXObject imgXObj = new PdfImageXObject(img);
List<PdfWidgetAnnotation> widgets = button.getWidgets();
for (PdfWidgetAnnotation widget : widgets) {
Rectangle rectangle = widget.getRectangle().toRectangle();
PdfFormXObject xObject = new PdfFormXObject(rectangle);
PdfCanvas canvas = new PdfCanvas(xObject, pdfDocument);
canvas.addXObject(imgXObj, rectangle.getWidth(), 0, 0, rectangle.getHeight(), rectangle.getLeft(), rectangle.getBottom());
widget.setNormalAppearance(xObject.getPdfObject());
}
(SetButtonImage test testSetImageToButtonWithManyVisualizationsWorkAround)
Using the current 7.1.5-SNAPSHOT underneath, this works including flattening.
Beware, this is not the perfect fix of the missing support for multi-widget push buttons, the code ignores quite some settings (e.g. the border of the button and the borderwidth around the image) and probably stretches the image in an unexpected way. Furthermore it is tested only against your example PDF. Thus, it really only can serve as a temporary work-around.
来源:https://stackoverflow.com/questions/54107835/itext-7-set-image-to-button-that-appears-multiple-times