Commit 8d36ca4d851ce62356a58f5455b23de5f84d8dbf
1 parent
6379f3f1
Refactor `AcroForm`: centralize functionality within `AcroForm` class for improv…
…ed modularity and reusability. - Move methods from `QPDFAcroFormDocumentHelper` to `AcroForm` (`validate`, `invalidateCache`, `addFormField`, and more) to reduce duplication. - Update function calls and improve encapsulation by leveraging `AcroForm`. - Enhance comments and align with PDF specifications for better clarity.
Showing
2 changed files
with
308 additions
and
46 deletions
libqpdf/QPDFAcroFormDocumentHelper.cc
| ... | ... | @@ -9,15 +9,15 @@ |
| 9 | 9 | #include <qpdf/QTC.hh> |
| 10 | 10 | #include <qpdf/QUtil.hh> |
| 11 | 11 | #include <qpdf/ResourceFinder.hh> |
| 12 | +#include <qpdf/Util.hh> | |
| 12 | 13 | |
| 13 | 14 | #include <deque> |
| 14 | 15 | #include <utility> |
| 15 | 16 | |
| 16 | 17 | using namespace qpdf; |
| 18 | +using namespace qpdf::impl; | |
| 17 | 19 | using namespace std::literals; |
| 18 | 20 | |
| 19 | -using AcroForm = impl::AcroForm; | |
| 20 | - | |
| 21 | 21 | class QPDFAcroFormDocumentHelper::Members: public AcroForm |
| 22 | 22 | { |
| 23 | 23 | public: |
| ... | ... | @@ -42,23 +42,41 @@ QPDFAcroFormDocumentHelper::get(QPDF& qpdf) |
| 42 | 42 | void |
| 43 | 43 | QPDFAcroFormDocumentHelper::validate(bool repair) |
| 44 | 44 | { |
| 45 | + m->validate(repair); | |
| 46 | +} | |
| 47 | + | |
| 48 | +void | |
| 49 | +AcroForm::validate(bool repair) | |
| 50 | +{ | |
| 45 | 51 | invalidateCache(); |
| 46 | - m->analyze(); | |
| 52 | + analyze(); | |
| 47 | 53 | } |
| 48 | 54 | |
| 49 | 55 | void |
| 50 | 56 | QPDFAcroFormDocumentHelper::invalidateCache() |
| 51 | 57 | { |
| 52 | - m->cache_valid_ = false; | |
| 53 | - m->fields_.clear(); | |
| 54 | - m->annotation_to_field_.clear(); | |
| 55 | - m->bad_fields_.clear(); | |
| 56 | - m->name_to_fields_.clear(); | |
| 58 | + m->invalidateCache(); | |
| 59 | +} | |
| 60 | + | |
| 61 | +void | |
| 62 | +AcroForm::invalidateCache() | |
| 63 | +{ | |
| 64 | + cache_valid_ = false; | |
| 65 | + fields_.clear(); | |
| 66 | + annotation_to_field_.clear(); | |
| 67 | + bad_fields_.clear(); | |
| 68 | + name_to_fields_.clear(); | |
| 57 | 69 | } |
| 58 | 70 | |
| 59 | 71 | bool |
| 60 | 72 | QPDFAcroFormDocumentHelper::hasAcroForm() |
| 61 | 73 | { |
| 74 | + return m->hasAcroForm(); | |
| 75 | +} | |
| 76 | + | |
| 77 | +bool | |
| 78 | +AcroForm::hasAcroForm() | |
| 79 | +{ | |
| 62 | 80 | return qpdf.getRoot().hasKey("/AcroForm"); |
| 63 | 81 | } |
| 64 | 82 | |
| ... | ... | @@ -76,19 +94,31 @@ AcroForm::getOrCreateAcroForm() |
| 76 | 94 | void |
| 77 | 95 | QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) |
| 78 | 96 | { |
| 79 | - auto acroform = m->getOrCreateAcroForm(); | |
| 97 | + m->addFormField(ff); | |
| 98 | +} | |
| 99 | + | |
| 100 | +void | |
| 101 | +AcroForm::addFormField(QPDFFormFieldObjectHelper ff) | |
| 102 | +{ | |
| 103 | + auto acroform = getOrCreateAcroForm(); | |
| 80 | 104 | auto fields = acroform.getKey("/Fields"); |
| 81 | 105 | if (!fields.isArray()) { |
| 82 | 106 | fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray()); |
| 83 | 107 | } |
| 84 | 108 | fields.appendItem(ff.getObjectHandle()); |
| 85 | - m->traverseField(ff.getObjectHandle(), {}, 0); | |
| 109 | + traverseField(ff.getObjectHandle(), {}, 0); | |
| 86 | 110 | } |
| 87 | 111 | |
| 88 | 112 | void |
| 89 | 113 | QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields) |
| 90 | 114 | { |
| 91 | - m->analyze(); | |
| 115 | + m->addAndRenameFormFields(fields); | |
| 116 | +} | |
| 117 | + | |
| 118 | +void | |
| 119 | +AcroForm::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields) | |
| 120 | +{ | |
| 121 | + analyze(); | |
| 92 | 122 | std::map<std::string, std::string> renames; |
| 93 | 123 | QPDFObjGen::set seen; |
| 94 | 124 | for (std::list<QPDFObjectHandle> queue{fields.begin(), fields.end()}; !queue.empty(); |
| ... | ... | @@ -139,6 +169,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> |
| 139 | 169 | void |
| 140 | 170 | QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remove) |
| 141 | 171 | { |
| 172 | + m->removeFormFields(to_remove); | |
| 173 | +} | |
| 174 | + | |
| 175 | +void | |
| 176 | +AcroForm::removeFormFields(std::set<QPDFObjGen> const& to_remove) | |
| 177 | +{ | |
| 142 | 178 | auto acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 143 | 179 | if (!acroform.isDictionary()) { |
| 144 | 180 | return; |
| ... | ... | @@ -149,19 +185,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo |
| 149 | 185 | } |
| 150 | 186 | |
| 151 | 187 | for (auto const& og: to_remove) { |
| 152 | - auto it = m->fields_.find(og); | |
| 153 | - if (it != m->fields_.end()) { | |
| 188 | + auto it = fields_.find(og); | |
| 189 | + if (it != fields_.end()) { | |
| 154 | 190 | for (auto aoh: it->second.annotations) { |
| 155 | - m->annotation_to_field_.erase(aoh.getObjectHandle().getObjGen()); | |
| 191 | + annotation_to_field_.erase(aoh.getObjectHandle().getObjGen()); | |
| 156 | 192 | } |
| 157 | 193 | auto const& name = it->second.name; |
| 158 | 194 | if (!name.empty()) { |
| 159 | - m->name_to_fields_[name].erase(og); | |
| 160 | - if (m->name_to_fields_[name].empty()) { | |
| 161 | - m->name_to_fields_.erase(name); | |
| 195 | + name_to_fields_[name].erase(og); | |
| 196 | + if (name_to_fields_[name].empty()) { | |
| 197 | + name_to_fields_.erase(name); | |
| 162 | 198 | } |
| 163 | 199 | } |
| 164 | - m->fields_.erase(og); | |
| 200 | + fields_.erase(og); | |
| 165 | 201 | } |
| 166 | 202 | } |
| 167 | 203 | |
| ... | ... | @@ -179,16 +215,28 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo |
| 179 | 215 | void |
| 180 | 216 | QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) |
| 181 | 217 | { |
| 218 | + m->setFormFieldName(ff, name); | |
| 219 | +} | |
| 220 | + | |
| 221 | +void | |
| 222 | +AcroForm::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) | |
| 223 | +{ | |
| 182 | 224 | ff.setFieldAttribute("/T", name); |
| 183 | - m->traverseField(ff, ff["/Parent"], 0); | |
| 225 | + traverseField(ff, ff["/Parent"], 0); | |
| 184 | 226 | } |
| 185 | 227 | |
| 186 | 228 | std::vector<QPDFFormFieldObjectHelper> |
| 187 | 229 | QPDFAcroFormDocumentHelper::getFormFields() |
| 188 | 230 | { |
| 189 | - m->analyze(); | |
| 231 | + return m->getFormFields(); | |
| 232 | +} | |
| 233 | + | |
| 234 | +std::vector<QPDFFormFieldObjectHelper> | |
| 235 | +AcroForm::getFormFields() | |
| 236 | +{ | |
| 237 | + analyze(); | |
| 190 | 238 | std::vector<QPDFFormFieldObjectHelper> result; |
| 191 | - for (auto const& [og, data]: m->fields_) { | |
| 239 | + for (auto const& [og, data]: fields_) { | |
| 192 | 240 | if (!data.annotations.empty()) { |
| 193 | 241 | result.emplace_back(qpdf.getObject(og)); |
| 194 | 242 | } |
| ... | ... | @@ -199,10 +247,16 @@ QPDFAcroFormDocumentHelper::getFormFields() |
| 199 | 247 | std::set<QPDFObjGen> |
| 200 | 248 | QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) |
| 201 | 249 | { |
| 202 | - m->analyze(); | |
| 250 | + return m->getFieldsWithQualifiedName(name); | |
| 251 | +} | |
| 252 | + | |
| 253 | +std::set<QPDFObjGen> | |
| 254 | +AcroForm::getFieldsWithQualifiedName(std::string const& name) | |
| 255 | +{ | |
| 256 | + analyze(); | |
| 203 | 257 | // Keep from creating an empty entry |
| 204 | - auto iter = m->name_to_fields_.find(name); | |
| 205 | - if (iter != m->name_to_fields_.end()) { | |
| 258 | + auto iter = name_to_fields_.find(name); | |
| 259 | + if (iter != name_to_fields_.end()) { | |
| 206 | 260 | return iter->second; |
| 207 | 261 | } |
| 208 | 262 | return {}; |
| ... | ... | @@ -211,11 +265,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) |
| 211 | 265 | std::vector<QPDFAnnotationObjectHelper> |
| 212 | 266 | QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) |
| 213 | 267 | { |
| 214 | - m->analyze(); | |
| 268 | + return m->getAnnotationsForField(h); | |
| 269 | +} | |
| 270 | + | |
| 271 | +std::vector<QPDFAnnotationObjectHelper> | |
| 272 | +AcroForm::getAnnotationsForField(QPDFFormFieldObjectHelper h) | |
| 273 | +{ | |
| 274 | + analyze(); | |
| 215 | 275 | std::vector<QPDFAnnotationObjectHelper> result; |
| 216 | 276 | QPDFObjGen og(h.getObjectHandle().getObjGen()); |
| 217 | - if (m->fields_.contains(og)) { | |
| 218 | - result = m->fields_[og].annotations; | |
| 277 | + if (fields_.contains(og)) { | |
| 278 | + result = fields_[og].annotations; | |
| 219 | 279 | } |
| 220 | 280 | return result; |
| 221 | 281 | } |
| ... | ... | @@ -235,7 +295,13 @@ AcroForm::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) |
| 235 | 295 | std::vector<QPDFFormFieldObjectHelper> |
| 236 | 296 | QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) |
| 237 | 297 | { |
| 238 | - m->analyze(); | |
| 298 | + return m->getFormFieldsForPage(ph); | |
| 299 | +} | |
| 300 | + | |
| 301 | +std::vector<QPDFFormFieldObjectHelper> | |
| 302 | +AcroForm::getFormFieldsForPage(QPDFPageObjectHelper ph) | |
| 303 | +{ | |
| 304 | + analyze(); | |
| 239 | 305 | QPDFObjGen::set todo; |
| 240 | 306 | std::vector<QPDFFormFieldObjectHelper> result; |
| 241 | 307 | for (auto& annot: getWidgetAnnotationsForPage(ph)) { |
| ... | ... | @@ -250,14 +316,20 @@ QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) |
| 250 | 316 | QPDFFormFieldObjectHelper |
| 251 | 317 | QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) |
| 252 | 318 | { |
| 319 | + return m->getFieldForAnnotation(h); | |
| 320 | +} | |
| 321 | + | |
| 322 | +QPDFFormFieldObjectHelper | |
| 323 | +AcroForm::getFieldForAnnotation(QPDFAnnotationObjectHelper h) | |
| 324 | +{ | |
| 253 | 325 | QPDFObjectHandle oh = h.getObjectHandle(); |
| 254 | 326 | if (!oh.isDictionaryOfType("", "/Widget")) { |
| 255 | 327 | return Null::temp(); |
| 256 | 328 | } |
| 257 | - m->analyze(); | |
| 329 | + analyze(); | |
| 258 | 330 | QPDFObjGen og(oh.getObjGen()); |
| 259 | - if (m->annotation_to_field_.contains(og)) { | |
| 260 | - return m->annotation_to_field_[og]; | |
| 331 | + if (annotation_to_field_.contains(og)) { | |
| 332 | + return annotation_to_field_[og]; | |
| 261 | 333 | } |
| 262 | 334 | return Null::temp(); |
| 263 | 335 | } |
| ... | ... | @@ -412,6 +484,12 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, |
| 412 | 484 | bool |
| 413 | 485 | QPDFAcroFormDocumentHelper::getNeedAppearances() |
| 414 | 486 | { |
| 487 | + return m->getNeedAppearances(); | |
| 488 | +} | |
| 489 | + | |
| 490 | +bool | |
| 491 | +AcroForm::getNeedAppearances() | |
| 492 | +{ | |
| 415 | 493 | bool result = false; |
| 416 | 494 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 417 | 495 | if (acroform.isDictionary() && acroform.getKey("/NeedAppearances").isBool()) { |
| ... | ... | @@ -423,6 +501,12 @@ QPDFAcroFormDocumentHelper::getNeedAppearances() |
| 423 | 501 | void |
| 424 | 502 | QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) |
| 425 | 503 | { |
| 504 | + m->setNeedAppearances(val); | |
| 505 | +} | |
| 506 | + | |
| 507 | +void | |
| 508 | +AcroForm::setNeedAppearances(bool val) | |
| 509 | +{ | |
| 426 | 510 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 427 | 511 | if (!acroform.isDictionary()) { |
| 428 | 512 | qpdf.getRoot().warn( |
| ... | ... | @@ -440,6 +524,12 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) |
| 440 | 524 | void |
| 441 | 525 | QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() |
| 442 | 526 | { |
| 527 | + m->generateAppearancesIfNeeded(); | |
| 528 | +} | |
| 529 | + | |
| 530 | +void | |
| 531 | +AcroForm::generateAppearancesIfNeeded() | |
| 532 | +{ | |
| 443 | 533 | if (!getNeedAppearances()) { |
| 444 | 534 | return; |
| 445 | 535 | } |
| ... | ... | @@ -466,6 +556,12 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() |
| 466 | 556 | void |
| 467 | 557 | QPDFAcroFormDocumentHelper::disableDigitalSignatures() |
| 468 | 558 | { |
| 559 | + m->disableDigitalSignatures(); | |
| 560 | +} | |
| 561 | + | |
| 562 | +void | |
| 563 | +AcroForm::disableDigitalSignatures() | |
| 564 | +{ | |
| 469 | 565 | qpdf.removeSecurityRestrictions(); |
| 470 | 566 | std::set<QPDFObjGen> to_remove; |
| 471 | 567 | auto fields = getFormFields(); |
| ... | ... | @@ -744,7 +840,6 @@ QPDFAcroFormDocumentHelper::transformAnnotations( |
| 744 | 840 | QPDF* from_qpdf, |
| 745 | 841 | QPDFAcroFormDocumentHelper* from_afdh) |
| 746 | 842 | { |
| 747 | - Array old_annots = std::move(a_old_annots); | |
| 748 | 843 | if (!from_qpdf) { |
| 749 | 844 | // Assume these are from the same QPDF. |
| 750 | 845 | from_qpdf = &qpdf; |
| ... | ... | @@ -752,6 +847,23 @@ QPDFAcroFormDocumentHelper::transformAnnotations( |
| 752 | 847 | } else if (from_qpdf != &qpdf && !from_afdh) { |
| 753 | 848 | from_afdh = &QPDFAcroFormDocumentHelper::get(*from_qpdf); |
| 754 | 849 | } |
| 850 | + m->transformAnnotations( | |
| 851 | + a_old_annots, new_annots, new_fields, old_fields, cm, from_qpdf, from_afdh->m.get()); | |
| 852 | +} | |
| 853 | + | |
| 854 | +void | |
| 855 | +AcroForm::transformAnnotations( | |
| 856 | + QPDFObjectHandle a_old_annots, | |
| 857 | + std::vector<QPDFObjectHandle>& new_annots, | |
| 858 | + std::vector<QPDFObjectHandle>& new_fields, | |
| 859 | + std::set<QPDFObjGen>& old_fields, | |
| 860 | + QPDFMatrix const& cm, | |
| 861 | + QPDF* from_qpdf, | |
| 862 | + AcroForm* from_afdh) | |
| 863 | +{ | |
| 864 | + qpdf_expect(from_qpdf); | |
| 865 | + qpdf_expect(from_afdh); | |
| 866 | + Array old_annots = std::move(a_old_annots); | |
| 755 | 867 | const bool foreign = from_qpdf != &qpdf; |
| 756 | 868 | |
| 757 | 869 | // It's possible that we will transform annotations that don't include any form fields. This |
| ... | ... | @@ -809,7 +921,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( |
| 809 | 921 | // Ensure that we have a /DR that is an indirect |
| 810 | 922 | // dictionary object. |
| 811 | 923 | if (!acroform) { |
| 812 | - acroform = m->getOrCreateAcroForm(); | |
| 924 | + acroform = getOrCreateAcroForm(); | |
| 813 | 925 | } |
| 814 | 926 | dr = acroform["/DR"]; |
| 815 | 927 | if (!dr) { |
| ... | ... | @@ -874,7 +986,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( |
| 874 | 986 | } |
| 875 | 987 | ++i; |
| 876 | 988 | } |
| 877 | - m->adjustInheritedFields( | |
| 989 | + adjustInheritedFields( | |
| 878 | 990 | obj, override_da, from_default_da, override_q, from_default_q); |
| 879 | 991 | if (foreign) { |
| 880 | 992 | // Lazily initialize our /DR and the conflict map. |
| ... | ... | @@ -890,7 +1002,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( |
| 890 | 1002 | obj.replace("/DR", dr); |
| 891 | 1003 | } |
| 892 | 1004 | if (obj["/DA"].isString() && !dr_map.empty()) { |
| 893 | - m->adjustDefaultAppearances(obj, dr_map); | |
| 1005 | + adjustDefaultAppearances(obj, dr_map); | |
| 894 | 1006 | } |
| 895 | 1007 | } |
| 896 | 1008 | } |
| ... | ... | @@ -1037,7 +1149,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( |
| 1037 | 1149 | } |
| 1038 | 1150 | Dictionary resources = dict["/Resources"]; |
| 1039 | 1151 | if (!dr_map.empty() && resources) { |
| 1040 | - m->adjustAppearanceStream(stream, dr_map); | |
| 1152 | + adjustAppearanceStream(stream, dr_map); | |
| 1041 | 1153 | } |
| 1042 | 1154 | } |
| 1043 | 1155 | auto rect = cm.transformRectangle(annot["/Rect"].getArrayAsRectangle()); |
| ... | ... | @@ -1052,6 +1164,16 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( |
| 1052 | 1164 | QPDFAcroFormDocumentHelper& from_afdh, |
| 1053 | 1165 | std::set<QPDFObjGen>* added_fields) |
| 1054 | 1166 | { |
| 1167 | + m->fixCopiedAnnotations(to_page, from_page, *from_afdh.m, added_fields); | |
| 1168 | +} | |
| 1169 | + | |
| 1170 | +void | |
| 1171 | +AcroForm::fixCopiedAnnotations( | |
| 1172 | + QPDFObjectHandle to_page, | |
| 1173 | + QPDFObjectHandle from_page, | |
| 1174 | + AcroForm& from_afdh, | |
| 1175 | + std::set<QPDFObjGen>* added_fields) | |
| 1176 | +{ | |
| 1055 | 1177 | auto old_annots = from_page.getKey("/Annots"); |
| 1056 | 1178 | if (old_annots.empty() || !old_annots.isArray()) { |
| 1057 | 1179 | return; |
| ... | ... | @@ -1066,7 +1188,7 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( |
| 1066 | 1188 | new_fields, |
| 1067 | 1189 | old_fields, |
| 1068 | 1190 | QPDFMatrix(), |
| 1069 | - &(from_afdh.getQPDF()), | |
| 1191 | + &(from_afdh.qpdf), | |
| 1070 | 1192 | &from_afdh); |
| 1071 | 1193 | |
| 1072 | 1194 | to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); | ... | ... |
libqpdf/qpdf/AcroForm.hh
| ... | ... | @@ -11,6 +11,19 @@ class QPDFAnnotationObjectHelper; |
| 11 | 11 | |
| 12 | 12 | namespace qpdf::impl |
| 13 | 13 | { |
| 14 | + /// @class AcroForm | |
| 15 | + /// @brief Represents the interactive form dictionary and the interactive form tree within a | |
| 16 | + /// PDF document. | |
| 17 | + /// @par | |
| 18 | + /// The AcroForm class deals with interactive forms defined in section 12.7 of the PDF | |
| 19 | + /// specification. This defines a tree structure consisting of an interactive form or | |
| 20 | + /// `/AcroForm` dictionary (section 12.7.3) at its root. The attributes of the | |
| 21 | + /// `/AcroForm` dictionary are defined in table 224 of the PDF 2.0 / table 220 of the | |
| 22 | + /// PDF 1.7 specification. | |
| 23 | + /// @par | |
| 24 | + /// The nodes of the interactive forms tree are represented by the FormNode class. | |
| 25 | + /// | |
| 26 | + /// @since 12.3 | |
| 14 | 27 | class AcroForm: public Doc::Common |
| 15 | 28 | { |
| 16 | 29 | public: |
| ... | ... | @@ -27,14 +40,61 @@ namespace qpdf::impl |
| 27 | 40 | // We have to analyze up front. Otherwise, when we are adding annotations and fields, we |
| 28 | 41 | // are in a temporarily unstable configuration where some widget annotations are not |
| 29 | 42 | // reachable. |
| 30 | - analyze(); | |
| 43 | + validate(); | |
| 31 | 44 | } |
| 32 | 45 | |
| 33 | - struct FieldData | |
| 34 | - { | |
| 35 | - std::vector<QPDFAnnotationObjectHelper> annotations; | |
| 36 | - std::string name; | |
| 37 | - }; | |
| 46 | + // Re-validate the AcroForm structure. This is useful if you have modified the structure of | |
| 47 | + // the AcroForm dictionary in a way that would invalidate the cache. | |
| 48 | + // | |
| 49 | + // If repair is true, the document will be repaired if possible if the validation encounters | |
| 50 | + // errors. | |
| 51 | + void validate(bool repair = true); | |
| 52 | + | |
| 53 | + // This class lazily creates an internal cache of the mapping among form fields, | |
| 54 | + // annotations, and pages. Methods within this class preserve the validity of this cache. | |
| 55 | + // However, if you modify pages' annotation dictionaries, the document's /AcroForm | |
| 56 | + // dictionary, or any form fields manually in a way that alters the association between | |
| 57 | + // forms, fields, annotations, and pages, it may cause this cache to become invalid. This | |
| 58 | + // method marks the cache invalid and forces it to be regenerated the next time it is | |
| 59 | + // needed. | |
| 60 | + void invalidateCache(); | |
| 61 | + | |
| 62 | + bool hasAcroForm(); | |
| 63 | + | |
| 64 | + // Add a form field, initializing the document's AcroForm dictionary if needed, updating the | |
| 65 | + // cache if necessary. Note that if you are adding fields that are copies of other fields, | |
| 66 | + // this method may result in multiple fields existing with the same qualified name, which | |
| 67 | + // can have unexpected side effects. In that case, you should use addAndRenameFormFields() | |
| 68 | + // instead. | |
| 69 | + void addFormField(QPDFFormFieldObjectHelper); | |
| 70 | + | |
| 71 | + // Add a collection of form fields making sure that their fully qualified names don't | |
| 72 | + // conflict with already present form fields. Fields within the collection of new fields | |
| 73 | + // that have the same name as each other will continue to do so. | |
| 74 | + void addAndRenameFormFields(std::vector<QPDFObjectHandle> fields); | |
| 75 | + | |
| 76 | + // Remove fields from the fields array | |
| 77 | + void removeFormFields(std::set<QPDFObjGen> const&); | |
| 78 | + | |
| 79 | + // Set the name of a field, updating internal records of field names. Name should be UTF-8 | |
| 80 | + // encoded. | |
| 81 | + void setFormFieldName(QPDFFormFieldObjectHelper, std::string const& name); | |
| 82 | + | |
| 83 | + // Return a vector of all terminal fields in a document. Terminal fields are fields that | |
| 84 | + // have no children that are also fields. Terminal fields may still have children that are | |
| 85 | + // annotations. Intermediate nodes in the fields tree are not included in this list, but you | |
| 86 | + // can still reach them through the getParent method of the field object helper. | |
| 87 | + std::vector<QPDFFormFieldObjectHelper> getFormFields(); | |
| 88 | + | |
| 89 | + // Return all the form fields that have the given fully-qualified name and also have an | |
| 90 | + // explicit "/T" attribute. For this information to be accurate, any changes to field names | |
| 91 | + // must be done through setFormFieldName() above. | |
| 92 | + std::set<QPDFObjGen> getFieldsWithQualifiedName(std::string const& name); | |
| 93 | + | |
| 94 | + // Return the annotations associated with a terminal field. Note that in the case of a field | |
| 95 | + // having a single annotation, the underlying object will typically be the same as the | |
| 96 | + // underlying object for the field. | |
| 97 | + std::vector<QPDFAnnotationObjectHelper> getAnnotationsForField(QPDFFormFieldObjectHelper); | |
| 38 | 98 | |
| 39 | 99 | /// Retrieves a list of widget annotations for the specified page. |
| 40 | 100 | /// |
| ... | ... | @@ -50,6 +110,86 @@ namespace qpdf::impl |
| 50 | 110 | std::vector<QPDFAnnotationObjectHelper> |
| 51 | 111 | getWidgetAnnotationsForPage(QPDFPageObjectHelper page); |
| 52 | 112 | |
| 113 | + // Return top-level form fields for a page. | |
| 114 | + std::vector<QPDFFormFieldObjectHelper> getFormFieldsForPage(QPDFPageObjectHelper); | |
| 115 | + | |
| 116 | + // Return the terminal field that is associated with this annotation. If the annotation | |
| 117 | + // dictionary is merged with the field dictionary, the underlying object will be the same, | |
| 118 | + // but this is not always the case. Note that if you call this method with an annotation | |
| 119 | + // that is not a widget annotation, there will not be an associated field, and this method | |
| 120 | + // will return a helper associated with a null object (isNull() == true). | |
| 121 | + QPDFFormFieldObjectHelper getFieldForAnnotation(QPDFAnnotationObjectHelper); | |
| 122 | + | |
| 123 | + // Return the current value of /NeedAppearances. If /NeedAppearances is missing, return | |
| 124 | + // false as that is how PDF viewers are supposed to interpret it. | |
| 125 | + bool getNeedAppearances(); | |
| 126 | + | |
| 127 | + // Indicate whether appearance streams must be regenerated. If you modify a field value, you | |
| 128 | + // should call setNeedAppearances(true) unless you also generate an appearance stream for | |
| 129 | + // the corresponding annotation at the same time. If you generate appearance streams for all | |
| 130 | + // fields, you can call setNeedAppearances(false). If you use | |
| 131 | + // QPDFFormFieldObjectHelper::setV, it will automatically call this method unless you tell | |
| 132 | + // it not to. | |
| 133 | + void setNeedAppearances(bool); | |
| 134 | + | |
| 135 | + // If /NeedAppearances is false, do nothing. Otherwise generate appearance streams for all | |
| 136 | + // widget annotations that need them. See comments in QPDFFormFieldObjectHelper.hh for | |
| 137 | + // generateAppearance for limitations. For checkbox and radio button fields, this code | |
| 138 | + // ensures that appearance state is consistent with the field's value and uses any | |
| 139 | + // pre-existing appearance streams. | |
| 140 | + void generateAppearancesIfNeeded(); | |
| 141 | + | |
| 142 | + // Disable Digital Signature Fields. Remove all digital signature fields from the document, | |
| 143 | + // leaving any annotation showing the content of the field intact. This also calls | |
| 144 | + // QPDF::removeSecurityRestrictions. | |
| 145 | + void disableDigitalSignatures(); | |
| 146 | + | |
| 147 | + // Note: this method works on all annotations, not just ones with associated fields. For | |
| 148 | + // each annotation in old_annots, apply the given transformation matrix to create a new | |
| 149 | + // annotation. New annotations are appended to new_annots. If the annotation is associated | |
| 150 | + // with a form field, a new form field is created that points to the new annotation and is | |
| 151 | + // appended to new_fields, and the old field is added to old_fields. | |
| 152 | + // | |
| 153 | + // old_annots may belong to a different QPDF object. In that case, you should pass in | |
| 154 | + // from_qpdf, and copyForeignObject will be called automatically. If this is the case, for | |
| 155 | + // efficiency, you may pass in a QPDFAcroFormDocumentHelper for the other file to avoid the | |
| 156 | + // expensive process of creating one for each call to transformAnnotations. New fields and | |
| 157 | + // annotations are not added to the document or pages. You have to do that yourself after | |
| 158 | + // calling transformAnnotations. If this operation will leave orphaned fields behind, such | |
| 159 | + // as if you are replacing the old annotations with the new ones on the same page and the | |
| 160 | + // fields and annotations are not shared, you will also need to remove the old fields to | |
| 161 | + // prevent them from hanging around unreferenced. | |
| 162 | + void transformAnnotations( | |
| 163 | + QPDFObjectHandle old_annots, | |
| 164 | + std::vector<QPDFObjectHandle>& new_annots, | |
| 165 | + std::vector<QPDFObjectHandle>& new_fields, | |
| 166 | + std::set<QPDFObjGen>& old_fields, | |
| 167 | + QPDFMatrix const& cm, | |
| 168 | + QPDF* from_qpdf, | |
| 169 | + AcroForm* from_afdh); | |
| 170 | + | |
| 171 | + // Copy form fields and annotations from one page to another, allowing the from page to be | |
| 172 | + // in a different QPDF or in the same QPDF. This would typically be called after calling | |
| 173 | + // addPage to add field/annotation awareness. When just copying the page by itself, | |
| 174 | + // annotations end up being shared, and fields end up being omitted because there is no | |
| 175 | + // reference to the field from the page. This method ensures that each separate copy of a | |
| 176 | + // page has private annotations and that fields and annotations are properly updated to | |
| 177 | + // resolve conflicts that may occur from common resource and field names across documents. | |
| 178 | + // It is basically a wrapper around transformAnnotations that handles updating the receiving | |
| 179 | + // page. If new_fields is non-null, any newly created fields are added to it. | |
| 180 | + void fixCopiedAnnotations( | |
| 181 | + QPDFObjectHandle to_page, | |
| 182 | + QPDFObjectHandle from_page, | |
| 183 | + AcroForm& from_afdh, | |
| 184 | + std::set<QPDFObjGen>* new_fields = nullptr); | |
| 185 | + | |
| 186 | + private: | |
| 187 | + struct FieldData | |
| 188 | + { | |
| 189 | + std::vector<QPDFAnnotationObjectHelper> annotations; | |
| 190 | + std::string name; | |
| 191 | + }; | |
| 192 | + | |
| 53 | 193 | /// Analyzes the AcroForm structure in the PDF document and updates the internal |
| 54 | 194 | /// cache with the form fields and their corresponding widget annotations. |
| 55 | 195 | /// |
| ... | ... | @@ -374,7 +514,7 @@ namespace qpdf::impl |
| 374 | 514 | std::string mapping_name() const; |
| 375 | 515 | |
| 376 | 516 | /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for |
| 377 | - /// inheritance through thehierarchy of ancestor nodes in the form field tree. | |
| 517 | + /// inheritance through the hierarchy of ancestor nodes in the form field tree. | |
| 378 | 518 | /// |
| 379 | 519 | /// This function attempts to retrieve the `/V` attribute. If the `inherit` |
| 380 | 520 | /// parameter is set to `true` and the `/V` is not found at the current level, the |
| ... | ... | @@ -549,11 +689,11 @@ namespace qpdf::impl |
| 549 | 689 | /// name. |
| 550 | 690 | /// |
| 551 | 691 | /// The method accesses the AcroForm dictionary within the root object of the PDF document. |
| 552 | - /// If the the AcroForm dictionary contains the given field name, it retrieves the | |
| 692 | + /// If the AcroForm dictionary contains the given field name, it retrieves the | |
| 553 | 693 | /// corresponding entry. Otherwise, it returns a default-constructed object handle. |
| 554 | 694 | /// |
| 555 | 695 | /// @param name The name of the form field to retrieve. |
| 556 | - /// @return A object handle corresponding to the specified name within the AcroForm | |
| 696 | + /// @return An object handle corresponding to the specified name within the AcroForm | |
| 557 | 697 | /// dictionary. |
| 558 | 698 | QPDFObjectHandle const& |
| 559 | 699 | from_AcroForm(std::string const& name) const | ... | ... |