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 @@
+
+
+
+
+
+
+
+
+
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);