Commit 397b097c469db89a49e5a6c2035a0beee2e4d117
1 parent
952a665a
Allow setting a form field's value
Showing
10 changed files
with
163 additions
and
1 deletions
ChangeLog
| 1 | 1 | 2018-06-21 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | |
| 3 | + * Added methods QPDFAcroFormDocumentHelper::setNeedAppearances and | |
| 4 | + added methods to QPDFFormFieldObjectHelper to set a field's value, | |
| 5 | + optionally updating the document to indicate that appearance | |
| 6 | + streams need to be regenerated. | |
| 7 | + | |
| 3 | 8 | * Added QPDFObject::newUnicodeString and QPDFObject::unparseBinary |
| 4 | 9 | to allow for more convenient creation of strings that are |
| 5 | 10 | explicitly encoded in UTF-16 BE. This is useful for creating | ... | ... |
include/qpdf/QPDFAcroFormDocumentHelper.hh
| ... | ... | @@ -135,6 +135,23 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper |
| 135 | 135 | QPDFFormFieldObjectHelper |
| 136 | 136 | getFieldForAnnotation(QPDFAnnotationObjectHelper); |
| 137 | 137 | |
| 138 | + // Return the current value of /NeedAppearances. If | |
| 139 | + // /NeedAppearances is missing, return false as that is how PDF | |
| 140 | + // viewers are supposed to interpret it. | |
| 141 | + QPDF_DLL | |
| 142 | + bool getNeedAppearances(); | |
| 143 | + | |
| 144 | + // Indicate whether appearance streams must be regenerated. If you | |
| 145 | + // modify a field value, you should call setNeedAppearances(true) | |
| 146 | + // unless you also generate an appearance stream for the | |
| 147 | + // corresponding annotation at the same time. If you generate | |
| 148 | + // appearance streams for all fields, you can call | |
| 149 | + // setNeedAppearances(false). If you use | |
| 150 | + // QPDFFormFieldObjectHelper::setV, it will automatically call | |
| 151 | + // this method unless you tell it not to. | |
| 152 | + QPDF_DLL | |
| 153 | + void setNeedAppearances(bool); | |
| 154 | + | |
| 138 | 155 | private: |
| 139 | 156 | void analyze(); |
| 140 | 157 | void traverseField(QPDFObjectHandle field, | ... | ... |
include/qpdf/QPDFFormFieldObjectHelper.hh
| ... | ... | @@ -114,6 +114,29 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper |
| 114 | 114 | QPDF_DLL |
| 115 | 115 | int getQuadding(); |
| 116 | 116 | |
| 117 | + // Set an attribute to the given value | |
| 118 | + QPDF_DLL | |
| 119 | + void setFieldAttribute(std::string const& key, QPDFObjectHandle value); | |
| 120 | + | |
| 121 | + // Set an attribute to the given value as a Unicode string (UTF-16 | |
| 122 | + // BE encoded). The input string should be UTF-8 encoded. | |
| 123 | + QPDF_DLL | |
| 124 | + void setFieldAttribute(std::string const& key, | |
| 125 | + std::string const& utf8_value); | |
| 126 | + | |
| 127 | + // Set /V (field value) to the given value. Optionally set | |
| 128 | + // /NeedAppearances to true. You can explicitly tell this method | |
| 129 | + // not to set /NeedAppearances if you are going to explicitly | |
| 130 | + // generate an appearance stream yourself. | |
| 131 | + QPDF_DLL | |
| 132 | + void setV(QPDFObjectHandle value, bool need_appearances = true); | |
| 133 | + | |
| 134 | + // Set /V (field value) to the given string value encoded as a | |
| 135 | + // Unicode string. The input value should be UTF-8 encoded. See | |
| 136 | + // comments above about /NeedAppearances. | |
| 137 | + QPDF_DLL | |
| 138 | + void setV(std::string const& utf8_value, bool need_appearances = true); | |
| 139 | + | |
| 117 | 140 | private: |
| 118 | 141 | class Members |
| 119 | 142 | { | ... | ... |
libqpdf/QPDFAcroFormDocumentHelper.cc
| ... | ... | @@ -250,3 +250,38 @@ QPDFAcroFormDocumentHelper::traverseField( |
| 250 | 250 | QPDFFormFieldObjectHelper(our_field); |
| 251 | 251 | } |
| 252 | 252 | } |
| 253 | + | |
| 254 | +bool | |
| 255 | +QPDFAcroFormDocumentHelper::getNeedAppearances() | |
| 256 | +{ | |
| 257 | + bool result = false; | |
| 258 | + QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm"); | |
| 259 | + if (acroform.isDictionary() && | |
| 260 | + acroform.getKey("/NeedAppearances").isBool()) | |
| 261 | + { | |
| 262 | + result = acroform.getKey("/NeedAppearances").getBoolValue(); | |
| 263 | + } | |
| 264 | + return result; | |
| 265 | +} | |
| 266 | + | |
| 267 | +void | |
| 268 | +QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) | |
| 269 | +{ | |
| 270 | + QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm"); | |
| 271 | + if (! acroform.isDictionary()) | |
| 272 | + { | |
| 273 | + this->qpdf.getRoot().warnIfPossible( | |
| 274 | + "ignoring call to QPDFAcroFormDocumentHelper::setNeedAppearances" | |
| 275 | + " on a file that lacks an /AcroForm dictionary"); | |
| 276 | + return; | |
| 277 | + } | |
| 278 | + if (val) | |
| 279 | + { | |
| 280 | + acroform.replaceKey("/NeedAppearances", | |
| 281 | + QPDFObjectHandle::newBool(true)); | |
| 282 | + } | |
| 283 | + else | |
| 284 | + { | |
| 285 | + acroform.removeKey("/NeedAppearances"); | |
| 286 | + } | |
| 287 | +} | ... | ... |
libqpdf/QPDFFormFieldObjectHelper.cc
| 1 | 1 | #include <qpdf/QPDFFormFieldObjectHelper.hh> |
| 2 | 2 | #include <qpdf/QTC.hh> |
| 3 | +#include <qpdf/QPDFAcroFormDocumentHelper.hh> | |
| 3 | 4 | |
| 4 | 5 | QPDFFormFieldObjectHelper::Members::~Members() |
| 5 | 6 | { |
| ... | ... | @@ -188,3 +189,44 @@ QPDFFormFieldObjectHelper::getQuadding() |
| 188 | 189 | } |
| 189 | 190 | return result; |
| 190 | 191 | } |
| 192 | + | |
| 193 | +void | |
| 194 | +QPDFFormFieldObjectHelper::setFieldAttribute( | |
| 195 | + std::string const& key, QPDFObjectHandle value) | |
| 196 | +{ | |
| 197 | + this->oh.replaceKey(key, value); | |
| 198 | +} | |
| 199 | + | |
| 200 | +void | |
| 201 | +QPDFFormFieldObjectHelper::setFieldAttribute( | |
| 202 | + std::string const& key, std::string const& utf8_value) | |
| 203 | +{ | |
| 204 | + this->oh.replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); | |
| 205 | +} | |
| 206 | + | |
| 207 | +void | |
| 208 | +QPDFFormFieldObjectHelper::setV( | |
| 209 | + QPDFObjectHandle value, bool need_appearances) | |
| 210 | +{ | |
| 211 | + setFieldAttribute("/V", value); | |
| 212 | + if (need_appearances) | |
| 213 | + { | |
| 214 | + QPDF* qpdf = this->oh.getOwningQPDF(); | |
| 215 | + if (! qpdf) | |
| 216 | + { | |
| 217 | + throw std::logic_error( | |
| 218 | + "QPDFFormFieldObjectHelper::setV called with" | |
| 219 | + " need_appearances = true on an object that is" | |
| 220 | + " not associated with an owning QPDF"); | |
| 221 | + } | |
| 222 | + QPDFAcroFormDocumentHelper(*qpdf).setNeedAppearances(true); | |
| 223 | + } | |
| 224 | +} | |
| 225 | + | |
| 226 | +void | |
| 227 | +QPDFFormFieldObjectHelper::setV( | |
| 228 | + std::string const& utf8_value, bool need_appearances) | |
| 229 | +{ | |
| 230 | + setV(QPDFObjectHandle::newUnicodeString(utf8_value), | |
| 231 | + need_appearances); | |
| 232 | +} | ... | ... |
qpdf/qtest/qpdf.test
| ... | ... | @@ -115,7 +115,7 @@ my @form_tests = ( |
| 115 | 115 | 'form-errors', |
| 116 | 116 | ); |
| 117 | 117 | |
| 118 | -$n_tests += scalar(@form_tests); | |
| 118 | +$n_tests += scalar(@form_tests) + 2; | |
| 119 | 119 | |
| 120 | 120 | # Many of the form*.pdf files were created by converting the |
| 121 | 121 | # LibreOffice document storage/form.odt to PDF and then manually |
| ... | ... | @@ -132,6 +132,15 @@ foreach my $f (@form_tests) |
| 132 | 132 | $td->NORMALIZE_NEWLINES); |
| 133 | 133 | } |
| 134 | 134 | |
| 135 | +$td->runtest("fill fields", | |
| 136 | + {$td->COMMAND => "test_driver 44 form-no-need-appearances.pdf"}, | |
| 137 | + {$td->FILE => "form-no-need-appearances.out", | |
| 138 | + $td->EXIT_STATUS => 0}, | |
| 139 | + $td->NORMALIZE_NEWLINES); | |
| 140 | +$td->runtest("compare files", | |
| 141 | + {$td->FILE => "a.pdf"}, | |
| 142 | + {$td->FILE => "form-no-need-appearances-filled.pdf"}); | |
| 143 | + | |
| 135 | 144 | show_ntests(); |
| 136 | 145 | # ---------- |
| 137 | 146 | $td->notify("--- Stream Replacement Tests ---"); | ... | ... |
qpdf/qtest/qpdf/form-no-need-appearances-filled.pdf
0 → 100644
No preview for this file type
qpdf/qtest/qpdf/form-no-need-appearances.out
0 → 100644
qpdf/qtest/qpdf/form-no-need-appearances.pdf
0 → 100644
No preview for this file type
qpdf/test_driver.cc
| ... | ... | @@ -1584,6 +1584,34 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 1584 | 1584 | } |
| 1585 | 1585 | } |
| 1586 | 1586 | } |
| 1587 | + else if (n == 44) | |
| 1588 | + { | |
| 1589 | + // Set form fields. | |
| 1590 | + QPDFAcroFormDocumentHelper afdh(pdf); | |
| 1591 | + std::vector<QPDFFormFieldObjectHelper> fields = afdh.getFormFields(); | |
| 1592 | + for (std::vector<QPDFFormFieldObjectHelper>::iterator iter = | |
| 1593 | + fields.begin(); | |
| 1594 | + iter != fields.end(); ++iter) | |
| 1595 | + { | |
| 1596 | + QPDFFormFieldObjectHelper& field(*iter); | |
| 1597 | + QPDFObjectHandle ft = field.getInheritableFieldValue("/FT"); | |
| 1598 | + if (ft.isName() && (ft.getName() == "/Tx")) | |
| 1599 | + { | |
| 1600 | + // \xc3\xb7 is utf-8 for U+00F7 (divided by) | |
| 1601 | + field.setV("3.14 \xc3\xb7 0"); | |
| 1602 | + std::cout << "Set field value: " | |
| 1603 | + << field.getFullyQualifiedName() | |
| 1604 | + << " -> " | |
| 1605 | + << field.getValueAsString() | |
| 1606 | + << std::endl; | |
| 1607 | + } | |
| 1608 | + } | |
| 1609 | + QPDFWriter w(pdf, "a.pdf"); | |
| 1610 | + w.setQDFMode(true); | |
| 1611 | + w.setStaticID(true); | |
| 1612 | + w.setSuppressOriginalObjectIDs(true); | |
| 1613 | + w.write(); | |
| 1614 | + } | |
| 1587 | 1615 | else |
| 1588 | 1616 | { |
| 1589 | 1617 | throw std::runtime_error(std::string("invalid test ") + | ... | ... |