Commit 397b097c469db89a49e5a6c2035a0beee2e4d117
1 parent
952a665a
Allow setting a form field's value
Showing
10 changed files
with
163 additions
and
1 deletions
ChangeLog
| 1 | 2018-06-21 Jay Berkenbilt <ejb@ql.org> | 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 | * Added QPDFObject::newUnicodeString and QPDFObject::unparseBinary | 8 | * Added QPDFObject::newUnicodeString and QPDFObject::unparseBinary |
| 4 | to allow for more convenient creation of strings that are | 9 | to allow for more convenient creation of strings that are |
| 5 | explicitly encoded in UTF-16 BE. This is useful for creating | 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,6 +135,23 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper | ||
| 135 | QPDFFormFieldObjectHelper | 135 | QPDFFormFieldObjectHelper |
| 136 | getFieldForAnnotation(QPDFAnnotationObjectHelper); | 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 | private: | 155 | private: |
| 139 | void analyze(); | 156 | void analyze(); |
| 140 | void traverseField(QPDFObjectHandle field, | 157 | void traverseField(QPDFObjectHandle field, |
include/qpdf/QPDFFormFieldObjectHelper.hh
| @@ -114,6 +114,29 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper | @@ -114,6 +114,29 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper | ||
| 114 | QPDF_DLL | 114 | QPDF_DLL |
| 115 | int getQuadding(); | 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 | private: | 140 | private: |
| 118 | class Members | 141 | class Members |
| 119 | { | 142 | { |
libqpdf/QPDFAcroFormDocumentHelper.cc
| @@ -250,3 +250,38 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -250,3 +250,38 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 250 | QPDFFormFieldObjectHelper(our_field); | 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 | #include <qpdf/QPDFFormFieldObjectHelper.hh> | 1 | #include <qpdf/QPDFFormFieldObjectHelper.hh> |
| 2 | #include <qpdf/QTC.hh> | 2 | #include <qpdf/QTC.hh> |
| 3 | +#include <qpdf/QPDFAcroFormDocumentHelper.hh> | ||
| 3 | 4 | ||
| 4 | QPDFFormFieldObjectHelper::Members::~Members() | 5 | QPDFFormFieldObjectHelper::Members::~Members() |
| 5 | { | 6 | { |
| @@ -188,3 +189,44 @@ QPDFFormFieldObjectHelper::getQuadding() | @@ -188,3 +189,44 @@ QPDFFormFieldObjectHelper::getQuadding() | ||
| 188 | } | 189 | } |
| 189 | return result; | 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,7 +115,7 @@ my @form_tests = ( | ||
| 115 | 'form-errors', | 115 | 'form-errors', |
| 116 | ); | 116 | ); |
| 117 | 117 | ||
| 118 | -$n_tests += scalar(@form_tests); | 118 | +$n_tests += scalar(@form_tests) + 2; |
| 119 | 119 | ||
| 120 | # Many of the form*.pdf files were created by converting the | 120 | # Many of the form*.pdf files were created by converting the |
| 121 | # LibreOffice document storage/form.odt to PDF and then manually | 121 | # LibreOffice document storage/form.odt to PDF and then manually |
| @@ -132,6 +132,15 @@ foreach my $f (@form_tests) | @@ -132,6 +132,15 @@ foreach my $f (@form_tests) | ||
| 132 | $td->NORMALIZE_NEWLINES); | 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 | show_ntests(); | 144 | show_ntests(); |
| 136 | # ---------- | 145 | # ---------- |
| 137 | $td->notify("--- Stream Replacement Tests ---"); | 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,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 | else | 1615 | else |
| 1588 | { | 1616 | { |
| 1589 | throw std::runtime_error(std::string("invalid test ") + | 1617 | throw std::runtime_error(std::string("invalid test ") + |