Allow setting a form field's value

This commit is contained in:
Jay Berkenbilt 2018-06-21 15:00:00 -04:00
parent 952a665a4e
commit 397b097c46
10 changed files with 163 additions and 1 deletions

View File

@ -1,5 +1,10 @@
2018-06-21 Jay Berkenbilt <ejb@ql.org>
* Added methods QPDFAcroFormDocumentHelper::setNeedAppearances and
added methods to QPDFFormFieldObjectHelper to set a field's value,
optionally updating the document to indicate that appearance
streams need to be regenerated.
* Added QPDFObject::newUnicodeString and QPDFObject::unparseBinary
to allow for more convenient creation of strings that are
explicitly encoded in UTF-16 BE. This is useful for creating

View File

@ -135,6 +135,23 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
QPDFFormFieldObjectHelper
getFieldForAnnotation(QPDFAnnotationObjectHelper);
// Return the current value of /NeedAppearances. If
// /NeedAppearances is missing, return false as that is how PDF
// viewers are supposed to interpret it.
QPDF_DLL
bool getNeedAppearances();
// Indicate whether appearance streams must be regenerated. If you
// modify a field value, you should call setNeedAppearances(true)
// unless you also generate an appearance stream for the
// corresponding annotation at the same time. If you generate
// appearance streams for all fields, you can call
// setNeedAppearances(false). If you use
// QPDFFormFieldObjectHelper::setV, it will automatically call
// this method unless you tell it not to.
QPDF_DLL
void setNeedAppearances(bool);
private:
void analyze();
void traverseField(QPDFObjectHandle field,

View File

@ -114,6 +114,29 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper
QPDF_DLL
int getQuadding();
// Set an attribute to the given value
QPDF_DLL
void setFieldAttribute(std::string const& key, QPDFObjectHandle value);
// Set an attribute to the given value as a Unicode string (UTF-16
// BE encoded). The input string should be UTF-8 encoded.
QPDF_DLL
void setFieldAttribute(std::string const& key,
std::string const& utf8_value);
// Set /V (field value) to the given value. Optionally set
// /NeedAppearances to true. You can explicitly tell this method
// not to set /NeedAppearances if you are going to explicitly
// generate an appearance stream yourself.
QPDF_DLL
void setV(QPDFObjectHandle value, bool need_appearances = true);
// Set /V (field value) to the given string value encoded as a
// Unicode string. The input value should be UTF-8 encoded. See
// comments above about /NeedAppearances.
QPDF_DLL
void setV(std::string const& utf8_value, bool need_appearances = true);
private:
class Members
{

View File

@ -250,3 +250,38 @@ QPDFAcroFormDocumentHelper::traverseField(
QPDFFormFieldObjectHelper(our_field);
}
}
bool
QPDFAcroFormDocumentHelper::getNeedAppearances()
{
bool result = false;
QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm");
if (acroform.isDictionary() &&
acroform.getKey("/NeedAppearances").isBool())
{
result = acroform.getKey("/NeedAppearances").getBoolValue();
}
return result;
}
void
QPDFAcroFormDocumentHelper::setNeedAppearances(bool val)
{
QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm");
if (! acroform.isDictionary())
{
this->qpdf.getRoot().warnIfPossible(
"ignoring call to QPDFAcroFormDocumentHelper::setNeedAppearances"
" on a file that lacks an /AcroForm dictionary");
return;
}
if (val)
{
acroform.replaceKey("/NeedAppearances",
QPDFObjectHandle::newBool(true));
}
else
{
acroform.removeKey("/NeedAppearances");
}
}

View File

@ -1,5 +1,6 @@
#include <qpdf/QPDFFormFieldObjectHelper.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
QPDFFormFieldObjectHelper::Members::~Members()
{
@ -188,3 +189,44 @@ QPDFFormFieldObjectHelper::getQuadding()
}
return result;
}
void
QPDFFormFieldObjectHelper::setFieldAttribute(
std::string const& key, QPDFObjectHandle value)
{
this->oh.replaceKey(key, value);
}
void
QPDFFormFieldObjectHelper::setFieldAttribute(
std::string const& key, std::string const& utf8_value)
{
this->oh.replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value));
}
void
QPDFFormFieldObjectHelper::setV(
QPDFObjectHandle value, bool need_appearances)
{
setFieldAttribute("/V", value);
if (need_appearances)
{
QPDF* qpdf = this->oh.getOwningQPDF();
if (! qpdf)
{
throw std::logic_error(
"QPDFFormFieldObjectHelper::setV called with"
" need_appearances = true on an object that is"
" not associated with an owning QPDF");
}
QPDFAcroFormDocumentHelper(*qpdf).setNeedAppearances(true);
}
}
void
QPDFFormFieldObjectHelper::setV(
std::string const& utf8_value, bool need_appearances)
{
setV(QPDFObjectHandle::newUnicodeString(utf8_value),
need_appearances);
}

View File

@ -115,7 +115,7 @@ my @form_tests = (
'form-errors',
);
$n_tests += scalar(@form_tests);
$n_tests += scalar(@form_tests) + 2;
# Many of the form*.pdf files were created by converting the
# LibreOffice document storage/form.odt to PDF and then manually
@ -132,6 +132,15 @@ foreach my $f (@form_tests)
$td->NORMALIZE_NEWLINES);
}
$td->runtest("fill fields",
{$td->COMMAND => "test_driver 44 form-no-need-appearances.pdf"},
{$td->FILE => "form-no-need-appearances.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("compare files",
{$td->FILE => "a.pdf"},
{$td->FILE => "form-no-need-appearances-filled.pdf"});
show_ntests();
# ----------
$td->notify("--- Stream Replacement Tests ---");

Binary file not shown.

View File

@ -0,0 +1,3 @@
Set field value: Text Box 1 -> 3.14 ÷ 0
Set field value: Text Box 2 -> 3.14 ÷ 0
test 44 done

Binary file not shown.

View File

@ -1584,6 +1584,34 @@ void runtest(int n, char const* filename1, char const* arg2)
}
}
}
else if (n == 44)
{
// Set form fields.
QPDFAcroFormDocumentHelper afdh(pdf);
std::vector<QPDFFormFieldObjectHelper> fields = afdh.getFormFields();
for (std::vector<QPDFFormFieldObjectHelper>::iterator iter =
fields.begin();
iter != fields.end(); ++iter)
{
QPDFFormFieldObjectHelper& field(*iter);
QPDFObjectHandle ft = field.getInheritableFieldValue("/FT");
if (ft.isName() && (ft.getName() == "/Tx"))
{
// \xc3\xb7 is utf-8 for U+00F7 (divided by)
field.setV("3.14 \xc3\xb7 0");
std::cout << "Set field value: "
<< field.getFullyQualifiedName()
<< " -> "
<< field.getValueAsString()
<< std::endl;
}
}
QPDFWriter w(pdf, "a.pdf");
w.setQDFMode(true);
w.setStaticID(true);
w.setSuppressOriginalObjectIDs(true);
w.write();
}
else
{
throw std::runtime_error(std::string("invalid test ") +