Commit b1dad0de2a12a0feb0608d1f68f3f1e8144592e6
1 parent
b1b789df
Fix previous fix to setting checkbox value (fixes #1056)
The code accepted values other than /Yes but still used /Yes as the checked value instead of obeying the normal appearance dictionary.
Showing
6 changed files
with
43 additions
and
11 deletions
ChangeLog
| 1 | +2024-02-11 Jay Berkenbilt <ejb@ql.org> | |
| 2 | + | |
| 3 | + * The previous fix to #1056 was incomplete. When setting a check | |
| 4 | + box value, the previous fix allowed any value other than /Off to | |
| 5 | + mean checked. Now we also set the actual value based on the | |
| 6 | + allowable non-/Off value in the normal appearance dictionary. | |
| 7 | + Fixes #1056. | |
| 8 | + | |
| 1 | 9 | 2024-02-03 Jay Berkenbilt <ejb@ql.org> |
| 2 | 10 | |
| 3 | 11 | * Add fuzz testing for JSON. | ... | ... |
include/qpdf/QPDFFormFieldObjectHelper.hh
| ... | ... | @@ -166,7 +166,9 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper |
| 166 | 166 | // either /Tx (text) or /Ch (choice), set /NeedAppearances to true. You can explicitly tell this |
| 167 | 167 | // method not to set /NeedAppearances if you are going to generate an appearance stream |
| 168 | 168 | // yourself. Starting with qpdf 8.3.0, this method handles fields of type /Btn (checkboxes, |
| 169 | - // radio buttons, pushbuttons) specially. | |
| 169 | + // radio buttons, pushbuttons) specially. When setting a checkbox value, any value other than | |
| 170 | + // /Off will be treated as on, and the actual value set will be based on the appearance stream's | |
| 171 | + // /N dictionary, so the value that ends up in /V may not exactly match the value you pass in. | |
| 170 | 172 | QPDF_DLL |
| 171 | 173 | void setV(QPDFObjectHandle value, bool need_appearances = true); |
| 172 | 174 | ... | ... |
libqpdf/QPDFFormFieldObjectHelper.cc
| ... | ... | @@ -310,8 +310,8 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) |
| 310 | 310 | setCheckBoxValue((name != "/Off")); |
| 311 | 311 | } |
| 312 | 312 | if (!okay) { |
| 313 | - this->oh.warnIfPossible("ignoring attempt to set a checkbox field to a value of " | |
| 314 | - "other than /Yes or /Off"); | |
| 313 | + this->oh.warnIfPossible( | |
| 314 | + "ignoring attempt to set a checkbox field to a value whose type is not name"); | |
| 315 | 315 | } |
| 316 | 316 | } else if (isRadioButton()) { |
| 317 | 317 | if (value.isName()) { |
| ... | ... | @@ -415,9 +415,6 @@ QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name) |
| 415 | 415 | void |
| 416 | 416 | QPDFFormFieldObjectHelper::setCheckBoxValue(bool value) |
| 417 | 417 | { |
| 418 | - // Set /AS to /Yes or /Off in addition to setting /V. | |
| 419 | - QPDFObjectHandle name = QPDFObjectHandle::newName(value ? "/Yes" : "/Off"); | |
| 420 | - setFieldAttribute("/V", name); | |
| 421 | 418 | QPDFObjectHandle AP = this->oh.getKey("/AP"); |
| 422 | 419 | QPDFObjectHandle annot; |
| 423 | 420 | if (AP.isNull()) { |
| ... | ... | @@ -439,6 +436,29 @@ QPDFFormFieldObjectHelper::setCheckBoxValue(bool value) |
| 439 | 436 | } else { |
| 440 | 437 | annot = this->oh; |
| 441 | 438 | } |
| 439 | + std::string on_value; | |
| 440 | + if (value) { | |
| 441 | + // Set the "on" value to the first value in the appearance stream's normal state dictionary | |
| 442 | + // that isn't /Off. If not found, fall back to /Yes. | |
| 443 | + if (AP.isDictionary()) { | |
| 444 | + auto N = AP.getKey("/N"); | |
| 445 | + if (N.isDictionary()) { | |
| 446 | + for (auto const& iter: N.ditems()) { | |
| 447 | + if (iter.first != "/Off") { | |
| 448 | + on_value = iter.first; | |
| 449 | + break; | |
| 450 | + } | |
| 451 | + } | |
| 452 | + } | |
| 453 | + } | |
| 454 | + if (on_value.empty()) { | |
| 455 | + on_value = "/Yes"; | |
| 456 | + } | |
| 457 | + } | |
| 458 | + | |
| 459 | + // Set /AS to the on value or /Off in addition to setting /V. | |
| 460 | + QPDFObjectHandle name = QPDFObjectHandle::newName(value ? on_value : "/Off"); | |
| 461 | + setFieldAttribute("/V", name); | |
| 442 | 462 | if (!annot.isInitialized()) { |
| 443 | 463 | QTC::TC("qpdf", "QPDFObjectHandle broken checkbox"); |
| 444 | 464 | this->oh.warnIfPossible("unable to set the value of this checkbox"); | ... | ... |
qpdf/qtest/qpdf/button-set-out.pdf
No preview for this file type
qpdf/qtest/qpdf/button-set.pdf
No preview for this file type
qpdf/test_driver.cc
| ... | ... | @@ -1942,7 +1942,9 @@ test_51(QPDF& pdf, char const* arg2) |
| 1942 | 1942 | } else if (Tval == "checkbox1") { |
| 1943 | 1943 | std::cout << "turning checkbox1 on\n"; |
| 1944 | 1944 | QPDFFormFieldObjectHelper foh(field); |
| 1945 | - foh.setV(QPDFObjectHandle::newName("/Yes")); | |
| 1945 | + // The value that eventually gets set is based on what's allowed in /N and may not match | |
| 1946 | + // this value. | |
| 1947 | + foh.setV(QPDFObjectHandle::newName("/Sure")); | |
| 1946 | 1948 | } else if (Tval == "checkbox2") { |
| 1947 | 1949 | std::cout << "turning checkbox2 off\n"; |
| 1948 | 1950 | QPDFFormFieldObjectHelper foh(field); | ... | ... |