Commit 397b097c469db89a49e5a6c2035a0beee2e4d117

Authored by Jay Berkenbilt
1 parent 952a665a

Allow setting a form field's value

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
  1 +Set field value: Text Box 1 -> 3.14 ÷ 0
  2 +Set field value: Text Box 2 -> 3.14 ÷ 0
  3 +test 44 done
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 ") +