Commit 00cf4ce581d08b6a59bf7d78bbb969b2ba8fe6ff
1 parent
2fe9086a
Introduce `FormNode::AP()` method and replace manual `/AP` processing with method calls
Showing
3 changed files
with
57 additions
and
42 deletions
libqpdf/QPDFFormFieldObjectHelper.cc
| ... | ... | @@ -415,7 +415,13 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, QPDFObjectH |
| 415 | 415 | void |
| 416 | 416 | FormNode::setFieldAttribute(std::string const& key, QPDFObjectHandle value) |
| 417 | 417 | { |
| 418 | - oh().replaceKey(key, value); | |
| 418 | + replace(key, value); | |
| 419 | +} | |
| 420 | + | |
| 421 | +void | |
| 422 | +FormNode::setFieldAttribute(std::string const& key, Name const& value) | |
| 423 | +{ | |
| 424 | + replace(key, value); | |
| 419 | 425 | } |
| 420 | 426 | |
| 421 | 427 | void |
| ... | ... | @@ -427,7 +433,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, std::string |
| 427 | 433 | void |
| 428 | 434 | FormNode::setFieldAttribute(std::string const& key, std::string const& utf8_value) |
| 429 | 435 | { |
| 430 | - oh().replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); | |
| 436 | + replace(key, String::utf16(utf8_value)); | |
| 431 | 437 | } |
| 432 | 438 | |
| 433 | 439 | void |
| ... | ... | @@ -517,13 +523,13 @@ FormNode::setRadioButtonValue(QPDFObjectHandle name) |
| 517 | 523 | } |
| 518 | 524 | setFieldAttribute("/V", name); |
| 519 | 525 | for (FormNode kid: kids) { |
| 520 | - QPDFObjectHandle AP = kid["/AP"]; | |
| 526 | + auto ap = kid.AP(); | |
| 521 | 527 | QPDFObjectHandle annot; |
| 522 | - if (AP.null()) { | |
| 528 | + if (!ap) { | |
| 523 | 529 | // The widget may be below. If there is more than one, just find the first one. |
| 524 | - for (auto const& grandkid: kid.Kids()) { | |
| 525 | - AP = grandkid.getKey("/AP"); | |
| 526 | - if (!AP.null()) { | |
| 530 | + for (FormNode grandkid: kid.Kids()) { | |
| 531 | + ap = grandkid.AP(); | |
| 532 | + if (ap) { | |
| 527 | 533 | annot = grandkid; |
| 528 | 534 | break; |
| 529 | 535 | } |
| ... | ... | @@ -535,11 +541,10 @@ FormNode::setRadioButtonValue(QPDFObjectHandle name) |
| 535 | 541 | warn("unable to set the value of this radio button"); |
| 536 | 542 | continue; |
| 537 | 543 | } |
| 538 | - if (AP.isDictionary() && AP.getKey("/N").isDictionary() && | |
| 539 | - AP.getKey("/N").hasKey(name.getName())) { | |
| 540 | - annot.replaceKey("/AS", name); | |
| 544 | + if (ap["/N"].contains(name.getName())) { | |
| 545 | + annot.replace("/AS", name); | |
| 541 | 546 | } else { |
| 542 | - annot.replaceKey("/AS", QPDFObjectHandle::newName("/Off")); | |
| 547 | + annot.replace("/AS", Name("/Off")); | |
| 543 | 548 | } |
| 544 | 549 | } |
| 545 | 550 | } |
| ... | ... | @@ -547,27 +552,26 @@ FormNode::setRadioButtonValue(QPDFObjectHandle name) |
| 547 | 552 | void |
| 548 | 553 | FormNode::setCheckBoxValue(bool value) |
| 549 | 554 | { |
| 550 | - QPDFObjectHandle AP = oh().getKey("/AP"); | |
| 555 | + auto ap = AP(); | |
| 551 | 556 | QPDFObjectHandle annot; |
| 552 | - if (AP.null()) { | |
| 553 | - // The widget may be below. If there is more than one, just | |
| 554 | - // find the first one. | |
| 555 | - for (auto const& kid: Kids()) { | |
| 556 | - AP = kid.getKey("/AP"); | |
| 557 | - if (!AP.null()) { | |
| 557 | + if (ap) { | |
| 558 | + annot = oh(); | |
| 559 | + } else { | |
| 560 | + // The widget may be below. If there is more than one, just find the first one. | |
| 561 | + for (FormNode kid: Kids()) { | |
| 562 | + ap = kid.AP(); | |
| 563 | + if (ap) { | |
| 558 | 564 | annot = kid; |
| 559 | 565 | break; |
| 560 | 566 | } |
| 561 | 567 | } |
| 562 | - } else { | |
| 563 | - annot = oh(); | |
| 564 | 568 | } |
| 565 | 569 | std::string on_value; |
| 566 | 570 | if (value) { |
| 567 | 571 | // Set the "on" value to the first value in the appearance stream's normal state dictionary |
| 568 | 572 | // that isn't /Off. If not found, fall back to /Yes. |
| 569 | - if (AP.isDictionary()) { | |
| 570 | - for (auto const& item: AP.getKey("/N").as_dictionary()) { | |
| 573 | + if (ap) { | |
| 574 | + for (auto const& item: Dictionary(ap["/N"])) { | |
| 571 | 575 | if (item.first != "/Off") { |
| 572 | 576 | on_value = item.first; |
| 573 | 577 | break; |
| ... | ... | @@ -580,15 +584,13 @@ FormNode::setCheckBoxValue(bool value) |
| 580 | 584 | } |
| 581 | 585 | |
| 582 | 586 | // Set /AS to the on value or /Off in addition to setting /V. |
| 583 | - QPDFObjectHandle name = QPDFObjectHandle::newName(value ? on_value : "/Off"); | |
| 587 | + auto name = Name(value ? on_value : "/Off"); | |
| 584 | 588 | setFieldAttribute("/V", name); |
| 585 | 589 | if (!annot) { |
| 586 | - QTC::TC("qpdf", "QPDFObjectHandle broken checkbox"); | |
| 587 | 590 | warn("unable to set the value of this checkbox"); |
| 588 | 591 | return; |
| 589 | 592 | } |
| 590 | - QTC::TC("qpdf", "QPDFFormFieldObjectHelper set checkbox AS"); | |
| 591 | - annot.replaceKey("/AS", name); | |
| 593 | + annot.replace("/AS", name); | |
| 592 | 594 | } |
| 593 | 595 | |
| 594 | 596 | void |
| ... | ... | @@ -899,12 +901,11 @@ FormNode::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) |
| 899 | 901 | {"/Subtype", Name("/Form")}}); |
| 900 | 902 | AS = QPDFObjectHandle::newStream(oh().getOwningQPDF(), "/Tx BMC\nEMC\n"); |
| 901 | 903 | AS.replaceDict(dict); |
| 902 | - Dictionary AP = aoh.getAppearanceDictionary(); | |
| 903 | - if (!AP) { | |
| 904 | - aoh.getObjectHandle().replaceKey("/AP", Dictionary::empty()); | |
| 905 | - AP = aoh.getAppearanceDictionary(); | |
| 904 | + if (auto ap = AP()) { | |
| 905 | + ap.replace("/N", AS); | |
| 906 | + } else { | |
| 907 | + aoh.replace("/AP", Dictionary({{"/N", AS}})); | |
| 906 | 908 | } |
| 907 | - AP.replace("/N", AS); | |
| 908 | 909 | } |
| 909 | 910 | if (!AS.isStream()) { |
| 910 | 911 | aoh.warn("unable to get normal appearance stream for update"); | ... | ... |
libqpdf/qpdf/AcroForm.hh
| ... | ... | @@ -356,18 +356,19 @@ namespace qpdf::impl |
| 356 | 356 | { |
| 357 | 357 | } |
| 358 | 358 | |
| 359 | - /// @brief Retrieves the /Kids array. | |
| 359 | + // Widget and annotation attributes | |
| 360 | + | |
| 361 | + /// @brief Retrieves the /AP attribute of the form node as a Dictionary. | |
| 360 | 362 | /// |
| 361 | - /// This method returns the /Kids entry, which is an array of the immediate descendants of | |
| 362 | - /// this node. It is only present if the node is a form field rather than a pure widget | |
| 363 | - /// annotation. | |
| 363 | + /// The /AP attribute, short for "appearance dictionary," defines how an annotation is | |
| 364 | + /// presented visually on a page. See section 12.5.5 of the PDF specification for more | |
| 365 | + /// details. | |
| 364 | 366 | /// |
| 365 | - /// @return An `Array` object containing the /Kids elements. If the /Kids entry | |
| 366 | - /// does not exist or is not a valid array, the returned `Array` will be invalid. | |
| 367 | - Array | |
| 368 | - Kids() const | |
| 367 | + /// @return A Dictionary containing the /AP attribute of the form node. | |
| 368 | + Dictionary | |
| 369 | + AP() const | |
| 369 | 370 | { |
| 370 | - return {get("/Kids")}; | |
| 371 | + return {get("/AP")}; | |
| 371 | 372 | } |
| 372 | 373 | |
| 373 | 374 | /// Retrieves the /Parent form field of the current field. |
| ... | ... | @@ -384,6 +385,20 @@ namespace qpdf::impl |
| 384 | 385 | return {get("/Parent")}; |
| 385 | 386 | } |
| 386 | 387 | |
| 388 | + /// @brief Retrieves the /Kids array. | |
| 389 | + /// | |
| 390 | + /// This method returns the /Kids entry, which is an array of the immediate descendants of | |
| 391 | + /// this node. It is only present if the node is a form field rather than a pure widget | |
| 392 | + /// annotation. | |
| 393 | + /// | |
| 394 | + /// @return An `Array` object containing the /Kids elements. If the /Kids entry | |
| 395 | + /// does not exist or is not a valid array, the returned `Array` will be invalid. | |
| 396 | + Array | |
| 397 | + Kids() const | |
| 398 | + { | |
| 399 | + return {get("/Kids")}; | |
| 400 | + } | |
| 401 | + | |
| 387 | 402 | /// @brief Returns the top-level field associated with the current field. |
| 388 | 403 | /// |
| 389 | 404 | /// The function traverses the hierarchy of parent fields to identify the highest-level |
| ... | ... | @@ -670,6 +685,7 @@ namespace qpdf::impl |
| 670 | 685 | // want to set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName |
| 671 | 686 | // instead. |
| 672 | 687 | void setFieldAttribute(std::string const& key, QPDFObjectHandle value); |
| 688 | + void setFieldAttribute(std::string const& key, Name const& value); | |
| 673 | 689 | |
| 674 | 690 | // Set an attribute to the given value as a Unicode string (UTF-16 BE encoded). The input |
| 675 | 691 | // string should be UTF-8 encoded. If you have a QPDFAcroFormDocumentHelper and you want to | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -193,8 +193,6 @@ QPDFPageDocumentHelper non-widget annotation 0 |
| 193 | 193 | QPDFObjectHandle replace with copy 0 |
| 194 | 194 | QPDFAnnotationObjectHelper forbidden flags 0 |
| 195 | 195 | QPDFAnnotationObjectHelper missing required flags 0 |
| 196 | -QPDFFormFieldObjectHelper set checkbox AS 0 | |
| 197 | -QPDFObjectHandle broken checkbox 0 | |
| 198 | 196 | QPDFFormFieldObjectHelper list not found 0 |
| 199 | 197 | QPDFFormFieldObjectHelper list found 0 |
| 200 | 198 | QPDFFormFieldObjectHelper list first too low 0 | ... | ... |