diff --git a/include/qpdf/QPDFFormFieldObjectHelper.hh b/include/qpdf/QPDFFormFieldObjectHelper.hh index b92114e..dd2e0e3 100644 --- a/include/qpdf/QPDFFormFieldObjectHelper.hh +++ b/include/qpdf/QPDFFormFieldObjectHelper.hh @@ -187,12 +187,6 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper void generateAppearance(QPDFAnnotationObjectHelper&); private: - QPDFObjectHandle getFieldFromAcroForm(std::string const& name); - void setRadioButtonValue(QPDFObjectHandle name); - void setCheckBoxValue(bool value); - void generateTextAppearance(QPDFAnnotationObjectHelper&); - QPDFObjectHandle getFontFromResource(QPDFObjectHandle resources, std::string const& font_name); - class Members; std::shared_ptr m; diff --git a/libqpdf/QPDFFormFieldObjectHelper.cc b/libqpdf/QPDFFormFieldObjectHelper.cc index de16556..a8bd5cd 100644 --- a/libqpdf/QPDFFormFieldObjectHelper.cc +++ b/libqpdf/QPDFFormFieldObjectHelper.cc @@ -1,5 +1,7 @@ #include +#include + #include #include #include @@ -14,37 +16,62 @@ using namespace qpdf; -class QPDFFormFieldObjectHelper::Members +using FormField = qpdf::impl::FormField; + +class QPDFFormFieldObjectHelper::Members: public qpdf::impl::FormField { + public: + Members(QPDFObjectHandle const& oh) : + FormField(oh) + { + } }; -QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper(QPDFObjectHandle oh) : - QPDFObjectHelper(oh), - m(std::make_shared()) +QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper(QPDFObjectHandle o) : + QPDFObjectHelper(o), + m(std::make_shared(oh())) { } QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper() : QPDFObjectHelper(QPDFObjectHandle::newNull()), - m(std::make_shared()) + m(std::make_shared(oh())) { } bool QPDFFormFieldObjectHelper::isNull() { + return m->isNull(); +} + +bool +FormField::isNull() +{ return oh().null(); } QPDFFormFieldObjectHelper QPDFFormFieldObjectHelper::getParent() { + return {m->getParent()}; +} + +FormField +FormField::getParent() +{ return oh().getKey("/Parent"); // may be null } QPDFFormFieldObjectHelper QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) { + return {m->getTopLevelField(is_different)}; +} + +FormField +FormField::getTopLevelField(bool* is_different) +{ auto top_field = oh(); QPDFObjGen::set seen; while (seen.add(top_field) && !top_field.getKeyIfDict("/Parent").null()) { @@ -57,7 +84,7 @@ QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) } QPDFObjectHandle -QPDFFormFieldObjectHelper::getFieldFromAcroForm(std::string const& name) +FormField::getFieldFromAcroForm(std::string const& name) { QPDFObjectHandle result = QPDFObjectHandle::newNull(); // Fields are supposed to be indirect, so this should work. @@ -75,6 +102,12 @@ QPDFFormFieldObjectHelper::getFieldFromAcroForm(std::string const& name) QPDFObjectHandle QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) { + return m->getInheritableFieldValue(name); +} + +QPDFObjectHandle +FormField::getInheritableFieldValue(std::string const& name) +{ QPDFObjectHandle node = oh(); if (!node.isDictionary()) { return QPDFObjectHandle::newNull(); @@ -96,6 +129,12 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) std::string QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& name) { + return m->getInheritableFieldValueAsString(name); +} + +std::string +FormField::getInheritableFieldValueAsString(std::string const& name) +{ auto fv = getInheritableFieldValue(name); if (fv.isString()) { return fv.getUTF8Value(); @@ -106,6 +145,12 @@ QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& n std::string QPDFFormFieldObjectHelper::getInheritableFieldValueAsName(std::string const& name) { + return m->getInheritableFieldValueAsName(name); +} + +std::string +FormField::getInheritableFieldValueAsName(std::string const& name) +{ if (Name fv = getInheritableFieldValue(name)) { return fv; } @@ -115,12 +160,24 @@ QPDFFormFieldObjectHelper::getInheritableFieldValueAsName(std::string const& nam std::string QPDFFormFieldObjectHelper::getFieldType() { + return m->getFieldType(); +} + +std::string +FormField::getFieldType() +{ return getInheritableFieldValueAsName("/FT"); } std::string QPDFFormFieldObjectHelper::getFullyQualifiedName() { + return m->getFullyQualifiedName(); +} + +std::string +FormField::getFullyQualifiedName() +{ std::string result; QPDFObjectHandle node = oh(); QPDFObjGen::set seen; @@ -139,6 +196,12 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() std::string QPDFFormFieldObjectHelper::getPartialName() { + return m->getPartialName(); +} + +std::string +FormField::getPartialName() +{ std::string result; if (oh().getKey("/T").isString()) { result = oh().getKey("/T").getUTF8Value(); @@ -149,6 +212,12 @@ QPDFFormFieldObjectHelper::getPartialName() std::string QPDFFormFieldObjectHelper::getAlternativeName() { + return m->getAlternativeName(); +} + +std::string +FormField::getAlternativeName() +{ if (oh().getKey("/TU").isString()) { QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU present"); return oh().getKey("/TU").getUTF8Value(); @@ -160,6 +229,12 @@ QPDFFormFieldObjectHelper::getAlternativeName() std::string QPDFFormFieldObjectHelper::getMappingName() { + return m->getMappingName(); +} + +std::string +FormField::getMappingName() +{ if (oh().getKey("/TM").isString()) { QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM present"); return oh().getKey("/TM").getUTF8Value(); @@ -171,6 +246,12 @@ QPDFFormFieldObjectHelper::getMappingName() QPDFObjectHandle QPDFFormFieldObjectHelper::getValue() { + return m->getValue(); +} + +QPDFObjectHandle +FormField::getValue() +{ return getInheritableFieldValue("/V"); } @@ -180,27 +261,57 @@ QPDFFormFieldObjectHelper::getValueAsString() return getInheritableFieldValueAsString("/V"); } +std::string +FormField::getValueAsString() +{ + return getInheritableFieldValueAsString("/V"); +} + QPDFObjectHandle QPDFFormFieldObjectHelper::getDefaultValue() { + return m->getDefaultValue(); +} + +QPDFObjectHandle +FormField::getDefaultValue() +{ return getInheritableFieldValue("/DV"); } std::string QPDFFormFieldObjectHelper::getDefaultValueAsString() { + return m->getDefaultValueAsString(); +} + +std::string +FormField::getDefaultValueAsString() +{ return getInheritableFieldValueAsString("/DV"); } QPDFObjectHandle QPDFFormFieldObjectHelper::getDefaultResources() { + return m->getDefaultResources(); +} + +QPDFObjectHandle +FormField::getDefaultResources() +{ return getFieldFromAcroForm("/DR"); } std::string QPDFFormFieldObjectHelper::getDefaultAppearance() { + return m->getDefaultAppearance(); +} + +std::string +FormField::getDefaultAppearance() +{ auto value = getInheritableFieldValue("/DA"); bool looked_in_acroform = false; if (!value.isString()) { @@ -217,6 +328,12 @@ QPDFFormFieldObjectHelper::getDefaultAppearance() int QPDFFormFieldObjectHelper::getQuadding() { + return m->getQuadding(); +} + +int +FormField::getQuadding() +{ QPDFObjectHandle fv = getInheritableFieldValue("/Q"); bool looked_in_acroform = false; if (!fv.isInteger()) { @@ -233,6 +350,12 @@ QPDFFormFieldObjectHelper::getQuadding() int QPDFFormFieldObjectHelper::getFlags() { + return m->getFlags(); +} + +int +FormField::getFlags() +{ QPDFObjectHandle f = getInheritableFieldValue("/Ff"); return f.isInteger() ? f.getIntValueAsInt() : 0; } @@ -240,42 +363,84 @@ QPDFFormFieldObjectHelper::getFlags() bool QPDFFormFieldObjectHelper::isText() { + return m->isText(); +} + +bool +FormField::isText() +{ return getFieldType() == "/Tx"; } bool QPDFFormFieldObjectHelper::isCheckbox() { + return m->isCheckbox(); +} + +bool +FormField::isCheckbox() +{ return getFieldType() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0; } bool QPDFFormFieldObjectHelper::isChecked() { + return m->isChecked(); +} + +bool +FormField::isChecked() +{ return isCheckbox() && Name(getValue()) != "/Off"; } bool QPDFFormFieldObjectHelper::isRadioButton() { + return m->isRadioButton(); +} + +bool +FormField::isRadioButton() +{ return getFieldType() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio; } bool QPDFFormFieldObjectHelper::isPushbutton() { + return m->isPushbutton(); +} + +bool +FormField::isPushbutton() +{ return getFieldType() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton; } bool QPDFFormFieldObjectHelper::isChoice() { + return m->isChoice(); +} + +bool +FormField::isChoice() +{ return getFieldType() == "/Ch"; } std::vector QPDFFormFieldObjectHelper::getChoices() { + return m->getChoices(); +} + +std::vector +FormField::getChoices() +{ if (!isChoice()) { return {}; } @@ -296,18 +461,36 @@ QPDFFormFieldObjectHelper::getChoices() void QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, QPDFObjectHandle value) { + m->setFieldAttribute(key, value); +} + +void +FormField::setFieldAttribute(std::string const& key, QPDFObjectHandle value) +{ oh().replaceKey(key, value); } void QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, std::string const& utf8_value) { + m->setFieldAttribute(key, utf8_value); +} + +void +FormField::setFieldAttribute(std::string const& key, std::string const& utf8_value) +{ oh().replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); } void QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) { + m->setV(value, need_appearances); +} + +void +FormField::setV(QPDFObjectHandle value, bool need_appearances) +{ Name name = value; if (getFieldType() == "/Btn") { if (isCheckbox()) { @@ -350,11 +533,17 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) void QPDFFormFieldObjectHelper::setV(std::string const& utf8_value, bool need_appearances) { + m->setV(utf8_value, need_appearances); +} + +void +FormField::setV(std::string const& utf8_value, bool need_appearances) +{ setV(QPDFObjectHandle::newUnicodeString(utf8_value), need_appearances); } void -QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name) +FormField::setRadioButtonValue(QPDFObjectHandle name) { // Set the value of a radio button field. This has the following specific behavior: // * If this is a radio button field that has a parent that is also a radio button field and has @@ -366,7 +555,7 @@ QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name) // Note that we never turn on /NeedAppearances when setting a radio button field. QPDFObjectHandle parent = oh().getKey("/Parent"); if (parent.isDictionary() && parent.getKey("/Parent").null()) { - QPDFFormFieldObjectHelper ph(parent); + FormField ph(parent); if (ph.isRadioButton()) { // This is most likely one of the individual buttons. Try calling on the parent. ph.setRadioButtonValue(name); @@ -409,7 +598,7 @@ QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name) } void -QPDFFormFieldObjectHelper::setCheckBoxValue(bool value) +FormField::setCheckBoxValue(bool value) { QPDFObjectHandle AP = oh().getKey("/AP"); QPDFObjectHandle annot; @@ -460,6 +649,12 @@ QPDFFormFieldObjectHelper::setCheckBoxValue(bool value) void QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper& aoh) { + m->generateAppearance(aoh); +} + +void +FormField::generateAppearance(QPDFAnnotationObjectHelper& aoh) +{ std::string ft = getFieldType(); // Ignore field types we don't know how to generate appearances for. Button fields don't really // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded. @@ -735,7 +930,7 @@ namespace } // namespace QPDFObjectHandle -QPDFFormFieldObjectHelper::getFontFromResource(QPDFObjectHandle resources, std::string const& name) +FormField::getFontFromResource(QPDFObjectHandle resources, std::string const& name) { QPDFObjectHandle result; if (resources.isDictionary() && resources.getKey("/Font").isDictionary() && @@ -746,7 +941,7 @@ QPDFFormFieldObjectHelper::getFontFromResource(QPDFObjectHandle resources, std:: } void -QPDFFormFieldObjectHelper::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) +FormField::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) { QPDFObjectHandle AS = aoh.getAppearanceStream("/N"); if (AS.null()) { diff --git a/libqpdf/qpdf/FormField.hh b/libqpdf/qpdf/FormField.hh new file mode 100644 index 0000000..af1b2d7 --- /dev/null +++ b/libqpdf/qpdf/FormField.hh @@ -0,0 +1,160 @@ +#ifndef FORMFIELD_HH +#define FORMFIELD_HH + +#include +#include + +#include + +class QPDFAnnotationObjectHelper; + +namespace qpdf::impl +{ + // This object helper helps with form fields for interactive forms. Please see comments in + // QPDFAcroFormDocumentHelper.hh for additional details. + class FormField: public QPDFObjectHelper + { + public: + FormField() = delete; + + FormField(QPDFObjectHandle const& oh) : + QPDFObjectHelper(oh) + { + } + + ~FormField() override = default; + + bool isNull(); + + // Return the field's parent. A form field object helper whose underlying object is null is + // returned if there is no parent. This condition may be tested by calling isNull(). + FormField getParent(); + + // Return the top-level field for this field. Typically this will be the field itself or its + // parent. If is_different is provided, it is set to true if the top-level field is + // different from the field itself; otherwise it is set to false. + FormField getTopLevelField(bool* is_different = nullptr); + + // Get a field value, possibly inheriting the value from an ancestor node. + QPDFObjectHandle getInheritableFieldValue(std::string const& name); + + // Get an inherited field value as a string. If it is not a string, silently return the + // empty string. + std::string getInheritableFieldValueAsString(std::string const& name); + + // Get an inherited field value of type name as a string representing the name. If it is not + // a name, silently return the empty string. + std::string getInheritableFieldValueAsName(std::string const& name); + + // Returns the value of /FT if present, otherwise returns the empty string. + std::string getFieldType(); + + std::string getFullyQualifiedName(); + + std::string getPartialName(); + + // Return the alternative field name (/TU), which is the field name intended to be presented + // to users. If not present, fall back to the fully qualified name. + std::string getAlternativeName(); + + // Return the mapping field name (/TM). If not present, fall back to the alternative name, + // then to the partial name. + std::string getMappingName(); + + QPDFObjectHandle getValue(); + + // Return the field's value as a string. If this is called with a field whose value is not a + std::string getValueAsString(); + + QPDFObjectHandle getDefaultValue(); + + // Return the field's default value as a string. If this is called with a field whose value + // is not a string, the empty string will be silently returned. + std::string getDefaultValueAsString(); + + // Return the default appearance string, taking inheritance from the field tree into + // account. Returns the empty string if the default appearance string is not available + // (because it's erroneously absent or because this is not a variable text field). If not + // found in the field hierarchy, look in /AcroForm. + std::string getDefaultAppearance(); + + // Return the default resource dictionary for the field. This comes not from the field but + // from the document-level /AcroForm dictionary. While several PDF generates put a /DR key + // in the form field's dictionary, experimentation suggests that many popular readers, + // including Adobe Acrobat and Acrobat Reader, ignore any /DR item on the field. + QPDFObjectHandle getDefaultResources(); + + // Return the quadding value, taking inheritance from the field tree into account. Returns 0 + // if quadding is not specified. Look in /AcroForm if not found in the field hierarchy. + int getQuadding(); + + // Return field flags from /Ff. The value is a logical or of pdf_form_field_flag_e as + // defined in qpdf/Constants.h// + int getFlags(); + + // Methods for testing for particular types of form fields + + // Returns true if field is of type /Tx + bool isText(); + // Returns true if field is of type /Btn and flags do not indicate some other type of + // button. + bool isCheckbox(); + + // Returns true if field is a checkbox and is checked. + bool isChecked(); + + // Returns true if field is of type /Btn and flags indicate that it is a radio button + bool isRadioButton(); + + // Returns true if field is of type /Btn and flags indicate that it is a pushbutton + bool isPushbutton(); + + // Returns true if fields if of type /Ch + bool isChoice(); + + // Returns choices display values as UTF-8 strings + std::vector getChoices(); + + // Set an attribute to the given value. If you have a QPDFAcroFormDocumentHelper and you + // want to set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName + // instead. + 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. If you have a QPDFAcroFormDocumentHelper and you want to + // set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName instead. + void setFieldAttribute(std::string const& key, std::string const& utf8_value); + + // Set /V (field value) to the given value. If need_appearances is true and the field type + // is either /Tx (text) or /Ch (choice), set /NeedAppearances to true. You can explicitly + // tell this method not to set /NeedAppearances if you are going to generate an appearance + // stream yourself. Starting with qpdf 8.3.0, this method handles fields of type /Btn + // (checkboxes, radio buttons, pushbuttons) specially. When setting a checkbox value, any + // value other than /Off will be treated as on, and the actual value set will be based on + // the appearance stream's /N dictionary, so the value that ends up in /V may not exactly + // match the value you pass in. + 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. + void setV(std::string const& utf8_value, bool need_appearances = true); + + // Update the appearance stream for this field. Note that qpdf's ability to generate + // appearance streams is limited. We only generate appearance streams for streams of type + // text or choice. The appearance uses the default parameters provided in the file, and it + // only supports ASCII characters. Quadding is currently ignored. While this functionality + // is limited, it should do a decent job on properly constructed PDF files when field values + // are restricted to ASCII characters. + void generateAppearance(QPDFAnnotationObjectHelper&); + + private: + QPDFObjectHandle getFieldFromAcroForm(std::string const& name); + void setRadioButtonValue(QPDFObjectHandle name); + void setCheckBoxValue(bool value); + void generateTextAppearance(QPDFAnnotationObjectHelper&); + QPDFObjectHandle + getFontFromResource(QPDFObjectHandle resources, std::string const& font_name); + }; +} // namespace qpdf::impl + +#endif // FORMFIELD_HH