diff --git a/openhtmltopdf-examples/src/main/resources/testcases/form-controls.html b/openhtmltopdf-examples/src/main/resources/testcases/form-controls.html index 6035daaf5..2e6bb3bf0 100644 --- a/openhtmltopdf-examples/src/main/resources/testcases/form-controls.html +++ b/openhtmltopdf-examples/src/main/resources/testcases/form-controls.html @@ -4,6 +4,7 @@ input { color: orange; font-family: monospace; } textarea { color: red; font-family: monospace; } button { color: blue; font-family: monospace; border-radius: 8px; background-color: yellow; } +input[name="signature"] { color: green; } @@ -20,6 +21,7 @@ +
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/form-signature-field.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/form-signature-field.pdf new file mode 100644 index 000000000..eadc3a117 Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/form-signature-field.pdf differ diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/form-signature-field.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/form-signature-field.html new file mode 100644 index 000000000..9139f63bb --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/form-signature-field.html @@ -0,0 +1,41 @@ + + + + + + +
+
+
+ Required signature please: + +
+
+ + +
Another one
+
+ +
+
+ + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 6fa141a88..f0f84b7f4 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -1492,6 +1492,11 @@ public void testIssue792TargetCounterStyle() throws IOException { assertTrue(vt.runTest("issue-792-target-counter-style")); } + @Test + public void testSignatureField() throws IOException { + assertTrue(vt.runTest("form-signature-field")); + } + // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java index 61f144704..d598e2672 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java @@ -293,8 +293,8 @@ public void paintBackground(RenderingContext c, Box box) { } } - private void processControls() { - _formState.processControls(_sharedContext, _writer, _root); + private void processControls(RenderingContext renderingContext) { + _formState.processControls(_sharedContext, _writer, _root, renderingContext); } /** @@ -898,7 +898,7 @@ public void finish(RenderingContext c, Box root) { _bmManager.writeOutline(c, root); // Also need access to the structure tree. - processControls(); + processControls(c); _linkManager.processLinks(_pdfUa); if (_pdfUa != null) { diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxForm.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxForm.java index 5f359fb3f..f9813a16c 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxForm.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxForm.java @@ -41,6 +41,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDNonTerminalField; import org.apache.pdfbox.pdmodel.interactive.form.PDPushButton; import org.apache.pdfbox.pdmodel.interactive.form.PDRadioButton; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.apache.pdfbox.pdmodel.interactive.form.PDTextField; import org.w3c.dom.Element; @@ -61,13 +62,13 @@ public class PdfBoxForm { // The global(per document) form state container private final PdfBoxPerDocumentFormState docFormsStateContainer; - + // The output device private final PdfBoxOutputDevice od; - + // The form element itself. private final Element element; - + // All controls, with a font name if needed. private final List controls = new ArrayList<>(); @@ -76,7 +77,7 @@ public class PdfBoxForm { // We've got to find all the radio button controls that belong to a group (common name). private final Map> radioGroups = new LinkedHashMap<>(); - + // Contains a tree of fields in the form: // person // person.details @@ -84,7 +85,7 @@ public class PdfBoxForm { // person.details.phone // etc. private final Map allFieldMap = new HashMap<>(); - + // A link in the tree of fields. We have this so that each field can look up // its parent field. private static class Field { @@ -95,14 +96,14 @@ private static class Field { // set its kids appropriately. private boolean isTerminal; } - + public static class Control { public final Box box; private final PDPage page; private final AffineTransform transform; private final RenderingContext c; private final float pageHeight; - + public Control(Box box, PDPage page, AffineTransform transform, RenderingContext c, float pageHeight) { this.box = box; this.page = page; @@ -111,23 +112,23 @@ public Control(Box box, PDPage page, AffineTransform transform, RenderingContext this.pageHeight = pageHeight; } } - + private static class ControlFontPair { private final String fontName; private final Control control; - + private ControlFontPair(Control control, String fontName) { this.control = control; this.fontName = fontName; } } - + private PdfBoxForm(Element element, PdfBoxPerDocumentFormState forms, PdfBoxOutputDevice od) { this.element = element; this.od = od; this.docFormsStateContainer = forms; } - + public static PdfBoxForm createForm(Element e, PdfBoxPerDocumentFormState forms, PdfBoxOutputDevice od) { return new PdfBoxForm(e, forms, od); } @@ -135,8 +136,8 @@ public static PdfBoxForm createForm(Element e, PdfBoxPerDocumentFormState forms, public void addControl(Control ctrl, String fontName) { controls.add(new ControlFontPair(ctrl, fontName)); } - - /** + + /** * This method will create a tree of names, both non-terminal * and terminal. */ @@ -145,9 +146,9 @@ private void processControlNames() { if (!control.control.box.getElement().hasAttribute("name")) { continue; } - + String name = control.control.box.getElement().getAttribute("name"); - + if (!name.contains(".")) { // It's a root field! Field f = new Field(); @@ -157,12 +158,12 @@ private void processControlNames() { allFieldMap.put(name, f); } else { String[] partials = name.split(Pattern.quote(".")); - + for (int i = 1; i <= partials.length; i++) { // Given a field name such as person.details.name // we check that 'person' is created first, then 'person.details' and // finally 'person.details.name'. - + String[] parent = new String[i]; System.arraycopy(partials, 0, parent, 0, i); String parentQualifiedName = ArrayUtil.join(parent, "."); @@ -171,7 +172,7 @@ private void processControlNames() { if (f == null) { Field fCreated = new Field(); fCreated.qualifiedName = parentQualifiedName; - fCreated.partialName = parent[i - 1]; + fCreated.partialName = parent[i - 1]; fCreated.isTerminal = (i == partials.length); allFieldMap.put(parentQualifiedName, fCreated); } @@ -179,7 +180,7 @@ private void processControlNames() { } } } - + /** * This method will create the non terminal fields. * It is called recursively to create all non-terminal field descendants. @@ -199,9 +200,9 @@ private void createNonTerminalFields(Field f, PDAcroForm form) { createNonTerminalFields(f2, form); } } - - f.field.getCOSObject().setItem(COSName.KIDS, kids); + + f.field.getCOSObject().setItem(COSName.KIDS, kids); } } @@ -217,7 +218,7 @@ private void createNonTerminalFields(PDAcroForm form) { f.field = nonTerminal; } } - + for (Field f : allFieldMap.values()) { if (!f.qualifiedName.contains(".")) { createNonTerminalFields(f, form); @@ -225,22 +226,22 @@ private void createNonTerminalFields(PDAcroForm form) { } } } - + /** * Get a PDF graphics operator for a specific color. */ private static String getColorOperator(FSColor color) { String colorOperator = ""; - + if (color instanceof FSRGBColor) { FSRGBColor rgb = (FSRGBColor) color; float r = (float) rgb.getRed() / 255; float g = (float) rgb.getGreen() / 255; float b = (float) rgb.getBlue() / 255; - + colorOperator = - String.format(Locale.US, "%.4f", r) + ' ' + - String.format(Locale.US, "%.4f", g) + ' ' + + String.format(Locale.US, "%.4f", r) + ' ' + + String.format(Locale.US, "%.4f", g) + ' ' + String.format(Locale.US, "%.4f", b) + ' ' + "rg"; } else if (color instanceof FSCMYKColor) { @@ -249,22 +250,22 @@ private static String getColorOperator(FSColor color) { float m = cmyk.getMagenta(); float y = cmyk.getYellow(); float k = cmyk.getBlack(); - - colorOperator = + + colorOperator = String.format(Locale.US, "%.4f", c) + ' ' + String.format(Locale.US, "%.4f", m) + ' ' + String.format(Locale.US, "%.4f", y) + ' ' + String.format(Locale.US, "%.4f", k) + ' ' + "k"; } - + return colorOperator; } - + private String getTextareaText(Element e) { return DOMUtil.getText(e); } - + private String populateOptions(Element e, List labels, List values, List selectedIndices) { List opts = DOMUtil.getChildren(e, "option"); if (opts == null) { @@ -273,141 +274,147 @@ private String populateOptions(Element e, List labels, List valu } String selected = ""; int i = 0; - + for (Element opt : opts) { String label = DOMUtil.getText(opt); labels.add(label); - + if (opt.hasAttribute("value")) { values.add(opt.getAttribute("value")); } else { values.add(label); } - + if (selected.isEmpty()) { selected = label; } - + if (opt.hasAttribute("selected")) { selected = label; } - + if (opt.hasAttribute("selected") && selectedIndices != null) { selectedIndices.add(i); } i++; } - + return selected; } - + private void processMultiSelectControl(ControlFontPair pair, Control ctrl, PDAcroForm acro, int i, Box root) throws IOException { PDListBox field = new PDListBox(acro); setPartialNameToField(ctrl, field); field.setMultiSelect(true); - + List labels = new ArrayList<>(); List values = new ArrayList<>(); List selected = new ArrayList<>(); populateOptions(ctrl.box.getElement(), labels, values, selected); - + field.setOptions(values, labels); field.setSelectedOptionsIndex(selected); - + FSColor color = ctrl.box.getStyle().getColor(); String colorOperator = getColorOperator(color); - + String fontInstruction = "/" + pair.fontName + " 0 Tf"; field.setDefaultAppearance(fontInstruction + ' ' + colorOperator); - + if (ctrl.box.getElement().hasAttribute("required")) { field.setRequired(true); } - + if (ctrl.box.getElement().hasAttribute("readonly")) { field.setReadOnly(true); } - + if (ctrl.box.getElement().hasAttribute("title")) { field.setAlternateFieldName(ctrl.box.getElement().getAttribute("title")); } - + PDAnnotationWidget widget = field.getWidgets().get(0); - Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, root, od); - PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), (float) rect2D.getWidth(), (float) rect2D.getHeight()); + Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, + root, od); + PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), + (float) rect2D.getWidth(), (float) rect2D.getHeight()); widget.setRectangle(rect); widget.setPage(ctrl.page); widget.setPrinted(true); - + ctrl.page.getAnnotations().add(widget); } - + /** * Processes select controls and the custom openhtmltopdf-combo control. */ - private void processSelectControl(ControlFontPair pair, Control ctrl, PDAcroForm acro, int i, Box root) throws IOException { + private void processSelectControl(ControlFontPair pair, Control ctrl, PDAcroForm acro, int i, Box root) + throws IOException { PDComboBox field = new PDComboBox(acro); setPartialNameToField(ctrl, field); - + List labels = new ArrayList<>(); List values = new ArrayList<>(); String selectedLabel = populateOptions(ctrl.box.getElement(), labels, values, null); - + field.setOptions(values, labels); field.setValue(selectedLabel); field.setDefaultValue(selectedLabel); - + FSColor color = ctrl.box.getStyle().getColor(); String colorOperator = getColorOperator(color); - + String fontInstruction = "/" + pair.fontName + " 0 Tf"; field.setDefaultAppearance(fontInstruction + ' ' + colorOperator); - + if (ctrl.box.getElement().hasAttribute("required")) { field.setRequired(true); } - + if (ctrl.box.getElement().hasAttribute("readonly")) { field.setReadOnly(true); } - + if (ctrl.box.getElement().hasAttribute("title")) { field.setAlternateFieldName(ctrl.box.getElement().getAttribute("title")); } - + if (ctrl.box.getElement().getNodeName().equals("openhtmltopdf-combo")) { field.setEdit(true); field.setCombo(true); } - + PDAnnotationWidget widget = field.getWidgets().get(0); - Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, root, od); - PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), (float) rect2D.getWidth(), (float) rect2D.getHeight()); + Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, + root, od); + PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), + (float) rect2D.getWidth(), (float) rect2D.getHeight()); widget.setRectangle(rect); widget.setPage(ctrl.page); widget.setPrinted(true); - + ctrl.page.getAnnotations().add(widget); } - - private void processHiddenControl(ControlFontPair pair, Control ctrl, PDAcroForm acro, int i, Box root) throws IOException { + + private void processHiddenControl(ControlFontPair pair, Control ctrl, PDAcroForm acro, int i, Box root) + throws IOException { PDTextField field = new PDTextField(acro); setPartialNameToField(ctrl, field); - + String value = ctrl.box.getElement().getAttribute("value"); - + field.setDefaultValue(value); field.setValue(value); - + // Even hidden fields need an associated widget to work. PDAnnotationWidget widgy = field.getWidgets().get(0); widgy.setPage(ctrl.page); @@ -415,59 +422,99 @@ private void processHiddenControl(ControlFontPair pair, Control ctrl, PDAcroForm widgy.setRectangle(new PDRectangle(0, 0, 1, 1)); ctrl.page.getAnnotations().add(widgy); } - - private void processTextControl(ControlFontPair pair, Control ctrl, PDAcroForm acro, int i, Box root) throws IOException { + + private void processTextControl(ControlFontPair pair, Control ctrl, PDAcroForm acro, int i, Box root) + throws IOException { PDTextField field = new PDTextField(acro); setPartialNameToField(ctrl, field); - + FSColor color = ctrl.box.getStyle().getColor(); String colorOperator = getColorOperator(color); String fontInstruction = "/" + pair.fontName + " 0 Tf"; field.setDefaultAppearance(fontInstruction + ' ' + colorOperator); - + String value = ctrl.box.getElement().getNodeName().equals("textarea") ? getTextareaText(ctrl.box.getElement()) : ctrl.box.getElement().getAttribute("value"); - + field.setDefaultValue(value); // The reset value. field.setValue(value); // The original value. - + if (OpenUtil.parseIntegerOrNull(ctrl.box.getElement().getAttribute("max-length")) != null) { field.setMaxLen(OpenUtil.parseIntegerOrNull(ctrl.box.getElement().getAttribute("max-length"))); } - + if (ctrl.box.getElement().hasAttribute("required")) { field.setRequired(true); } - + if (ctrl.box.getElement().hasAttribute("readonly")) { field.setReadOnly(true); } - + if (ctrl.box.getElement().getNodeName().equals("textarea")) { field.setMultiline(true); } else if (ctrl.box.getElement().getAttribute("type").equals("password")) { field.setPassword(true); } else if (ctrl.box.getElement().getAttribute("type").equals("file")) { - XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.GENERAL_PDF_ACROBAT_READER_DOES_NOT_SUPPORT_FORMS_WITH_FILE_INPUT); + XRLog.log(Level.WARNING, + LogMessageId.LogMessageId0Param.GENERAL_PDF_ACROBAT_READER_DOES_NOT_SUPPORT_FORMS_WITH_FILE_INPUT); field.setFileSelect(true); } - + if (ctrl.box.getElement().hasAttribute("title")) { field.setAlternateFieldName(ctrl.box.getElement().getAttribute("title")); } - + PDAnnotationWidget widget = field.getWidgets().get(0); - Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, root, od); - PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), (float) rect2D.getWidth(), (float) rect2D.getHeight()); + Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, + root, od); + PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), + (float) rect2D.getWidth(), (float) rect2D.getHeight()); + + widget.setRectangle(rect); + widget.setPage(ctrl.page); + widget.setPrinted(true); + + ctrl.page.getAnnotations().add(widget); + } + + private void processSignatureControl(ControlFontPair pair, Control ctrl, PDAcroForm acro, int i, Box root) + throws IOException { + PDSignatureField field = new PDSignatureField(acro); + + setPartialNameToField(ctrl, field); + + + if (ctrl.box.getElement().hasAttribute("required")) { + field.setRequired(true); + } + + if (ctrl.box.getElement().hasAttribute("readonly")) { + field.setReadOnly(true); + } + + if (ctrl.box.getElement().hasAttribute("title")) { + field.setAlternateFieldName(ctrl.box.getElement().getAttribute("title")); + } + if (ctrl.box.getElement().hasAttribute("alt")) { + field.setPartialName(ctrl.box.getElement().getAttribute("alt")); + } + + PDAnnotationWidget widget = field.getWidgets().get(0); + + Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, + root, od); + PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), + (float) rect2D.getWidth(), (float) rect2D.getHeight()); widget.setRectangle(rect); widget.setPage(ctrl.page); widget.setPrinted(true); - + ctrl.page.getAnnotations().add(widget); } @@ -483,32 +530,32 @@ public enum CheckboxStyle { STAR(72), SQUARE(110); - + private final int caption; - + CheckboxStyle(int caption) { this.caption = caption; } - + public static CheckboxStyle fromIdent(IdentValue id) { if (id == IdentValue.CHECK) return CHECK; - + if (id == IdentValue.CROSS) return CROSS; - + if (id == IdentValue.SQUARE) return SQUARE; - + if (id == IdentValue.CIRCLE) return CIRCLE; - + if (id == IdentValue.DIAMOND) return DIAMOND; - + if (id == IdentValue.STAR) return STAR; - + return CHECK; } } @@ -516,19 +563,14 @@ public static CheckboxStyle fromIdent(IdentValue id) { /** * Creates a checkbox appearance stream. Uses an ordinal of the zapf dingbats font for the check mark. */ - public static PDAppearanceStream createCheckboxAppearance(CheckboxStyle style, PDDocument doc, PDResources resources) { - String appear = - "q\n" + - "BT\n" + - "1 0 0 1 15 20 Tm\n" + - "/OpenHTMLZap 100 Tf\n" + - "(" + (char) style.caption + ") Tj\n" + - "ET\n" + - "Q\n"; - + public static PDAppearanceStream createCheckboxAppearance(CheckboxStyle style, PDDocument doc, + PDResources resources) { + String appear = "q\n" + "BT\n" + "1 0 0 1 15 20 Tm\n" + "/OpenHTMLZap 100 Tf\n" + "(" + (char) style.caption + + ") Tj\n" + "ET\n" + "Q\n"; + return createCheckboxAppearance(appear, doc, resources); } - + public static PDAppearanceStream createCheckboxAppearance(String appear, PDDocument doc, PDResources resources) { PDAppearanceStream s = new PDAppearanceStream(doc); s.setBBox(new PDRectangle(100f, 100f)); @@ -547,12 +589,9 @@ private COSString getCOSStringUTF16Encoded(String value) { ByteArrayOutputStream out = new ByteArrayOutputStream(data.length + 2); out.write(0xFE); // BOM out.write(0xFF); // BOM - try - { + try { out.write(data); - } - catch (IOException e) - { + } catch (IOException e) { // should never happen throw new RuntimeException(e); } @@ -560,8 +599,9 @@ private COSString getCOSStringUTF16Encoded(String value) { COSString valueEncoded = new COSString(bytes); return valueEncoded; } - - private void processCheckboxControl(ControlFontPair pair, PDAcroForm acro, int i, Control ctrl, Box root) throws IOException { + + private void processCheckboxControl(ControlFontPair pair, PDAcroForm acro, int i, Control ctrl, Box root) + throws IOException { PDCheckBox field = new PDCheckBox(acro); setPartialNameToField(ctrl, field); @@ -569,25 +609,25 @@ private void processCheckboxControl(ControlFontPair pair, PDAcroForm acro, int i if (ctrl.box.getElement().hasAttribute("required")) { field.setRequired(true); } - + if (ctrl.box.getElement().hasAttribute("readonly")) { field.setReadOnly(true); } - + /* - * The only way I could get Acrobat Reader to display the checkbox checked properly was to + * The only way I could get Acrobat Reader to display the checkbox checked properly was to * use an explicitly encoded unicode string for the OPT entry of the dictionary. */ COSArray arr = new COSArray(); arr.add(getCOSStringUTF16Encoded(ctrl.box.getElement().getAttribute("value"))); field.getCOSObject().setItem(COSName.OPT, arr); - + if (ctrl.box.getElement().hasAttribute("title")) { field.setAlternateFieldName(ctrl.box.getElement().getAttribute("title")); } - + COSName zero = COSName.getPDFName("0"); - + if (ctrl.box.getElement().hasAttribute("checked")) { field.getCOSObject().setItem(COSName.AS, zero); field.getCOSObject().setItem(COSName.V, zero); @@ -597,18 +637,21 @@ private void processCheckboxControl(ControlFontPair pair, PDAcroForm acro, int i field.getCOSObject().setItem(COSName.V, COSName.Off); field.getCOSObject().setItem(COSName.DV, COSName.Off); } - - Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, root, od); - PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), (float) rect2D.getWidth(), (float) rect2D.getHeight()); + + Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, + root, od); + PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), + (float) rect2D.getWidth(), (float) rect2D.getHeight()); PDAnnotationWidget widget = field.getWidgets().get(0); widget.setRectangle(rect); widget.setPage(ctrl.page); widget.setPrinted(true); - + CheckboxStyle style = CheckboxStyle.fromIdent(ctrl.box.getStyle().getIdent(CSSName.FS_CHECKBOX_STYLE)); - - PDAppearanceCharacteristicsDictionary appearanceCharacteristics = new PDAppearanceCharacteristicsDictionary(new COSDictionary()); + + PDAppearanceCharacteristicsDictionary appearanceCharacteristics = new PDAppearanceCharacteristicsDictionary( + new COSDictionary()); appearanceCharacteristics.setNormalCaption(String.valueOf((char) style.caption)); widget.setAppearanceCharacteristics(appearanceCharacteristics); @@ -618,10 +661,10 @@ private void processCheckboxControl(ControlFontPair pair, PDAcroForm acro, int i PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary(); appearanceDict.getCOSObject().setItem(COSName.N, dict); widget.setAppearance(appearanceDict); - + ctrl.page.getAnnotations().add(widget); } - + private void processRadioButtonGroup(List group, PDAcroForm acro, int i, Box root) throws IOException { String groupName = group.get(0).box.getElement().getAttribute("name"); PDRadioButton field = new PDRadioButton(acro); @@ -629,33 +672,31 @@ private void processRadioButtonGroup(List group, PDAcroForm acro, int i Field fObj = allFieldMap.get(groupName); setPartialNameToField(group.get(0).box.getElement(), fObj, field); - List values = - group.stream() - .map(ctrl -> ctrl.box.getElement().getAttribute("value")) + List values = group.stream().map(ctrl -> ctrl.box.getElement().getAttribute("value")) .collect(Collectors.toList()); field.setExportValues(values); // We can not make individual members of the group readonly so only make // all radio buttons in group readonly if they are all marked readonly. - boolean readonly = - group.stream() - .allMatch(ctrl -> ctrl.box.getElement().hasAttribute("readonly")); + boolean readonly = group.stream().allMatch(ctrl -> ctrl.box.getElement().hasAttribute("readonly")); field.setReadOnly(readonly); List widgets = new ArrayList<>(group.size()); - + int radioCnt = 0; - + for (Control ctrl : group) { - Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, root, od); - PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), (float) rect2D.getWidth(), (float) rect2D.getHeight()); - + Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, + ctrl.transform, root, od); + PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), + (float) rect2D.getWidth(), (float) rect2D.getHeight()); + PDAnnotationWidget widget = new PDAnnotationWidget(); - + widget.setRectangle(rect); widget.setPage(ctrl.page); widget.setPrinted(true); - + COSDictionary dict = new COSDictionary(); dict.setItem(COSName.getPDFName("" + radioCnt), docFormsStateContainer.getRadioOnStream()); dict.setItem(COSName.Off, docFormsStateContainer.getRadioOffStream()); @@ -669,10 +710,10 @@ private void processRadioButtonGroup(List group, PDAcroForm acro, int i } widget.setAppearance(appearanceDict); - + widgets.add(widget); ctrl.page.getAnnotations().add(widget); - + radioCnt++; } @@ -684,7 +725,7 @@ private void processRadioButtonGroup(List group, PDAcroForm acro, int i } } } - + private void processSubmitControl(PDAcroForm acro, int i, Control ctrl, Box root) throws IOException { final int FLAG_USE_GET = 1 << 3; final int FLAG_USE_HTML_SUBMIT = 1 << 2; @@ -694,17 +735,17 @@ private void processSubmitControl(PDAcroForm acro, int i, Control ctrl, Box root if (ctrl.box.getElement().hasAttribute("name")) { // Buttons can't have a value so we create a hidden text field instead. PDTextField field = new PDTextField(acro); - + Field fObj = allFieldMap.get(ctrl.box.getElement().getAttribute("name")); fObj.field = field; - + field.setPartialName(fObj.partialName); - + String value = ctrl.box.getElement().getAttribute("value"); - + field.setDefaultValue(value); field.setValue(value); - + // Even hidden fields need an associated widget to work. PDAnnotationWidget widgy = field.getWidgets().get(0); widgy.setPage(ctrl.page); @@ -712,14 +753,16 @@ private void processSubmitControl(PDAcroForm acro, int i, Control ctrl, Box root widgy.setRectangle(new PDRectangle(0, 0, 1, 1)); ctrl.page.getAnnotations().add(widgy); } - + // We use an internal name so as not to conflict with a hidden text that we just created. btn.setPartialName("OpenHTMLCtrl" + i); - + PDAnnotationWidget widget = btn.getWidgets().get(0); - - Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, root, od); - PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), (float) rect2D.getWidth(), (float) rect2D.getHeight()); + + Rectangle2D rect2D = PdfBoxFastLinkManager.createTargetArea(ctrl.c, ctrl.box, ctrl.pageHeight, ctrl.transform, + root, od); + PDRectangle rect = new PDRectangle((float) rect2D.getMinX(), (float) rect2D.getMinY(), + (float) rect2D.getWidth(), (float) rect2D.getHeight()); widget.setRectangle(rect); widget.setPage(ctrl.page); @@ -730,15 +773,16 @@ private void processSubmitControl(PDAcroForm acro, int i, Control ctrl, Box root fieldsToInclude.add(f.qualifiedName); } } - + if (ctrl.box.getElement().getAttribute("type").equals("reset")) { PDActionResetForm reset = new PDActionResetForm(); reset.setFields(fieldsToInclude.getCOSArray()); - widget.setAction(reset);; + widget.setAction(reset); + ; } else { PDFileSpecification fs = PDFileSpecification.createFS(new COSString(element.getAttribute("action"))); PDActionSubmitForm submit = new PDActionSubmitForm(); - + submit.setFields(fieldsToInclude.getCOSArray()); submit.setFile(fs); @@ -778,57 +822,60 @@ private static void setPartialNameToField(Element element, Field fObj, PDField f XRLog.log(Level.WARNING, LogMessageId.LogMessageId2Param.GENERAL_PDF_FOUND_ELEMENT_WITHOUT_ATTRIBUTE_NAME, element.getTagName(), sb.toString()); } } - + public int process(PDAcroForm acro, int startId, Box root) throws IOException { processControlNames(); - + int i = startId; for (ControlFontPair pair : controls) { i++; - + Control ctrl = pair.control; Element e = ctrl.box.getElement(); - - if ((e.getNodeName().equals("input") && - e.getAttribute("type").equals("text")) || - (e.getNodeName().equals("textarea")) || - (e.getNodeName().equals("input") && - e.getAttribute("type").equals("password")) || - (e.getNodeName().equals("input") && - e.getAttribute("type").equals("file"))) { + + + if ((e.getNodeName().equals("input") && e.getAttribute("type").equals("text") + && e.getAttribute("class").contains("signature"))) { + + processSignatureControl(pair, ctrl, acro, i, root); + + } else if ((e.getNodeName().equals("input") && e.getAttribute("type").equals("text")) + || (e.getNodeName().equals("textarea")) + || (e.getNodeName().equals("input") && e.getAttribute("type").equals("password")) + || (e.getNodeName().equals("input") && e.getAttribute("type").equals("file"))) { // Start with the text controls (text, password, file and textarea). processTextControl(pair, ctrl, acro, i, root); } else if ((e.getNodeName().equals("select") && !e.hasAttribute("multiple")) || (e.getNodeName().equals("openhtmltopdf-combo"))) { - + processSelectControl(pair, ctrl, acro, i, root); } else if (e.getNodeName().equals("select") && e.hasAttribute("multiple")) { - + processMultiSelectControl(pair, ctrl, acro, i, root); } else if (e.getNodeName().equals("input") && e.getAttribute("type").equals("checkbox")) { - + processCheckboxControl(pair, acro, i, ctrl, root); } else if (e.getNodeName().equals("input") && e.getAttribute("type").equals("hidden")) { - + processHiddenControl(pair, ctrl, acro, i, root); } else if (e.getNodeName().equals("input") && e.getAttribute("type").equals("radio")) { // We have to do radio button groups in one hit so add them to a map of list keyed on name. List radioGroup = radioGroups.get(e.getAttribute("name")); - + if (radioGroup == null) { radioGroup = new ArrayList<>(); radioGroups.put(e.getAttribute("name"), radioGroup); } radioGroup.add(ctrl); - } else if ((e.getNodeName().equals("input") && + } else if ((e.getNodeName().equals("input") && e.getAttribute("type").equals("submit")) || (e.getNodeName().equals("button") && !e.getAttribute("type").equals("button")) || @@ -839,21 +886,21 @@ public int process(PDAcroForm acro, int startId, Box root) throws IOException { submits.add(ctrl); } } - + // Now process each group of radio buttons. for (List group : radioGroups.values()) { i++; processRadioButtonGroup(group, acro, i, root); } - + // We do submit controls last as we need all the fields in this form. for (Control ctrl : submits) { i++; processSubmitControl(acro, i, ctrl, root); } - + createNonTerminalFields(acro); - + return i; } } diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPerDocumentFormState.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPerDocumentFormState.java index cea67bded..8262bb85a 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPerDocumentFormState.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPerDocumentFormState.java @@ -95,8 +95,8 @@ public void addControlIfRequired(Box box, PDPage page, AffineTransform transform } } - private String getControlFont(SharedContext sharedContext, PdfBoxForm.Control ctrl) { - PDFont fnt = ((PdfBoxFSFont) sharedContext.getFont(ctrl.box.getStyle().getFontSpecification())).getFontDescription().get(0).getFont(); + private String getControlFont(SharedContext sharedContext, PdfBoxForm.Control ctrl, RenderingContext renderingContext) { + PDFont fnt = ((PdfBoxFSFont) sharedContext.getFont(ctrl.box.getStyle().getFont(renderingContext))).getFontDescription().get(0).getFont(); String fontName; if (!controlFonts.containsKey(fnt)) { @@ -139,14 +139,14 @@ private void createCheckboxFontResource() { } } - public void processControls(SharedContext sharedContext, PDDocument writer, Box root) { + public void processControls(SharedContext sharedContext, PDDocument writer, Box root, RenderingContext renderingContext) { for (PdfBoxForm.Control ctrl : controls) { PdfBoxForm frm = findEnclosingForm(ctrl.box.getElement()); String fontName = null; if (!ArrayUtil.isOneOf(ctrl.box.getElement().getAttribute("type"), "checkbox", "radio", "hidden")) { // Need to embed a font for every control other than checkbox, radio and hidden. - fontName = getControlFont(sharedContext, ctrl); + fontName = getControlFont(sharedContext, ctrl, renderingContext); } else if (ctrl.box.getElement().getAttribute("type").equals("checkbox")) { createCheckboxFontResource(); createCheckboxAppearanceStreams(writer, ctrl);