Commit 00cf4ce581d08b6a59bf7d78bbb969b2ba8fe6ff

Authored by m-holger
1 parent 2fe9086a

Introduce `FormNode::AP()` method and replace manual `/AP` processing with method calls

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
... ...