From bc28f7dbb48ca429904c57870df2f4433af7ea81 Mon Sep 17 00:00:00 2001 From: m-holger Date: Wed, 3 Dec 2025 16:42:59 +0000 Subject: [PATCH] Introduce `FormNode::field()` and `FormNode::widget()` methods; refactor `QPDFAcroFormDocumentHelper` to use them instead of manual key checks --- libqpdf/QPDFAcroFormDocumentHelper.cc | 13 ++++++------- libqpdf/qpdf/AcroForm.hh | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index 8eb496a..d331632 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -412,10 +412,9 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might // be there to be inherited by annotations below it. - Array Kids = field["/Kids"]; - const bool is_field = depth == 0 || Kids || field.hasKey("/Parent"); - const bool is_annotation = - !Kids && (field.hasKey("/Subtype") || field.hasKey("/Rect") || field.hasKey("/AP")); + FormNode node = field; + const bool is_field = depth == 0 || node.field(); + const bool is_annotation = node.widget(); QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1); QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1)); @@ -428,8 +427,8 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, } if (is_annotation) { - QPDFObjectHandle our_field = (is_field ? field : parent); - fields_[our_field.getObjGen()].annotations.emplace_back(field); + auto our_field = (is_field ? field : parent); + fields_[our_field].annotations.emplace_back(field); annotation_to_field_[og] = QPDFFormFieldObjectHelper(our_field); } @@ -462,7 +461,7 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, name_to_fields_[name].insert(og); } - for (auto const& kid: Kids) { + for (auto const& kid: node.Kids()) { if (bad_fields_.contains(kid)) { continue; } diff --git a/libqpdf/qpdf/AcroForm.hh b/libqpdf/qpdf/AcroForm.hh index 48559a6..814a004 100644 --- a/libqpdf/qpdf/AcroForm.hh +++ b/libqpdf/qpdf/AcroForm.hh @@ -644,6 +644,41 @@ namespace qpdf::impl /// no value is found. std::string default_appearance() const; + /// @brief Determines whether the form node represents a field (does not apply to root + /// fields). + /// + /// This method checks if the current node represents a field by examining two conditions: + /// 1. The presence of children nodes (via the `/Kids` array). + /// 2. Whether it contains the `/Parent` key. + /// + /// @return `true` if the node represents a field (either has children or contains a + /// `/Parent` key), otherwise `false`. + bool + field() const + { + return Kids() || contains("/Parent"); + } + + /// @brief Determines if the form node represents a widget annotation. + /// + /// This method checks whether the current form node is a widget annotation + /// by verifying the following conditions: + /// + /// - The node does not have any /Kids entries (i.e., it is not a parent node with + /// descendants). + /// - The node contains any of the following attributes commonly associated with widget + /// annotations: + /// - `/Subtype` + /// - `/Rect` + /// - `/AP` + /// + /// @return `true` if the form node is a widget annotation; otherwise, `false`. + bool + widget() const + { + return !Kids() && (contains("/Subtype") || contains("/Rect") || contains("/AP")); + } + // Return the default resource dictionary for the field. This comes not from the field but // from the document-level /AcroForm dictionary. While several PDF generators put a /DR key // in the form field's dictionary, experimentation suggests that many popular readers, -- libgit2 0.21.4