diff --git a/include/qpdf/QPDFAcroFormDocumentHelper.hh b/include/qpdf/QPDFAcroFormDocumentHelper.hh index 3694681..ef5c6b1 100644 --- a/include/qpdf/QPDFAcroFormDocumentHelper.hh +++ b/include/qpdf/QPDFAcroFormDocumentHelper.hh @@ -225,21 +225,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper std::set* new_fields = nullptr); private: - void analyze(); - bool traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth); - QPDFObjectHandle getOrCreateAcroForm(); - void adjustInheritedFields( - QPDFObjectHandle obj, - bool override_da, - std::string const& from_default_da, - bool override_q, - int from_default_q); - void adjustDefaultAppearances( - QPDFObjectHandle obj, - std::map> const& dr_map); - void adjustAppearanceStream( - QPDFObjectHandle stream, std::map> dr_map); - + friend class QPDF::Doc; class Members; std::shared_ptr m; diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index d86f604..1b3e034 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -147,6 +148,21 @@ QPDF::QPDF() : m->unique_id = unique_id.fetch_add(1ULL); } +/// @brief Initializes the AcroForm functionality for the document. +/// @par +/// This method creates a unique instance of QPDFAcroFormDocumentHelper and associates it +/// with the document. It also updates the `acroform_` pointer to reference the AcroForm +/// instance managed by the helper. +/// +/// The method has been separated out from `acroform` to avoid it being inlined +/// unnecessarily. +void +QPDF::Doc::init_acroform() +{ + acroform_dh_ = std::make_unique(qpdf); + acroform_ = acroform_dh_->m.get(); +} + // Provide access to disconnect(). Disconnect will in due course be merged into the current ObjCache // (future Objects::Entry) to centralize all QPDF access to QPDFObject. class Disconnect: BaseHandle diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index 65f103c..c463b91 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -1,5 +1,7 @@ #include +#include + #include #include #include @@ -7,51 +9,38 @@ #include #include #include +#include #include #include using namespace qpdf; +using namespace qpdf::impl; using namespace std::literals; -class QPDFAcroFormDocumentHelper::Members -{ - public: - Members() = default; - Members(Members const&) = delete; - ~Members() = default; - - struct FieldData - { - std::vector annotations; - std::string name; - }; - - bool cache_valid{false}; - std::map field_to; - std::map annotation_to_field; - std::map> name_to_fields; - std::set bad_fields; -}; +using AcroForm = impl::AcroForm; QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) : QPDFDocumentHelper(qpdf), - m(std::make_shared()) + m(std::make_shared(qpdf)) { - // We have to analyze up front. Otherwise, when we are adding annotations and fields, we are in - // a temporarily unstable configuration where some widget annotations are not reachable. - analyze(); } QPDFAcroFormDocumentHelper& QPDFAcroFormDocumentHelper::get(QPDF& qpdf) { - return qpdf.doc().acroform(); + return qpdf.doc().acroform_dh(); } void QPDFAcroFormDocumentHelper::validate(bool repair) { + m->validate(repair); +} + +void +AcroForm::validate(bool repair) +{ invalidateCache(); analyze(); } @@ -59,19 +48,33 @@ QPDFAcroFormDocumentHelper::validate(bool repair) void QPDFAcroFormDocumentHelper::invalidateCache() { - m->cache_valid = false; - m->field_to.clear(); - m->annotation_to_field.clear(); + m->invalidateCache(); +} + +void +AcroForm::invalidateCache() +{ + cache_valid_ = false; + fields_.clear(); + annotation_to_field_.clear(); + bad_fields_.clear(); + name_to_fields_.clear(); } bool QPDFAcroFormDocumentHelper::hasAcroForm() { + return m->hasAcroForm(); +} + +bool +AcroForm::hasAcroForm() +{ return qpdf.getRoot().hasKey("/AcroForm"); } QPDFObjectHandle -QPDFAcroFormDocumentHelper::getOrCreateAcroForm() +AcroForm::getOrCreateAcroForm() { auto acroform = qpdf.getRoot().getKey("/AcroForm"); if (!acroform.isDictionary()) { @@ -84,6 +87,12 @@ QPDFAcroFormDocumentHelper::getOrCreateAcroForm() void QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) { + m->addFormField(ff); +} + +void +AcroForm::addFormField(QPDFFormFieldObjectHelper ff) +{ auto acroform = getOrCreateAcroForm(); auto fields = acroform.getKey("/Fields"); if (!fields.isArray()) { @@ -96,6 +105,12 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) void QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector fields) { + m->addAndRenameFormFields(fields); +} + +void +AcroForm::addAndRenameFormFields(std::vector fields) +{ analyze(); std::map renames; QPDFObjGen::set seen; @@ -147,6 +162,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector void QPDFAcroFormDocumentHelper::removeFormFields(std::set const& to_remove) { + m->removeFormFields(to_remove); +} + +void +AcroForm::removeFormFields(std::set const& to_remove) +{ auto acroform = qpdf.getRoot().getKey("/AcroForm"); if (!acroform.isDictionary()) { return; @@ -157,19 +178,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set const& to_remo } for (auto const& og: to_remove) { - auto it = m->field_to.find(og); - if (it != m->field_to.end()) { + auto it = fields_.find(og); + if (it != fields_.end()) { for (auto aoh: it->second.annotations) { - m->annotation_to_field.erase(aoh.getObjectHandle().getObjGen()); + annotation_to_field_.erase(aoh.getObjectHandle().getObjGen()); } auto const& name = it->second.name; if (!name.empty()) { - m->name_to_fields[name].erase(og); - if (m->name_to_fields[name].empty()) { - m->name_to_fields.erase(name); + name_to_fields_[name].erase(og); + if (name_to_fields_[name].empty()) { + name_to_fields_.erase(name); } } - m->field_to.erase(og); + fields_.erase(og); } } @@ -187,6 +208,12 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set const& to_remo void QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) { + m->setFormFieldName(ff, name); +} + +void +AcroForm::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) +{ ff.setFieldAttribute("/T", name); traverseField(ff, ff["/Parent"], 0); } @@ -194,9 +221,15 @@ QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std:: std::vector QPDFAcroFormDocumentHelper::getFormFields() { + return m->getFormFields(); +} + +std::vector +AcroForm::getFormFields() +{ analyze(); std::vector result; - for (auto const& [og, data]: m->field_to) { + for (auto const& [og, data]: fields_) { if (!data.annotations.empty()) { result.emplace_back(qpdf.getObject(og)); } @@ -207,10 +240,16 @@ QPDFAcroFormDocumentHelper::getFormFields() std::set QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) { + return m->getFieldsWithQualifiedName(name); +} + +std::set +AcroForm::getFieldsWithQualifiedName(std::string const& name) +{ analyze(); // Keep from creating an empty entry - auto iter = m->name_to_fields.find(name); - if (iter != m->name_to_fields.end()) { + auto iter = name_to_fields_.find(name); + if (iter != name_to_fields_.end()) { return iter->second; } return {}; @@ -219,11 +258,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) std::vector QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) { + return m->getAnnotationsForField(h); +} + +std::vector +AcroForm::getAnnotationsForField(QPDFFormFieldObjectHelper h) +{ analyze(); std::vector result; QPDFObjGen og(h.getObjectHandle().getObjGen()); - if (m->field_to.contains(og)) { - result = m->field_to[og].annotations; + if (fields_.contains(og)) { + result = fields_[og].annotations; } return result; } @@ -231,12 +276,24 @@ QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) std::vector QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) { + return m->getWidgetAnnotationsForPage(h); +} + +std::vector +AcroForm::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) +{ return h.getAnnotations("/Widget"); } std::vector QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) { + return m->getFormFieldsForPage(ph); +} + +std::vector +AcroForm::getFormFieldsForPage(QPDFPageObjectHelper ph) +{ analyze(); QPDFObjGen::set todo; std::vector result; @@ -252,25 +309,31 @@ QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) QPDFFormFieldObjectHelper QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) { + return m->getFieldForAnnotation(h); +} + +QPDFFormFieldObjectHelper +AcroForm::getFieldForAnnotation(QPDFAnnotationObjectHelper h) +{ QPDFObjectHandle oh = h.getObjectHandle(); if (!oh.isDictionaryOfType("", "/Widget")) { return Null::temp(); } analyze(); QPDFObjGen og(oh.getObjGen()); - if (m->annotation_to_field.contains(og)) { - return m->annotation_to_field[og]; + if (annotation_to_field_.contains(og)) { + return annotation_to_field_[og]; } return Null::temp(); } void -QPDFAcroFormDocumentHelper::analyze() +AcroForm::analyze() { - if (m->cache_valid) { + if (cache_valid_) { return; } - m->cache_valid = true; + cache_valid_ = true; QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); if (!(acroform.isDictionary() && acroform.hasKey("/Fields"))) { return; @@ -293,11 +356,11 @@ QPDFAcroFormDocumentHelper::analyze() // a file that contains this kind of error will probably not // actually work with most viewers. - for (auto const& ph: QPDFPageDocumentHelper(qpdf).getAllPages()) { + for (QPDFPageObjectHelper ph: pages) { for (auto const& iter: getWidgetAnnotationsForPage(ph)) { QPDFObjectHandle annot(iter.getObjectHandle()); QPDFObjGen og(annot.getObjGen()); - if (!m->annotation_to_field.contains(og)) { + if (!annotation_to_field_.contains(og)) { // This is not supposed to happen, but it's easy enough for us to handle this case. // Treat the annotation as its own field. This could allow qpdf to sensibly handle a // case such as a PDF creator adding a self-contained annotation (merged with the @@ -306,16 +369,15 @@ QPDFAcroFormDocumentHelper::analyze() annot.warn( "this widget annotation is not reachable from /AcroForm in the document " "catalog"); - m->annotation_to_field[og] = QPDFFormFieldObjectHelper(annot); - m->field_to[og].annotations.emplace_back(annot); + annotation_to_field_[og] = QPDFFormFieldObjectHelper(annot); + fields_[og].annotations.emplace_back(annot); } } } } bool -QPDFAcroFormDocumentHelper::traverseField( - QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth) +AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth) { if (depth > 100) { // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that @@ -339,8 +401,7 @@ QPDFAcroFormDocumentHelper::traverseField( return false; } QPDFObjGen og(field.getObjGen()); - if (m->field_to.contains(og) || m->annotation_to_field.contains(og) || - m->bad_fields.contains(og)) { + if (fields_.contains(og) || annotation_to_field_.contains(og) || bad_fields_.contains(og)) { field.warn("loop detected while traversing /AcroForm"); return false; } @@ -368,8 +429,8 @@ QPDFAcroFormDocumentHelper::traverseField( if (is_annotation) { QPDFObjectHandle our_field = (is_field ? field : parent); - m->field_to[our_field.getObjGen()].annotations.emplace_back(field); - m->annotation_to_field[og] = QPDFFormFieldObjectHelper(our_field); + fields_[our_field.getObjGen()].annotations.emplace_back(field); + annotation_to_field_[og] = QPDFFormFieldObjectHelper(our_field); } if (is_field && depth != 0 && field["/Parent"] != parent) { @@ -392,22 +453,22 @@ QPDFAcroFormDocumentHelper::traverseField( if (is_field && field.hasKey("/T")) { QPDFFormFieldObjectHelper foh(field); std::string name = foh.getFullyQualifiedName(); - auto old = m->field_to.find(og); - if (old != m->field_to.end() && !old->second.name.empty()) { + auto old = fields_.find(og); + if (old != fields_.end() && !old->second.name.empty()) { // We might be updating after a name change, so remove any old information - m->name_to_fields[old->second.name].erase(og); + name_to_fields_[old->second.name].erase(og); } - m->field_to[og].name = name; - m->name_to_fields[name].insert(og); + fields_[og].name = name; + name_to_fields_[name].insert(og); } for (auto const& kid: Kids) { - if (m->bad_fields.contains(kid)) { + if (bad_fields_.contains(kid)) { continue; } if (!traverseField(kid, field, 1 + depth)) { - m->bad_fields.insert(kid); + bad_fields_.insert(kid); } } return true; @@ -416,6 +477,12 @@ QPDFAcroFormDocumentHelper::traverseField( bool QPDFAcroFormDocumentHelper::getNeedAppearances() { + return m->getNeedAppearances(); +} + +bool +AcroForm::getNeedAppearances() +{ bool result = false; QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); if (acroform.isDictionary() && acroform.getKey("/NeedAppearances").isBool()) { @@ -427,6 +494,12 @@ QPDFAcroFormDocumentHelper::getNeedAppearances() void QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) { + m->setNeedAppearances(val); +} + +void +AcroForm::setNeedAppearances(bool val) +{ QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); if (!acroform.isDictionary()) { qpdf.getRoot().warn( @@ -444,6 +517,12 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) void QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() { + m->generateAppearancesIfNeeded(); +} + +void +AcroForm::generateAppearancesIfNeeded() +{ if (!getNeedAppearances()) { return; } @@ -470,6 +549,12 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() void QPDFAcroFormDocumentHelper::disableDigitalSignatures() { + m->disableDigitalSignatures(); +} + +void +AcroForm::disableDigitalSignatures() +{ qpdf.removeSecurityRestrictions(); std::set to_remove; auto fields = getFormFields(); @@ -491,7 +576,7 @@ QPDFAcroFormDocumentHelper::disableDigitalSignatures() } void -QPDFAcroFormDocumentHelper::adjustInheritedFields( +AcroForm::adjustInheritedFields( QPDFObjectHandle obj, bool override_da, std::string const& from_default_da, @@ -598,7 +683,7 @@ ResourceReplacer::handleToken(QPDFTokenizer::Token const& token) } void -QPDFAcroFormDocumentHelper::adjustDefaultAppearances( +AcroForm::adjustDefaultAppearances( QPDFObjectHandle obj, std::map> const& dr_map) { // This method is called on a field that has been copied from another file but whose /DA still @@ -656,7 +741,7 @@ QPDFAcroFormDocumentHelper::adjustDefaultAppearances( } void -QPDFAcroFormDocumentHelper::adjustAppearanceStream( +AcroForm::adjustAppearanceStream( QPDFObjectHandle stream, std::map> dr_map) { // We don't have to modify appearance streams or their resource dictionaries for them to display @@ -748,7 +833,6 @@ QPDFAcroFormDocumentHelper::transformAnnotations( QPDF* from_qpdf, QPDFAcroFormDocumentHelper* from_afdh) { - Array old_annots = std::move(a_old_annots); if (!from_qpdf) { // Assume these are from the same QPDF. from_qpdf = &qpdf; @@ -756,6 +840,23 @@ QPDFAcroFormDocumentHelper::transformAnnotations( } else if (from_qpdf != &qpdf && !from_afdh) { from_afdh = &QPDFAcroFormDocumentHelper::get(*from_qpdf); } + m->transformAnnotations( + a_old_annots, new_annots, new_fields, old_fields, cm, from_qpdf, from_afdh->m.get()); +} + +void +AcroForm::transformAnnotations( + QPDFObjectHandle a_old_annots, + std::vector& new_annots, + std::vector& new_fields, + std::set& old_fields, + QPDFMatrix const& cm, + QPDF* from_qpdf, + AcroForm* from_afdh) +{ + qpdf_expect(from_qpdf); + qpdf_expect(from_afdh); + Array old_annots = std::move(a_old_annots); const bool foreign = from_qpdf != &qpdf; // It's possible that we will transform annotations that don't include any form fields. This @@ -1056,6 +1157,16 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( QPDFAcroFormDocumentHelper& from_afdh, std::set* added_fields) { + m->fixCopiedAnnotations(to_page, from_page, *from_afdh.m, added_fields); +} + +void +AcroForm::fixCopiedAnnotations( + QPDFObjectHandle to_page, + QPDFObjectHandle from_page, + AcroForm& from_afdh, + std::set* added_fields) +{ auto old_annots = from_page.getKey("/Annots"); if (old_annots.empty() || !old_annots.isArray()) { return; @@ -1070,7 +1181,7 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( new_fields, old_fields, QPDFMatrix(), - &(from_afdh.getQPDF()), + &(from_afdh.qpdf), &from_afdh); to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); diff --git a/libqpdf/QPDFFormFieldObjectHelper.cc b/libqpdf/QPDFFormFieldObjectHelper.cc index e1662e0..58fbece 100644 --- a/libqpdf/QPDFFormFieldObjectHelper.cc +++ b/libqpdf/QPDFFormFieldObjectHelper.cc @@ -1,6 +1,6 @@ #include -#include +#include #include #include @@ -16,15 +16,15 @@ using namespace qpdf; -using FormField = qpdf::impl::FormField; +using FormNode = qpdf::impl::FormNode; -const QPDFObjectHandle FormField::null_oh; +const QPDFObjectHandle FormNode::null_oh; -class QPDFFormFieldObjectHelper::Members: public FormField +class QPDFFormFieldObjectHelper::Members: public FormNode { public: Members(QPDFObjectHandle const& oh) : - FormField(oh) + FormNode(oh) { } }; @@ -59,8 +59,8 @@ QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) return Null::if_null(m->root_field(is_different).oh()); } -FormField -FormField::root_field(bool* is_different) +FormNode +FormNode::root_field(bool* is_different) { if (is_different) { *is_different = false; @@ -87,7 +87,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) } QPDFObjectHandle const& -FormField::inherited(std::string const& name, bool acroform) const +FormNode::inherited(std::string const& name, bool acroform) const { if (!obj) { return null_oh; @@ -111,7 +111,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& n } std::string -FormField::inheritable_string(std::string const& name) const +FormNode::inheritable_string(std::string const& name) const { if (auto fv = inheritable_value(name)) { return fv.utf8_value(); @@ -144,7 +144,7 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() } std::string -FormField::fully_qualified_name() const +FormNode::fully_qualified_name() const { std::string result; auto node = *this; @@ -169,7 +169,7 @@ QPDFFormFieldObjectHelper::getPartialName() } std::string -FormField::partial_name() const +FormNode::partial_name() const { if (auto pn = T()) { return pn.utf8_value(); @@ -184,7 +184,7 @@ QPDFFormFieldObjectHelper::getAlternativeName() } std::string -FormField::alternative_name() const +FormNode::alternative_name() const { if (auto an = TU()) { return an.utf8_value(); @@ -199,7 +199,7 @@ QPDFFormFieldObjectHelper::getMappingName() } std::string -FormField::mapping_name() const +FormNode::mapping_name() const { if (auto mn = TM()) { return mn.utf8_value(); @@ -220,7 +220,7 @@ QPDFFormFieldObjectHelper::getValueAsString() } std::string -FormField::value() const +FormNode::value() const { return inheritable_string("/V"); } @@ -238,7 +238,7 @@ QPDFFormFieldObjectHelper::getDefaultValueAsString() } std::string -FormField::default_value() const +FormNode::default_value() const { return inheritable_string("/DV"); } @@ -250,7 +250,7 @@ QPDFFormFieldObjectHelper::getDefaultResources() } QPDFObjectHandle -FormField::getDefaultResources() +FormNode::getDefaultResources() { return from_AcroForm("/DR"); } @@ -262,7 +262,7 @@ QPDFFormFieldObjectHelper::getDefaultAppearance() } std::string -FormField::default_appearance() const +FormNode::default_appearance() const { if (auto DA = inheritable_value("/DA")) { return DA.utf8_value(); @@ -280,7 +280,7 @@ QPDFFormFieldObjectHelper::getQuadding() } int -FormField::getQuadding() +FormNode::getQuadding() { auto fv = inheritable_value("/Q"); bool looked_in_acroform = false; @@ -302,7 +302,7 @@ QPDFFormFieldObjectHelper::getFlags() } int -FormField::getFlags() +FormNode::getFlags() { auto f = inheritable_value("/Ff"); return f.isInteger() ? f.getIntValueAsInt() : 0; @@ -315,7 +315,7 @@ QPDFFormFieldObjectHelper::isText() } bool -FormField::isText() +FormNode::isText() { return FT() == "/Tx"; } @@ -327,7 +327,7 @@ QPDFFormFieldObjectHelper::isCheckbox() } bool -FormField::isCheckbox() +FormNode::isCheckbox() { return FT() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0; } @@ -339,7 +339,7 @@ QPDFFormFieldObjectHelper::isChecked() } bool -FormField::isChecked() +FormNode::isChecked() { return isCheckbox() && V() != "/Off"; } @@ -351,7 +351,7 @@ QPDFFormFieldObjectHelper::isRadioButton() } bool -FormField::isRadioButton() +FormNode::isRadioButton() { return FT() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio; } @@ -363,7 +363,7 @@ QPDFFormFieldObjectHelper::isPushbutton() } bool -FormField::isPushbutton() +FormNode::isPushbutton() { return FT() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton; } @@ -375,7 +375,7 @@ QPDFFormFieldObjectHelper::isChoice() } bool -FormField::isChoice() +FormNode::isChoice() { return FT() == "/Ch"; } @@ -387,7 +387,7 @@ QPDFFormFieldObjectHelper::getChoices() } std::vector -FormField::getChoices() +FormNode::getChoices() { if (!isChoice()) { return {}; @@ -413,7 +413,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, QPDFObjectH } void -FormField::setFieldAttribute(std::string const& key, QPDFObjectHandle value) +FormNode::setFieldAttribute(std::string const& key, QPDFObjectHandle value) { oh().replaceKey(key, value); } @@ -425,7 +425,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, std::string } void -FormField::setFieldAttribute(std::string const& key, std::string const& utf8_value) +FormNode::setFieldAttribute(std::string const& key, std::string const& utf8_value) { oh().replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); } @@ -437,7 +437,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) } void -FormField::setV(QPDFObjectHandle value, bool need_appearances) +FormNode::setV(QPDFObjectHandle value, bool need_appearances) { Name name = value; if (FT() == "/Btn") { @@ -485,13 +485,13 @@ QPDFFormFieldObjectHelper::setV(std::string const& utf8_value, bool need_appeara } void -FormField::setV(std::string const& utf8_value, bool need_appearances) +FormNode::setV(std::string const& utf8_value, bool need_appearances) { setV(QPDFObjectHandle::newUnicodeString(utf8_value), need_appearances); } void -FormField::setRadioButtonValue(QPDFObjectHandle name) +FormNode::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 @@ -503,7 +503,7 @@ FormField::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()) { - FormField ph(parent); + FormNode ph(parent); if (ph.isRadioButton()) { // This is most likely one of the individual buttons. Try calling on the parent. ph.setRadioButtonValue(name); @@ -546,7 +546,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name) } void -FormField::setCheckBoxValue(bool value) +FormNode::setCheckBoxValue(bool value) { QPDFObjectHandle AP = oh().getKey("/AP"); QPDFObjectHandle annot; @@ -601,7 +601,7 @@ QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper& aoh) } void -FormField::generateAppearance(QPDFAnnotationObjectHelper& aoh) +FormNode::generateAppearance(QPDFAnnotationObjectHelper& aoh) { // Ignore field types we don't know how to generate appearances for. Button fields don't really // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded. @@ -877,7 +877,7 @@ namespace } // namespace QPDFObjectHandle -FormField::getFontFromResource(QPDFObjectHandle resources, std::string const& name) +FormNode::getFontFromResource(QPDFObjectHandle resources, std::string const& name) { QPDFObjectHandle result; if (resources.isDictionary() && resources.getKey("/Font").isDictionary() && @@ -888,7 +888,7 @@ FormField::getFontFromResource(QPDFObjectHandle resources, std::string const& na } void -FormField::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) +FormNode::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) { QPDFObjectHandle AS = aoh.getAppearanceStream("/N"); if (AS.null()) { diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index 8820423..e1141a3 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -1866,7 +1867,7 @@ QPDFJob::doUnderOverlayForPage( if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) { return ""; } - auto& dest_afdh = dest_page.qpdf()->doc().acroform(); + auto& dest_afdh = dest_page.qpdf()->doc().acroform_dh(); auto const& pages = uo.pdf->doc().pages().all(); std::string content; @@ -1887,7 +1888,8 @@ QPDFJob::doUnderOverlayForPage( QPDFMatrix cm; std::string new_content = dest_page.placeFormXObject( fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); - dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->doc().acroform()); + dest_page.copyAnnotations( + from_page, cm, &dest_afdh, &from_page.qpdf()->doc().acroform_dh()); if (!new_content.empty()) { resources.mergeResources(Dictionary({{"/XObject", Dictionary::empty()}})); auto xobject = resources.getKey("/XObject"); @@ -2107,7 +2109,7 @@ QPDFJob::handleTransformations(QPDF& pdf) QPDFAcroFormDocumentHelper* afdh_ptr = nullptr; auto afdh = [&]() -> QPDFAcroFormDocumentHelper& { if (!afdh_ptr) { - afdh_ptr = &pdf.doc().acroform(); + afdh_ptr = &pdf.doc().acroform_dh(); } return *afdh_ptr; }; @@ -2978,8 +2980,7 @@ QPDFJob::doSplitPages(QPDF& pdf) QPDF outpdf; outpdf.doc().config(m->d_cfg); outpdf.emptyPDF(); - QPDFAcroFormDocumentHelper* out_afdh = - afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr; + impl::AcroForm* out_afdh = afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr; for (size_t pageno = first; pageno <= last; ++pageno) { QPDFObjectHandle page = pages.at(pageno - 1); outpdf.addPage(page, false); diff --git a/libqpdf/QPDF_pages.cc b/libqpdf/QPDF_pages.cc index 021c42b..fdf4b68 100644 --- a/libqpdf/QPDF_pages.cc +++ b/libqpdf/QPDF_pages.cc @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include #include @@ -660,7 +660,7 @@ void Pages::flatten_annotations_for_page( QPDFPageObjectHelper& page, QPDFObjectHandle& resources, - QPDFAcroFormDocumentHelper& afdh, + impl::AcroForm& afdh, int required_flags, int forbidden_flags) { diff --git a/libqpdf/qpdf/AcroForm.hh b/libqpdf/qpdf/AcroForm.hh new file mode 100644 index 0000000..955e125 --- /dev/null +++ b/libqpdf/qpdf/AcroForm.hh @@ -0,0 +1,723 @@ +#ifndef ACRO_FORM_HH +#define ACRO_FORM_HH + +#include +#include +#include + +#include + +class QPDFAnnotationObjectHelper; + +namespace qpdf::impl +{ + /// @class AcroForm + /// @brief Represents the interactive form dictionary and the interactive form tree within a + /// PDF document. + /// @par + /// The AcroForm class deals with interactive forms defined in section 12.7 of the PDF + /// specification. This defines a tree structure consisting of an interactive form or + /// `/AcroForm` dictionary (section 12.7.3) at its root. The attributes of the + /// `/AcroForm` dictionary are defined in table 224 of the PDF 2.0 / table 220 of the + /// PDF 1.7 specification. + /// @par + /// The nodes of the interactive forms tree are represented by the FormNode class. + /// + /// @since 12.3 + class AcroForm: public Doc::Common + { + public: + AcroForm() = delete; + AcroForm(AcroForm const&) = delete; + AcroForm(AcroForm&&) = delete; + AcroForm& operator=(AcroForm const&) = delete; + AcroForm& operator=(AcroForm&&) = delete; + ~AcroForm() = default; + + AcroForm(impl::Doc& doc) : + Common(doc) + { + // We have to analyze up front. Otherwise, when we are adding annotations and fields, we + // are in a temporarily unstable configuration where some widget annotations are not + // reachable. + validate(); + } + + // Re-validate the AcroForm structure. This is useful if you have modified the structure of + // the AcroForm dictionary in a way that would invalidate the cache. + // + // If repair is true, the document will be repaired if possible if the validation encounters + // errors. + void validate(bool repair = true); + + // This class lazily creates an internal cache of the mapping among form fields, + // annotations, and pages. Methods within this class preserve the validity of this cache. + // However, if you modify pages' annotation dictionaries, the document's /AcroForm + // dictionary, or any form fields manually in a way that alters the association between + // forms, fields, annotations, and pages, it may cause this cache to become invalid. This + // method marks the cache invalid and forces it to be regenerated the next time it is + // needed. + void invalidateCache(); + + bool hasAcroForm(); + + // Add a form field, initializing the document's AcroForm dictionary if needed, updating the + // cache if necessary. Note that if you are adding fields that are copies of other fields, + // this method may result in multiple fields existing with the same qualified name, which + // can have unexpected side effects. In that case, you should use addAndRenameFormFields() + // instead. + void addFormField(QPDFFormFieldObjectHelper); + + // Add a collection of form fields making sure that their fully qualified names don't + // conflict with already present form fields. Fields within the collection of new fields + // that have the same name as each other will continue to do so. + void addAndRenameFormFields(std::vector fields); + + // Remove fields from the fields array + void removeFormFields(std::set const&); + + // Set the name of a field, updating internal records of field names. Name should be UTF-8 + // encoded. + void setFormFieldName(QPDFFormFieldObjectHelper, std::string const& name); + + // Return a vector of all terminal fields in a document. Terminal fields are fields that + // have no children that are also fields. Terminal fields may still have children that are + // annotations. Intermediate nodes in the fields tree are not included in this list, but you + // can still reach them through the getParent method of the field object helper. + std::vector getFormFields(); + + // Return all the form fields that have the given fully-qualified name and also have an + // explicit "/T" attribute. For this information to be accurate, any changes to field names + // must be done through setFormFieldName() above. + std::set getFieldsWithQualifiedName(std::string const& name); + + // Return the annotations associated with a terminal field. Note that in the case of a field + // having a single annotation, the underlying object will typically be the same as the + // underlying object for the field. + std::vector getAnnotationsForField(QPDFFormFieldObjectHelper); + + /// Retrieves a list of widget annotations for the specified page. + /// + /// A widget annotation represents the visual part of a form field in a PDF. + /// This function filters annotations on the given page, returning only those + /// annotations whose subtype is "/Widget". + /// + /// @param page A `QPDFPageObjectHelper` representing the page from which to + /// extract widget annotations. + /// + /// @return A vector of `QPDFAnnotationObjectHelper` objects corresponding to + /// the widget annotations found on the specified page. + std::vector + getWidgetAnnotationsForPage(QPDFPageObjectHelper page); + + // Return top-level form fields for a page. + std::vector getFormFieldsForPage(QPDFPageObjectHelper); + + // Return the terminal field that is associated with this annotation. If the annotation + // dictionary is merged with the field dictionary, the underlying object will be the same, + // but this is not always the case. Note that if you call this method with an annotation + // that is not a widget annotation, there will not be an associated field, and this method + // will return a helper associated with a null object (isNull() == true). + 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. + 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. + void setNeedAppearances(bool); + + // If /NeedAppearances is false, do nothing. Otherwise generate appearance streams for all + // widget annotations that need them. See comments in QPDFFormFieldObjectHelper.hh for + // generateAppearance for limitations. For checkbox and radio button fields, this code + // ensures that appearance state is consistent with the field's value and uses any + // pre-existing appearance streams. + void generateAppearancesIfNeeded(); + + // Disable Digital Signature Fields. Remove all digital signature fields from the document, + // leaving any annotation showing the content of the field intact. This also calls + // QPDF::removeSecurityRestrictions. + void disableDigitalSignatures(); + + // Note: this method works on all annotations, not just ones with associated fields. For + // each annotation in old_annots, apply the given transformation matrix to create a new + // annotation. New annotations are appended to new_annots. If the annotation is associated + // with a form field, a new form field is created that points to the new annotation and is + // appended to new_fields, and the old field is added to old_fields. + // + // old_annots may belong to a different QPDF object. In that case, you should pass in + // from_qpdf, and copyForeignObject will be called automatically. If this is the case, for + // efficiency, you may pass in a QPDFAcroFormDocumentHelper for the other file to avoid the + // expensive process of creating one for each call to transformAnnotations. New fields and + // annotations are not added to the document or pages. You have to do that yourself after + // calling transformAnnotations. If this operation will leave orphaned fields behind, such + // as if you are replacing the old annotations with the new ones on the same page and the + // fields and annotations are not shared, you will also need to remove the old fields to + // prevent them from hanging around unreferenced. + void transformAnnotations( + QPDFObjectHandle old_annots, + std::vector& new_annots, + std::vector& new_fields, + std::set& old_fields, + QPDFMatrix const& cm, + QPDF* from_qpdf, + AcroForm* from_afdh); + + // Copy form fields and annotations from one page to another, allowing the from page to be + // in a different QPDF or in the same QPDF. This would typically be called after calling + // addPage to add field/annotation awareness. When just copying the page by itself, + // annotations end up being shared, and fields end up being omitted because there is no + // reference to the field from the page. This method ensures that each separate copy of a + // page has private annotations and that fields and annotations are properly updated to + // resolve conflicts that may occur from common resource and field names across documents. + // It is basically a wrapper around transformAnnotations that handles updating the receiving + // page. If new_fields is non-null, any newly created fields are added to it. + void fixCopiedAnnotations( + QPDFObjectHandle to_page, + QPDFObjectHandle from_page, + AcroForm& from_afdh, + std::set* new_fields = nullptr); + + private: + struct FieldData + { + std::vector annotations; + std::string name; + }; + + /// Analyzes the AcroForm structure in the PDF document and updates the internal + /// cache with the form fields and their corresponding widget annotations. + /// + /// The function performs the following steps: + /// - Checks if the cache is valid. If it is, the function exits early. + /// - Retrieves the `/AcroForm` dictionary from the PDF and checks if it contains + /// a `/Fields` key. + /// - If `/Fields` exist and is an array, iterates through the fields and traverses + /// them to map annotations bidirectionally to form fields. + /// - Logs a warning if the `/Fields` key is present but not an array, and initializes + /// it to an empty array. + /// - Ensures that all widget annotations are processed, including any annotations + /// that might not be reachable from the `/AcroForm`. Treats such annotations as + /// their own fields. + /// - Provides a workaround for PDF documents containing inconsistencies, such as + /// widget annotations on a page not being referenced in `/AcroForm`. + /// + /// This function allows precise navigation and manipulation of form fields and + /// their related annotations, facilitating advanced PDF document processing. + void analyze(); + + /// Recursively traverses the structure of form fields and annotations in a PDF's /AcroForm. + /// + /// The method is designed to process form fields in a hierarchical /AcroForm structure. + /// It captures field and annotation data, resolves parent-child relationships, detects + /// loops, and avoids stack overflow from excessive recursion depth. + /// + /// @param field The current field or annotation to process. + /// @param parent The parent field object. If the current field is a top-level field, parent + /// will be a null object. + /// @param depth The current recursion depth to limit stack usage and avoid infinite loops. + /// + /// @return True if the field was processed successfully, false otherwise. + /// + /// - Recursion is limited to a depth of 100 to prevent stack overflow with maliciously + /// crafted files. + /// - The function skips non-indirect and invalid objects (e.g., non-dictionaries or objects + /// with invalid parent references). + /// - Detects and warns about loops in the /AcroForm hierarchy. + /// - Differentiates between terminal fields, annotations, and composite fields based on + /// dictionary keys. + /// - Tracks processed fields and annotations using internal maps to prevent reprocessing + /// and detect loops. + /// - Updates name-to-field mappings for terminal fields with a valid fully qualified name. + /// - Ensures the integrity of parent-child relationships within the field hierarchy. + /// - Any invalid child objects are logged and skipped during traversal. + bool traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth); + + /// Retrieves or creates the /AcroForm dictionary in the PDF document's root. + /// + /// - If the /AcroForm key exists in the document root and is a dictionary, + /// it is returned as is. + /// - If the /AcroForm key does not exist or is not a dictionary, a new + /// dictionary is created, stored as the /AcroForm entry in the document root, + /// and then returned. + /// + /// @return A QPDFObjectHandle representing the /AcroForm dictionary. + QPDFObjectHandle getOrCreateAcroForm(); + + /// Adjusts inherited field properties for an AcroForm field object. + /// + /// This method ensures that the `/DA` (default appearance) and `/Q` (quadding) keys + /// of the specified field object are overridden if necessary, based on the provided + /// parameters. The overriding is performed only if the respective `override_da` or + /// `override_q` flags are set to true, and when the original object's values differ from + /// the provided defaults. No changes are made to fields that have explicit values for `/DA` + /// or `/Q`. + /// + /// The function is primarily used for adjusting inherited form field properties in cases + /// where the document structure or inherited values have changed (e.g., when working with + /// fields in a PDF document). + /// + /// @param obj The `QPDFObjectHandle` instance representing the form field object to be + /// adjusted. + /// @param override_da A boolean flag indicating whether to override the `/DA` key. + /// @param from_default_da The default appearance string to apply if overriding the `/DA` + /// key. + /// @param override_q A boolean flag indicating whether to override the `/Q` key. + /// @param from_default_q The default quadding value (alignment) to apply if overriding the + /// `/Q` key. + void adjustInheritedFields( + QPDFObjectHandle obj, + bool override_da, + std::string const& from_default_da, + bool override_q, + int from_default_q); + + /// Adjusts the default appearances (/DA) of an AcroForm field object. + /// + /// This method ensures that form fields copied from another PDF document + /// have their default appearances resource references updated to correctly + /// point to the appropriate resources in the current document's resource + /// dictionary (/DR). It resolves name conflicts between the dictionaries + /// of the source and destination documents by using a mapping provided in + /// `dr_map`. + /// + /// The method parses the /DA string, processes its resource references, + /// and regenerates the /DA with updated references. + /// + /// @param obj The AcroForm field object whose /DA is being adjusted. + /// @param dr_map A mapping between resource names in the source document's + /// resource dictionary and their corresponding names in the current + /// document's resource dictionary. + void adjustDefaultAppearances( + QPDFObjectHandle obj, + std::map> const& dr_map); + + /// Modifies the appearance stream of an AcroForm field to ensure its resources + /// align with the resource dictionary and appearance settings. This method + /// ensures proper resource handling to avoid any conflicts when regenerating + /// the appearance stream. + /// + /// Adjustments include: + /// - Creating a private resource dictionary for the stream if not already present. + /// - Merging top-level resource keys into the stream's resource dictionary. + /// - Resolving naming conflicts between existing and remapped resource keys. + /// - Removing empty sub-dictionaries from the resource dictionary. + /// - Attaching a token filter to rewrite resource references in the stream content. + /// + /// If conflicts between keys are encountered or the stream cannot be parsed successfully, + /// appropriate warnings will be generated instead of halting execution. + /// + /// @param stream The QPDFObjectHandle representation of the PDF appearance stream to be + /// adjusted. + /// @param dr_map A mapping of resource types and their corresponding name remappings + /// used for resolving resource conflicts and regenerating appearances. + void adjustAppearanceStream( + QPDFObjectHandle stream, + std::map> dr_map); + + std::map fields_; + std::map annotation_to_field_; + std::map> name_to_fields_; + std::set bad_fields_; + bool cache_valid_{false}; + + }; // class Acroform + + /// @class FormNode + /// @brief Represents a node in the interactive forms tree of a PDF document. + /// + /// This class models nodes that may be either form field dictionaries or widget annotation + /// dictionaries, as defined in the PDF specification (sections 12.7 and 12.5.6.19). + /// + /// For a detailed description of the attributes that this class can expose, refer to the + /// corresponding tables in the PDF 2.0 (Table 226) or PDF 1.7 (Table 220) specifications. + class FormNode: public qpdf::BaseDictionary + { + public: + FormNode() = default; + FormNode(FormNode const&) = default; + FormNode& operator=(FormNode const&) = default; + FormNode(FormNode&&) = default; + FormNode& operator=(FormNode&&) = default; + ~FormNode() = default; + + FormNode(QPDFObjectHandle const& oh) : + BaseDictionary(oh) + { + } + + FormNode(QPDFObjectHandle&& oh) : + BaseDictionary(std::move(oh)) + { + } + + /// Retrieves the /Parent form field of the current field. + /// + /// This function accesses the parent field in the hierarchical structure of form fields, if + /// it exists. The parent is determined based on the /Parent attribute in the field + /// dictionary. + /// + /// @return A FormNode object representing the parent field. If the current field has no + /// parent, an empty FormNode object is returned. + FormNode + Parent() + { + return {get("/Parent")}; + } + + /// @brief Returns the top-level field associated with the current field. + /// + /// The function traverses the hierarchy of parent fields to identify the highest-level + /// field in the tree. Typically, this will be the current field itself unless it has a + /// parent field. Optionally, it can indicate whether the top-level field is different from + /// the current field. + /// + /// @param is_different A pointer to a boolean that, if provided, will be set to true if the + /// top-level field differs from the current field; otherwise, it will be set to + /// false. + /// + /// @return The top-level field in the form field hierarchy. + FormNode root_field(bool* is_different = nullptr); + + /// @brief Retrieves the inherited value of the specified attribute. + /// + /// @param name The name of the attribute to retrieve. + /// @param acroform If true, checks the document's /AcroForm dictionary for the attribute + /// if it is not found in the field hierarchy. + /// + /// @return A constant reference to the QPDFObjectHandle representing the value of the + /// specified attribute, if found. If the attribute is not found in the field + /// hierarchy or the /AcroForm dictionary (when `acroform` is true), returns a + /// reference to a static null object handle. + QPDFObjectHandle const& inherited(std::string const& name, bool acroform = false) const; + + /// @brief Retrieves the value of a specified field, accounting for inheritance through the + /// hierarchy of ancestor nodes in the form field tree. + /// + /// This function attempts to retrieve the value of the specified field. If the `inherit` + /// parameter is set to `true` and the field value is not found at the current level, the + /// method traverses up the parent hierarchy to find the value. The traversal stops when a + /// value is found, when the root node is reached, or when a loop detection mechanism + /// prevents further traversal. + /// + /// @tparam T The return type of the field value. + /// @param name The name of the field to retrieve the value for. + /// @param inherit If set to `true`, the function will attempt to retrieve the value by + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. + /// @return Returns the field's value if found; otherwise, returns a default-constructed + /// value of type `T`. + template + T + inheritable_value(std::string const& name, bool inherit = true, bool acroform = false) const + { + if (auto& v = get(name)) { + return {v}; + } + return {inherit ? inherited(name, acroform) : null_oh}; + } + + /// @brief Retrieves an inherited field string attribute as a string. + /// + /// @param name The name of the field for which the value is to be retrieved. + /// @return The inherited field value as a UTF-8 encoded string, or an empty string if the + /// value does not exist or is not of String type. + std::string inheritable_string(std::string const& name) const; + + /// @brief Retrieves the field type (/FT attribute). + /// + /// @param inherit If set to `true`, the function will attempt to retrieve the value by + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. + /// @return Returns the field type if found; otherwise, returns a default-constructed + /// `Name`. + Name + FT(bool inherit = true) const + { + return inheritable_value("/FT"); + } + + /// @brief Retrieves the partial field name (/T attribute). + /// + /// @return Returns the partial field name if found; otherwise, returns a + /// default-constructed `String`. + String + T() const + { + return {get("/T")}; + } + + /// @brief Retrieves the alternative name (/TU attribute). + /// + /// @return Returns the alternative name if found; otherwise, returns a default-constructed + /// `String`. + String + TU() const + { + return {get("/TU")}; + } + + /// @brief Retrieves the mapping name (/TM attribute). + /// + /// @return Returns the mapping name if found; otherwise, returns a default-constructed + /// `String`. + String + TM() const + { + return {get("/TM")}; + } + + /// @brief Retrieves the fully qualified name of the form field. + /// + /// This method constructs the fully qualified name of the form field by traversing through + /// its parent hierarchy. The fully qualified name is constructed by concatenating the /T + /// (field name) attribute of each parent node with periods as separators, starting from the + /// root of the hierarchy. + /// + /// If the field has no parent hierarchy, the result will simply be the /T attribute of the + /// current field. In cases of potential circular references, loop detection is applied. + /// + /// @return A string representing the fully qualified name of the field. + std::string fully_qualified_name() const; + + /// @brief Retrieves the partial name (/T attribute) of the form field. + /// + /// This method returns the value of the field's /T attribute, which is the partial name + /// used to identify the field within its parent hierarchy. If the attribute is not set, an + /// empty string is returned. + /// + /// @return A string representing the partial name of the field in UTF-8 encoding, or an + /// empty string if the /T attribute is not present. + std::string partial_name() const; + + /// @brief Retrieves the alternative name for the form field. + /// + /// This method attempts to return the alternative name (/TU) of the form field, which is + /// the field name intended to be presented, to users as a UTF-8 string, if it exists. If + /// the alternative name is not present, the method falls back to the fully qualified name + /// of the form field. + /// + /// @return The alternative name of the form field as a string, or the + /// fully qualified name if the alternative name is unavailable. + std::string alternative_name() const; + + /// @brief Retrieves the mapping field name (/TM) for the form field. + /// + /// If the mapping name (/TM) is present, it is returned as a UTF-8 string. If not, it falls + /// back to the 'alternative name', which is obtained using the `alternative_name()` method. + /// + /// @return The mapping field name (/TM) as a UTF-8 string or the alternative name if the + /// mapping name is absent. + std::string mapping_name() const; + + /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for + /// inheritance through the hierarchy of ancestor nodes in the form field tree. + /// + /// This function attempts to retrieve the `/V` attribute. If the `inherit` + /// parameter is set to `true` and the `/V` is not found at the current level, the + /// method traverses up the parent hierarchy to find the value. The traversal stops when + /// `/V` is found, when the root node is reached, or when a loop detection mechanism + /// prevents further traversal. + /// + /// @tparam T The return type. + /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. + /// @return Returns the field's value if found; otherwise, returns a default-constructed + /// value of type `T`. + template + T + V(bool inherit = true) const + { + return inheritable_value("/V", inherit); + } + + /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for + /// inheritance through the hierarchy of ancestor nodes in the form field tree. + /// + /// This function attempts to retrieve the `/V` attribute. If the `inherit` + /// parameter is set to `true` and the `/V` is not found at the current level, the + /// method traverses up the parent hierarchy to find the value. The traversal stops when + /// `/V` is found, when the root node is reached, or when a loop detection mechanism + /// prevents further traversal. + /// + /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. + /// @return Returns the field's value if found; otherwise, returns a default-constructed + /// object handle. + QPDFObjectHandle const& + V(bool inherit = true) const + { + if (auto& v = get("/V")) { + return v; + } + return {inherit ? inherited("/V") : null_oh}; + } + + /// @brief Retrieves the field value `/V` attribute of the form field, considering + /// inheritance, if the value is a String. + /// + /// This function extracts the value of the form field, accounting for potential inheritance + /// through the form hierarchy. It returns the value if it is a String, and an empty string + /// otherwise. + /// + /// @return A string containing the actual or inherited `/V` attribute of the form field, or + /// an empty string if the value is not present or not a String. + std::string value() const; + + /// @brief Retrieves the field default value (`/DV` attribute) of a specified field, + /// accounting for inheritance through the hierarchy of ancestor nodes in the form + /// field tree. + /// + /// This function attempts to retrieve the `/DV` attribute. If the `inherit` parameter is + /// set to `true` and the `/DV` is not found at the current level, the method traverses up + /// the parent hierarchy to find the value. The traversal stops when + /// `/DV` is found, when the root node is reached, or when a loop detection mechanism + /// prevents further traversal. + /// + /// @tparam T The return type. + /// @param inherit If set to `true`, the function will attempt to retrieve `/DV` by + /// inheritance from the parent hierarchy of the form field. Defaults to `true`. + /// @return Returns the field's default value if found; otherwise, returns a + /// default-constructed value of type `T`. + QPDFObjectHandle const& + DV(bool inherit = true) const + { + if (auto& v = get("/DV")) { + return v; + } + return {inherit ? inherited("/DV") : null_oh}; + } + + /// @brief Retrieves the default value `/DV` attribute of the form field, considering + /// inheritance, if the default value is a String. + /// + /// This function extracts the default value of the form field, accounting for potential + /// inheritance through the form hierarchy. It returns the value if it is a String, and an + /// empty string otherwise. + /// + /// @return A string containing the actual or inherited `/DV` attribute of the form field, + /// or an empty string if the value is not present or not a String. + std::string default_value() const; + + /// @brief Returns the default appearance string for the form field, considering inheritance + /// from the field tree hierarchy and the document's /AcroForm dictionary. + /// + /// This method retrieves the field's /DA (default appearance) attribute. If the attribute + /// is not directly available, it checks the parent fields in the hierarchy for an inherited + /// value. If no value is found in the field hierarchy, it attempts to retrieve the /DA + /// attribute from the document's /AcroForm dictionary. The method returns an empty string + /// if no default appearance string is available or applicable. + /// + /// @return A string representing the default appearance, or an empty string if + /// no value is found. + std::string default_appearance() const; + + // 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: + /// @brief Retrieves an entry from the document's /AcroForm dictionary using the specified + /// name. + /// + /// The method accesses the AcroForm dictionary within the root object of the PDF document. + /// If the AcroForm dictionary contains the given field name, it retrieves the + /// corresponding entry. Otherwise, it returns a default-constructed object handle. + /// + /// @param name The name of the form field to retrieve. + /// @return An object handle corresponding to the specified name within the AcroForm + /// dictionary. + QPDFObjectHandle const& + from_AcroForm(std::string const& name) const + { + return {qpdf() ? qpdf()->getRoot()["/AcroForm"][name] : null_oh}; + } + + void setRadioButtonValue(QPDFObjectHandle name); + void setCheckBoxValue(bool value); + void generateTextAppearance(QPDFAnnotationObjectHelper&); + QPDFObjectHandle + getFontFromResource(QPDFObjectHandle resources, std::string const& font_name); + + static const QPDFObjectHandle null_oh; + }; // class FormNode +} // namespace qpdf::impl + +class QPDFAcroFormDocumentHelper::Members: public qpdf::impl::AcroForm +{ + public: + Members(QPDF& qpdf) : + AcroForm(qpdf.doc()) + { + } +}; + +#endif // ACRO_FORM_HH diff --git a/libqpdf/qpdf/FormField.hh b/libqpdf/qpdf/FormField.hh deleted file mode 100644 index dbeeffa..0000000 --- a/libqpdf/qpdf/FormField.hh +++ /dev/null @@ -1,390 +0,0 @@ -#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 qpdf::BaseDictionary - { - public: - FormField() = default; - FormField(FormField const&) = default; - FormField& operator=(FormField const&) = default; - FormField(FormField&&) = default; - FormField& operator=(FormField&&) = default; - ~FormField() = default; - - FormField(QPDFObjectHandle const& oh) : - BaseDictionary(oh) - { - } - - FormField(QPDFObjectHandle&& oh) : - BaseDictionary(std::move(oh)) - { - } - - /// Retrieves the /Parent form field of the current field. - /// - /// This function accesses the parent field in the hierarchical structure of form fields, if - /// it exists. The parent is determined based on the /Parent attribute in the field - /// dictionary. - /// - /// @return A FormField object representing the parent field. If the current field has no - /// parent, an empty FormField object is returned. - FormField - Parent() - { - return {get("/Parent")}; - } - - /// @brief Returns the top-level field associated with the current field. - /// - /// The function traverses the hierarchy of parent fields to identify the highest-level - /// field in the tree. Typically, this will be the current field itself unless it has a - /// parent field. Optionally, it can indicate whether the top-level field is different from - /// the current field. - /// - /// @param is_different A pointer to a boolean that, if provided, will be set to true if the - /// top-level field differs from the current field; otherwise, it will be set to - /// false. - /// - /// @return The top-level field in the form field hierarchy. - FormField root_field(bool* is_different = nullptr); - - /// @brief Retrieves the inherited value of the specified attribute. - /// - /// @param name The name of the attribute to retrieve. - /// @param acroform If true, checks the document's /AcroForm dictionary for the attribute - /// if it is not found in the field hierarchy. - /// - /// @return A constant reference to the QPDFObjectHandle representing the value of the - /// specified attribute, if found. If the attribute is not found in the field - /// hierarchy or the /AcroForm dictionary (when `acroform` is true), returns a - /// reference to a static null object handle. - QPDFObjectHandle const& inherited(std::string const& name, bool acroform = false) const; - - /// @brief Retrieves the value of a specified field, accounting for inheritance through the - /// hierarchy of ancestor nodes in the form field tree. - /// - /// This function attempts to retrieve the value of the specified field. If the `inherit` - /// parameter is set to `true` and the field value is not found at the current level, the - /// method traverses up the parent hierarchy to find the value. The traversal stops when a - /// value is found, when the root node is reached, or when a loop detection mechanism - /// prevents further traversal. - /// - /// @tparam T The return type of the field value. - /// @param name The name of the field to retrieve the value for. - /// @param inherit If set to `true`, the function will attempt to retrieve the value by - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. - /// @return Returns the field's value if found; otherwise, returns a default-constructed - /// value of type `T`. - template - T - inheritable_value(std::string const& name, bool inherit = true, bool acroform = false) const - { - if (auto& v = get(name)) { - return {v}; - } - return {inherit ? inherited(name, acroform) : null_oh}; - } - - /// @brief Retrieves an inherited field string attribute as a string. - /// - /// @param name The name of the field for which the value is to be retrieved. - /// @return The inherited field value as a UTF-8 encoded string, or an empty string if the - /// value does not exist or is not of String type. - std::string inheritable_string(std::string const& name) const; - - /// @brief Retrieves the field type (/FT attribute). - /// - /// @param inherit If set to `true`, the function will attempt to retrieve the value by - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. - /// @return Returns the field type if found; otherwise, returns a default-constructed - /// `Name`. - Name - FT(bool inherit = true) const - { - return inheritable_value("/FT"); - } - - /// @brief Retrieves the partial field name (/T attribute). - /// - /// @return Returns the partial field name if found; otherwise, returns a - /// default-constructed `String`. - String - T() const - { - return {get("/T")}; - } - - /// @brief Retrieves the alternative name (/TU attribute). - /// - /// @return Returns the alternative name if found; otherwise, returns a default-constructed - /// `String`. - String - TU() const - { - return {get("/TU")}; - } - - /// @brief Retrieves the mapping name (/TM attribute). - /// - /// @return Returns the mapping name if found; otherwise, returns a default-constructed - /// `String`. - String - TM() const - { - return {get("/TM")}; - } - - /// @brief Retrieves the fully qualified name of the form field. - /// - /// This method constructs the fully qualified name of the form field by traversing through - /// its parent hierarchy. The fully qualified name is constructed by concatenating the /T - /// (field name) attribute of each parent node with periods as separators, starting from the - /// root of the hierarchy. - /// - /// If the field has no parent hierarchy, the result will simply be the /T attribute of the - /// current field. In cases of potential circular references, loop detection is applied. - /// - /// @return A string representing the fully qualified name of the field. - std::string fully_qualified_name() const; - - /// @brief Retrieves the partial name (/T attribute) of the form field. - /// - /// This method returns the value of the field's /T attribute, which is the partial name - /// used to identify the field within its parent hierarchy. If the attribute is not set, an - /// empty string is returned. - /// - /// @return A string representing the partial name of the field in UTF-8 encoding, or an - /// empty string if the /T attribute is not present. - std::string partial_name() const; - - /// @brief Retrieves the alternative name for the form field. - /// - /// This method attempts to return the alternative name (/TU) of the form field, which is - /// the field name intended to be presented, to users as a UTF-8 string, if it exists. If - /// the alternative name is not present, the method falls back to the fully qualified name - /// of the form field. - /// - /// @return The alternative name of the form field as a string, or the - /// fully qualified name if the alternative name is unavailable. - std::string alternative_name() const; - - /// @brief Retrieves the mapping field name (/TM) for the form field. - /// - /// If the mapping name (/TM) is present, it is returned as a UTF-8 string. If not, it falls - /// back to the 'alternative name', which is obtained using the `alternative_name()` method. - /// - /// @return The mapping field name (/TM) as a UTF-8 string or the alternative name if the - /// mapping name is absent. - std::string mapping_name() const; - - /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for - /// inheritance through thehierarchy of ancestor nodes in the form field tree. - /// - /// This function attempts to retrieve the `/V` attribute. If the `inherit` - /// parameter is set to `true` and the `/V` is not found at the current level, the - /// method traverses up the parent hierarchy to find the value. The traversal stops when - /// `/V` is found, when the root node is reached, or when a loop detection mechanism - /// prevents further traversal. - /// - /// @tparam T The return type. - /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. - /// @return Returns the field's value if found; otherwise, returns a default-constructed - /// value of type `T`. - template - T - V(bool inherit = true) const - { - return inheritable_value("/V", inherit); - } - - /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for - /// inheritance through the hierarchy of ancestor nodes in the form field tree. - /// - /// This function attempts to retrieve the `/V` attribute. If the `inherit` - /// parameter is set to `true` and the `/V` is not found at the current level, the - /// method traverses up the parent hierarchy to find the value. The traversal stops when - /// `/V` is found, when the root node is reached, or when a loop detection mechanism - /// prevents further traversal. - /// - /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. - /// @return Returns the field's value if found; otherwise, returns a default-constructed - /// object handle. - QPDFObjectHandle const& - V(bool inherit = true) const - { - if (auto& v = get("/V")) { - return v; - } - return {inherit ? inherited("/V") : null_oh}; - } - - /// @brief Retrieves the field value `/V` attribute of the form field, considering - /// inheritance, if the value is a String. - /// - /// This function extracts the value of the form field, accounting for potential inheritance - /// through the form hierarchy. It returns the value if it is a String, and an empty string - /// otherwise. - /// - /// @return A string containing the actual or inherited `/V` attribute of the form field, or - /// an empty string if the value is not present or not a String. - std::string value() const; - - /// @brief Retrieves the field default value (`/DV` attribute) of a specified field, - /// accounting for inheritance through the hierarchy of ancestor nodes in the form - /// field tree. - /// - /// This function attempts to retrieve the `/DV` attribute. If the `inherit` parameter is - /// set to `true` and the `/DV` is not found at the current level, the method traverses up - /// the parent hierarchy to find the value. The traversal stops when - /// `/DV` is found, when the root node is reached, or when a loop detection mechanism - /// prevents further traversal. - /// - /// @tparam T The return type. - /// @param inherit If set to `true`, the function will attempt to retrieve `/DV` by - /// inheritance from the parent hierarchy of the form field. Defaults to `true`. - /// @return Returns the field's default value if found; otherwise, returns a - /// default-constructed value of type `T`. - QPDFObjectHandle const& - DV(bool inherit = true) const - { - if (auto& v = get("/DV")) { - return v; - } - return {inherit ? inherited("/DV") : null_oh}; - } - - /// @brief Retrieves the default value `/DV` attribute of the form field, considering - /// inheritance, if the default value is a String. - /// - /// This function extracts the default value of the form field, accounting for potential - /// inheritance through the form hierarchy. It returns the value if it is a String, and an - /// empty string otherwise. - /// - /// @return A string containing the actual or inherited `/V` attribute of the form field, or - /// an empty string if the value is not present or not a String. - std::string default_value() const; - - /// @brief Returns the default appearance string for the form field, considering inheritance - /// from the field tree hierarchy and the document's /AcroForm dictionary. - /// - /// This method retrieves the field's /DA (default appearance) attribute. If the attribute - /// is not directly available, it checks the parent fields in the hierarchy for an inherited - /// value. If no value is found in the field hierarchy, it attempts to retrieve the /DA - /// attribute from the document's /AcroForm dictionary. The method returns an empty string - /// if no default appearance string is available or applicable. - /// - /// @return A string representing the default appearance, or an empty string if - /// no value is found. - std::string default_appearance() const; - - // 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: - /// @brief Retrieves an entry from the document's /AcroForm dictionary using the specified - /// name. - /// - /// The method accesses the AcroForm dictionary within the root object of the PDF document. - /// If the the AcroForm dictionary contains the given field name, it retrieves the - /// corresponding entry. Otherwise, it returns a default-constructed object handle. - /// - /// @param name The name of the form field to retrieve. - /// @return A object handle corresponding to the specified name within the AcroForm - /// dictionary. - QPDFObjectHandle const& - from_AcroForm(std::string const& name) const - { - return {qpdf() ? qpdf()->getRoot()["/AcroForm"][name] : null_oh}; - } - - void setRadioButtonValue(QPDFObjectHandle name); - void setCheckBoxValue(bool value); - void generateTextAppearance(QPDFAnnotationObjectHelper&); - QPDFObjectHandle - getFontFromResource(QPDFObjectHandle resources, std::string const& font_name); - - static const QPDFObjectHandle null_oh; - }; -} // namespace qpdf::impl - -#endif // FORMFIELD_HH diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index f522be7..2eb58da 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -28,8 +28,9 @@ namespace qpdf namespace impl { + class AcroForm; using Doc = QPDF::Doc; - } + } // namespace impl class Doc: public QPDF { @@ -374,11 +375,33 @@ class QPDF::Doc bool reconstructed_xref() const; QPDFAcroFormDocumentHelper& + acroform_dh() + { + if (!acroform_) { + no_inspection(); + init_acroform(); + } + return *acroform_dh_; + } + + /// @brief Retrieves the shared impl::AcroForm instance associated with the document. + /// + /// @note The AcroForm class caches the form field structure for efficiency. If any part + /// of the form field structure is modified directly the `validate` method MUST be + /// called before calling any other AcroForm methods in order to refresh the cache. + /// + /// If the AcroForm instance has not already been initialized, the `init_acroform()` + /// function is called to initialize it. + /// + /// @return A reference to the shared AcroForm object of the document. + /// + /// @since 12.3 + impl::AcroForm& acroform() { if (!acroform_) { no_inspection(); - acroform_ = std::make_unique(qpdf); + init_acroform(); } return *acroform_; } @@ -438,8 +461,11 @@ class QPDF::Doc qpdf::Doc::Config cf; private: + void init_acroform(); + // Document Helpers; - std::unique_ptr acroform_; + std::unique_ptr acroform_dh_; + impl::AcroForm* acroform_{nullptr}; std::unique_ptr embedded_files_; std::unique_ptr outlines_; std::unique_ptr page_dh_; @@ -1173,7 +1199,7 @@ class QPDF::Doc::Pages: Common void flatten_annotations_for_page( QPDFPageObjectHelper& page, QPDFObjectHandle& resources, - QPDFAcroFormDocumentHelper& afdh, + impl::AcroForm& afdh, int required_flags, int forbidden_flags); diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 1e87fd6..e306363 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -3567,7 +3567,6 @@ test_101(QPDF& pdf, char const* arg2) std::cout << oh.unparseResolved() << '\n'; } - auto test_helper_throws = [&qpdf](auto helper_func) { bool thrown = false; try {