Commit bc28f7dbb48ca429904c57870df2f4433af7ea81
1 parent
00cf4ce5
Introduce `FormNode::field()` and `FormNode::widget()` methods; refactor `QPDFAc…
…roFormDocumentHelper` to use them instead of manual key checks
Showing
2 changed files
with
41 additions
and
7 deletions
libqpdf/QPDFAcroFormDocumentHelper.cc
| @@ -412,10 +412,9 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, | @@ -412,10 +412,9 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, | ||
| 412 | // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might | 412 | // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might |
| 413 | // be there to be inherited by annotations below it. | 413 | // be there to be inherited by annotations below it. |
| 414 | 414 | ||
| 415 | - Array Kids = field["/Kids"]; | ||
| 416 | - const bool is_field = depth == 0 || Kids || field.hasKey("/Parent"); | ||
| 417 | - const bool is_annotation = | ||
| 418 | - !Kids && (field.hasKey("/Subtype") || field.hasKey("/Rect") || field.hasKey("/AP")); | 415 | + FormNode node = field; |
| 416 | + const bool is_field = depth == 0 || node.field(); | ||
| 417 | + const bool is_annotation = node.widget(); | ||
| 419 | 418 | ||
| 420 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1); | 419 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1); |
| 421 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1)); | 420 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1)); |
| @@ -428,8 +427,8 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, | @@ -428,8 +427,8 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, | ||
| 428 | } | 427 | } |
| 429 | 428 | ||
| 430 | if (is_annotation) { | 429 | if (is_annotation) { |
| 431 | - QPDFObjectHandle our_field = (is_field ? field : parent); | ||
| 432 | - fields_[our_field.getObjGen()].annotations.emplace_back(field); | 430 | + auto our_field = (is_field ? field : parent); |
| 431 | + fields_[our_field].annotations.emplace_back(field); | ||
| 433 | annotation_to_field_[og] = QPDFFormFieldObjectHelper(our_field); | 432 | annotation_to_field_[og] = QPDFFormFieldObjectHelper(our_field); |
| 434 | } | 433 | } |
| 435 | 434 | ||
| @@ -462,7 +461,7 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, | @@ -462,7 +461,7 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, | ||
| 462 | name_to_fields_[name].insert(og); | 461 | name_to_fields_[name].insert(og); |
| 463 | } | 462 | } |
| 464 | 463 | ||
| 465 | - for (auto const& kid: Kids) { | 464 | + for (auto const& kid: node.Kids()) { |
| 466 | if (bad_fields_.contains(kid)) { | 465 | if (bad_fields_.contains(kid)) { |
| 467 | continue; | 466 | continue; |
| 468 | } | 467 | } |
libqpdf/qpdf/AcroForm.hh
| @@ -644,6 +644,41 @@ namespace qpdf::impl | @@ -644,6 +644,41 @@ namespace qpdf::impl | ||
| 644 | /// no value is found. | 644 | /// no value is found. |
| 645 | std::string default_appearance() const; | 645 | std::string default_appearance() const; |
| 646 | 646 | ||
| 647 | + /// @brief Determines whether the form node represents a field (does not apply to root | ||
| 648 | + /// fields). | ||
| 649 | + /// | ||
| 650 | + /// This method checks if the current node represents a field by examining two conditions: | ||
| 651 | + /// 1. The presence of children nodes (via the `/Kids` array). | ||
| 652 | + /// 2. Whether it contains the `/Parent` key. | ||
| 653 | + /// | ||
| 654 | + /// @return `true` if the node represents a field (either has children or contains a | ||
| 655 | + /// `/Parent` key), otherwise `false`. | ||
| 656 | + bool | ||
| 657 | + field() const | ||
| 658 | + { | ||
| 659 | + return Kids() || contains("/Parent"); | ||
| 660 | + } | ||
| 661 | + | ||
| 662 | + /// @brief Determines if the form node represents a widget annotation. | ||
| 663 | + /// | ||
| 664 | + /// This method checks whether the current form node is a widget annotation | ||
| 665 | + /// by verifying the following conditions: | ||
| 666 | + /// | ||
| 667 | + /// - The node does not have any /Kids entries (i.e., it is not a parent node with | ||
| 668 | + /// descendants). | ||
| 669 | + /// - The node contains any of the following attributes commonly associated with widget | ||
| 670 | + /// annotations: | ||
| 671 | + /// - `/Subtype` | ||
| 672 | + /// - `/Rect` | ||
| 673 | + /// - `/AP` | ||
| 674 | + /// | ||
| 675 | + /// @return `true` if the form node is a widget annotation; otherwise, `false`. | ||
| 676 | + bool | ||
| 677 | + widget() const | ||
| 678 | + { | ||
| 679 | + return !Kids() && (contains("/Subtype") || contains("/Rect") || contains("/AP")); | ||
| 680 | + } | ||
| 681 | + | ||
| 647 | // Return the default resource dictionary for the field. This comes not from the field but | 682 | // Return the default resource dictionary for the field. This comes not from the field but |
| 648 | // from the document-level /AcroForm dictionary. While several PDF generators put a /DR key | 683 | // from the document-level /AcroForm dictionary. While several PDF generators put a /DR key |
| 649 | // in the form field's dictionary, experimentation suggests that many popular readers, | 684 | // in the form field's dictionary, experimentation suggests that many popular readers, |