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,15 +9,15 @@ | ||
| 9 | #include <qpdf/QTC.hh> | 9 | #include <qpdf/QTC.hh> |
| 10 | #include <qpdf/QUtil.hh> | 10 | #include <qpdf/QUtil.hh> |
| 11 | #include <qpdf/ResourceFinder.hh> | 11 | #include <qpdf/ResourceFinder.hh> |
| 12 | +#include <qpdf/Util.hh> | ||
| 12 | 13 | ||
| 13 | #include <deque> | 14 | #include <deque> |
| 14 | #include <utility> | 15 | #include <utility> |
| 15 | 16 | ||
| 16 | using namespace qpdf; | 17 | using namespace qpdf; |
| 18 | +using namespace qpdf::impl; | ||
| 17 | using namespace std::literals; | 19 | using namespace std::literals; |
| 18 | 20 | ||
| 19 | -using AcroForm = impl::AcroForm; | ||
| 20 | - | ||
| 21 | class QPDFAcroFormDocumentHelper::Members: public AcroForm | 21 | class QPDFAcroFormDocumentHelper::Members: public AcroForm |
| 22 | { | 22 | { |
| 23 | public: | 23 | public: |
| @@ -42,23 +42,41 @@ QPDFAcroFormDocumentHelper::get(QPDF& qpdf) | @@ -42,23 +42,41 @@ QPDFAcroFormDocumentHelper::get(QPDF& qpdf) | ||
| 42 | void | 42 | void |
| 43 | QPDFAcroFormDocumentHelper::validate(bool repair) | 43 | QPDFAcroFormDocumentHelper::validate(bool repair) |
| 44 | { | 44 | { |
| 45 | + m->validate(repair); | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +void | ||
| 49 | +AcroForm::validate(bool repair) | ||
| 50 | +{ | ||
| 45 | invalidateCache(); | 51 | invalidateCache(); |
| 46 | - m->analyze(); | 52 | + analyze(); |
| 47 | } | 53 | } |
| 48 | 54 | ||
| 49 | void | 55 | void |
| 50 | QPDFAcroFormDocumentHelper::invalidateCache() | 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 | bool | 71 | bool |
| 60 | QPDFAcroFormDocumentHelper::hasAcroForm() | 72 | QPDFAcroFormDocumentHelper::hasAcroForm() |
| 61 | { | 73 | { |
| 74 | + return m->hasAcroForm(); | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +bool | ||
| 78 | +AcroForm::hasAcroForm() | ||
| 79 | +{ | ||
| 62 | return qpdf.getRoot().hasKey("/AcroForm"); | 80 | return qpdf.getRoot().hasKey("/AcroForm"); |
| 63 | } | 81 | } |
| 64 | 82 | ||
| @@ -76,19 +94,31 @@ AcroForm::getOrCreateAcroForm() | @@ -76,19 +94,31 @@ AcroForm::getOrCreateAcroForm() | ||
| 76 | void | 94 | void |
| 77 | QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) | 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 | auto fields = acroform.getKey("/Fields"); | 104 | auto fields = acroform.getKey("/Fields"); |
| 81 | if (!fields.isArray()) { | 105 | if (!fields.isArray()) { |
| 82 | fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray()); | 106 | fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray()); |
| 83 | } | 107 | } |
| 84 | fields.appendItem(ff.getObjectHandle()); | 108 | fields.appendItem(ff.getObjectHandle()); |
| 85 | - m->traverseField(ff.getObjectHandle(), {}, 0); | 109 | + traverseField(ff.getObjectHandle(), {}, 0); |
| 86 | } | 110 | } |
| 87 | 111 | ||
| 88 | void | 112 | void |
| 89 | QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields) | 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 | std::map<std::string, std::string> renames; | 122 | std::map<std::string, std::string> renames; |
| 93 | QPDFObjGen::set seen; | 123 | QPDFObjGen::set seen; |
| 94 | for (std::list<QPDFObjectHandle> queue{fields.begin(), fields.end()}; !queue.empty(); | 124 | for (std::list<QPDFObjectHandle> queue{fields.begin(), fields.end()}; !queue.empty(); |
| @@ -139,6 +169,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> | @@ -139,6 +169,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> | ||
| 139 | void | 169 | void |
| 140 | QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remove) | 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 | auto acroform = qpdf.getRoot().getKey("/AcroForm"); | 178 | auto acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 143 | if (!acroform.isDictionary()) { | 179 | if (!acroform.isDictionary()) { |
| 144 | return; | 180 | return; |
| @@ -149,19 +185,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo | @@ -149,19 +185,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo | ||
| 149 | } | 185 | } |
| 150 | 186 | ||
| 151 | for (auto const& og: to_remove) { | 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 | for (auto aoh: it->second.annotations) { | 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 | auto const& name = it->second.name; | 193 | auto const& name = it->second.name; |
| 158 | if (!name.empty()) { | 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,16 +215,28 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remo | ||
| 179 | void | 215 | void |
| 180 | QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) | 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 | ff.setFieldAttribute("/T", name); | 224 | ff.setFieldAttribute("/T", name); |
| 183 | - m->traverseField(ff, ff["/Parent"], 0); | 225 | + traverseField(ff, ff["/Parent"], 0); |
| 184 | } | 226 | } |
| 185 | 227 | ||
| 186 | std::vector<QPDFFormFieldObjectHelper> | 228 | std::vector<QPDFFormFieldObjectHelper> |
| 187 | QPDFAcroFormDocumentHelper::getFormFields() | 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 | std::vector<QPDFFormFieldObjectHelper> result; | 238 | std::vector<QPDFFormFieldObjectHelper> result; |
| 191 | - for (auto const& [og, data]: m->fields_) { | 239 | + for (auto const& [og, data]: fields_) { |
| 192 | if (!data.annotations.empty()) { | 240 | if (!data.annotations.empty()) { |
| 193 | result.emplace_back(qpdf.getObject(og)); | 241 | result.emplace_back(qpdf.getObject(og)); |
| 194 | } | 242 | } |
| @@ -199,10 +247,16 @@ QPDFAcroFormDocumentHelper::getFormFields() | @@ -199,10 +247,16 @@ QPDFAcroFormDocumentHelper::getFormFields() | ||
| 199 | std::set<QPDFObjGen> | 247 | std::set<QPDFObjGen> |
| 200 | QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) | 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 | // Keep from creating an empty entry | 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 | return iter->second; | 260 | return iter->second; |
| 207 | } | 261 | } |
| 208 | return {}; | 262 | return {}; |
| @@ -211,11 +265,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) | @@ -211,11 +265,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) | ||
| 211 | std::vector<QPDFAnnotationObjectHelper> | 265 | std::vector<QPDFAnnotationObjectHelper> |
| 212 | QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) | 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 | std::vector<QPDFAnnotationObjectHelper> result; | 275 | std::vector<QPDFAnnotationObjectHelper> result; |
| 216 | QPDFObjGen og(h.getObjectHandle().getObjGen()); | 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 | return result; | 280 | return result; |
| 221 | } | 281 | } |
| @@ -235,7 +295,13 @@ AcroForm::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) | @@ -235,7 +295,13 @@ AcroForm::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) | ||
| 235 | std::vector<QPDFFormFieldObjectHelper> | 295 | std::vector<QPDFFormFieldObjectHelper> |
| 236 | QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) | 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 | QPDFObjGen::set todo; | 305 | QPDFObjGen::set todo; |
| 240 | std::vector<QPDFFormFieldObjectHelper> result; | 306 | std::vector<QPDFFormFieldObjectHelper> result; |
| 241 | for (auto& annot: getWidgetAnnotationsForPage(ph)) { | 307 | for (auto& annot: getWidgetAnnotationsForPage(ph)) { |
| @@ -250,14 +316,20 @@ QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) | @@ -250,14 +316,20 @@ QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) | ||
| 250 | QPDFFormFieldObjectHelper | 316 | QPDFFormFieldObjectHelper |
| 251 | QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) | 317 | QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) |
| 252 | { | 318 | { |
| 319 | + return m->getFieldForAnnotation(h); | ||
| 320 | +} | ||
| 321 | + | ||
| 322 | +QPDFFormFieldObjectHelper | ||
| 323 | +AcroForm::getFieldForAnnotation(QPDFAnnotationObjectHelper h) | ||
| 324 | +{ | ||
| 253 | QPDFObjectHandle oh = h.getObjectHandle(); | 325 | QPDFObjectHandle oh = h.getObjectHandle(); |
| 254 | if (!oh.isDictionaryOfType("", "/Widget")) { | 326 | if (!oh.isDictionaryOfType("", "/Widget")) { |
| 255 | return Null::temp(); | 327 | return Null::temp(); |
| 256 | } | 328 | } |
| 257 | - m->analyze(); | 329 | + analyze(); |
| 258 | QPDFObjGen og(oh.getObjGen()); | 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 | return Null::temp(); | 334 | return Null::temp(); |
| 263 | } | 335 | } |
| @@ -412,6 +484,12 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, | @@ -412,6 +484,12 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, | ||
| 412 | bool | 484 | bool |
| 413 | QPDFAcroFormDocumentHelper::getNeedAppearances() | 485 | QPDFAcroFormDocumentHelper::getNeedAppearances() |
| 414 | { | 486 | { |
| 487 | + return m->getNeedAppearances(); | ||
| 488 | +} | ||
| 489 | + | ||
| 490 | +bool | ||
| 491 | +AcroForm::getNeedAppearances() | ||
| 492 | +{ | ||
| 415 | bool result = false; | 493 | bool result = false; |
| 416 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); | 494 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 417 | if (acroform.isDictionary() && acroform.getKey("/NeedAppearances").isBool()) { | 495 | if (acroform.isDictionary() && acroform.getKey("/NeedAppearances").isBool()) { |
| @@ -423,6 +501,12 @@ QPDFAcroFormDocumentHelper::getNeedAppearances() | @@ -423,6 +501,12 @@ QPDFAcroFormDocumentHelper::getNeedAppearances() | ||
| 423 | void | 501 | void |
| 424 | QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) | 502 | QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) |
| 425 | { | 503 | { |
| 504 | + m->setNeedAppearances(val); | ||
| 505 | +} | ||
| 506 | + | ||
| 507 | +void | ||
| 508 | +AcroForm::setNeedAppearances(bool val) | ||
| 509 | +{ | ||
| 426 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); | 510 | QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); |
| 427 | if (!acroform.isDictionary()) { | 511 | if (!acroform.isDictionary()) { |
| 428 | qpdf.getRoot().warn( | 512 | qpdf.getRoot().warn( |
| @@ -440,6 +524,12 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) | @@ -440,6 +524,12 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) | ||
| 440 | void | 524 | void |
| 441 | QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() | 525 | QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() |
| 442 | { | 526 | { |
| 527 | + m->generateAppearancesIfNeeded(); | ||
| 528 | +} | ||
| 529 | + | ||
| 530 | +void | ||
| 531 | +AcroForm::generateAppearancesIfNeeded() | ||
| 532 | +{ | ||
| 443 | if (!getNeedAppearances()) { | 533 | if (!getNeedAppearances()) { |
| 444 | return; | 534 | return; |
| 445 | } | 535 | } |
| @@ -466,6 +556,12 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() | @@ -466,6 +556,12 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() | ||
| 466 | void | 556 | void |
| 467 | QPDFAcroFormDocumentHelper::disableDigitalSignatures() | 557 | QPDFAcroFormDocumentHelper::disableDigitalSignatures() |
| 468 | { | 558 | { |
| 559 | + m->disableDigitalSignatures(); | ||
| 560 | +} | ||
| 561 | + | ||
| 562 | +void | ||
| 563 | +AcroForm::disableDigitalSignatures() | ||
| 564 | +{ | ||
| 469 | qpdf.removeSecurityRestrictions(); | 565 | qpdf.removeSecurityRestrictions(); |
| 470 | std::set<QPDFObjGen> to_remove; | 566 | std::set<QPDFObjGen> to_remove; |
| 471 | auto fields = getFormFields(); | 567 | auto fields = getFormFields(); |
| @@ -744,7 +840,6 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -744,7 +840,6 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 744 | QPDF* from_qpdf, | 840 | QPDF* from_qpdf, |
| 745 | QPDFAcroFormDocumentHelper* from_afdh) | 841 | QPDFAcroFormDocumentHelper* from_afdh) |
| 746 | { | 842 | { |
| 747 | - Array old_annots = std::move(a_old_annots); | ||
| 748 | if (!from_qpdf) { | 843 | if (!from_qpdf) { |
| 749 | // Assume these are from the same QPDF. | 844 | // Assume these are from the same QPDF. |
| 750 | from_qpdf = &qpdf; | 845 | from_qpdf = &qpdf; |
| @@ -752,6 +847,23 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -752,6 +847,23 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 752 | } else if (from_qpdf != &qpdf && !from_afdh) { | 847 | } else if (from_qpdf != &qpdf && !from_afdh) { |
| 753 | from_afdh = &QPDFAcroFormDocumentHelper::get(*from_qpdf); | 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 | const bool foreign = from_qpdf != &qpdf; | 867 | const bool foreign = from_qpdf != &qpdf; |
| 756 | 868 | ||
| 757 | // It's possible that we will transform annotations that don't include any form fields. This | 869 | // It's possible that we will transform annotations that don't include any form fields. This |
| @@ -809,7 +921,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -809,7 +921,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 809 | // Ensure that we have a /DR that is an indirect | 921 | // Ensure that we have a /DR that is an indirect |
| 810 | // dictionary object. | 922 | // dictionary object. |
| 811 | if (!acroform) { | 923 | if (!acroform) { |
| 812 | - acroform = m->getOrCreateAcroForm(); | 924 | + acroform = getOrCreateAcroForm(); |
| 813 | } | 925 | } |
| 814 | dr = acroform["/DR"]; | 926 | dr = acroform["/DR"]; |
| 815 | if (!dr) { | 927 | if (!dr) { |
| @@ -874,7 +986,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -874,7 +986,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 874 | } | 986 | } |
| 875 | ++i; | 987 | ++i; |
| 876 | } | 988 | } |
| 877 | - m->adjustInheritedFields( | 989 | + adjustInheritedFields( |
| 878 | obj, override_da, from_default_da, override_q, from_default_q); | 990 | obj, override_da, from_default_da, override_q, from_default_q); |
| 879 | if (foreign) { | 991 | if (foreign) { |
| 880 | // Lazily initialize our /DR and the conflict map. | 992 | // Lazily initialize our /DR and the conflict map. |
| @@ -890,7 +1002,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -890,7 +1002,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 890 | obj.replace("/DR", dr); | 1002 | obj.replace("/DR", dr); |
| 891 | } | 1003 | } |
| 892 | if (obj["/DA"].isString() && !dr_map.empty()) { | 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,7 +1149,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 1037 | } | 1149 | } |
| 1038 | Dictionary resources = dict["/Resources"]; | 1150 | Dictionary resources = dict["/Resources"]; |
| 1039 | if (!dr_map.empty() && resources) { | 1151 | if (!dr_map.empty() && resources) { |
| 1040 | - m->adjustAppearanceStream(stream, dr_map); | 1152 | + adjustAppearanceStream(stream, dr_map); |
| 1041 | } | 1153 | } |
| 1042 | } | 1154 | } |
| 1043 | auto rect = cm.transformRectangle(annot["/Rect"].getArrayAsRectangle()); | 1155 | auto rect = cm.transformRectangle(annot["/Rect"].getArrayAsRectangle()); |
| @@ -1052,6 +1164,16 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( | @@ -1052,6 +1164,16 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( | ||
| 1052 | QPDFAcroFormDocumentHelper& from_afdh, | 1164 | QPDFAcroFormDocumentHelper& from_afdh, |
| 1053 | std::set<QPDFObjGen>* added_fields) | 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 | auto old_annots = from_page.getKey("/Annots"); | 1177 | auto old_annots = from_page.getKey("/Annots"); |
| 1056 | if (old_annots.empty() || !old_annots.isArray()) { | 1178 | if (old_annots.empty() || !old_annots.isArray()) { |
| 1057 | return; | 1179 | return; |
| @@ -1066,7 +1188,7 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( | @@ -1066,7 +1188,7 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( | ||
| 1066 | new_fields, | 1188 | new_fields, |
| 1067 | old_fields, | 1189 | old_fields, |
| 1068 | QPDFMatrix(), | 1190 | QPDFMatrix(), |
| 1069 | - &(from_afdh.getQPDF()), | 1191 | + &(from_afdh.qpdf), |
| 1070 | &from_afdh); | 1192 | &from_afdh); |
| 1071 | 1193 | ||
| 1072 | to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); | 1194 | to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); |
libqpdf/qpdf/AcroForm.hh
| @@ -11,6 +11,19 @@ class QPDFAnnotationObjectHelper; | @@ -11,6 +11,19 @@ class QPDFAnnotationObjectHelper; | ||
| 11 | 11 | ||
| 12 | namespace qpdf::impl | 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 | class AcroForm: public Doc::Common | 27 | class AcroForm: public Doc::Common |
| 15 | { | 28 | { |
| 16 | public: | 29 | public: |
| @@ -27,14 +40,61 @@ namespace qpdf::impl | @@ -27,14 +40,61 @@ namespace qpdf::impl | ||
| 27 | // We have to analyze up front. Otherwise, when we are adding annotations and fields, we | 40 | // We have to analyze up front. Otherwise, when we are adding annotations and fields, we |
| 28 | // are in a temporarily unstable configuration where some widget annotations are not | 41 | // are in a temporarily unstable configuration where some widget annotations are not |
| 29 | // reachable. | 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 | /// Retrieves a list of widget annotations for the specified page. | 99 | /// Retrieves a list of widget annotations for the specified page. |
| 40 | /// | 100 | /// |
| @@ -50,6 +110,86 @@ namespace qpdf::impl | @@ -50,6 +110,86 @@ namespace qpdf::impl | ||
| 50 | std::vector<QPDFAnnotationObjectHelper> | 110 | std::vector<QPDFAnnotationObjectHelper> |
| 51 | getWidgetAnnotationsForPage(QPDFPageObjectHelper page); | 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 | /// Analyzes the AcroForm structure in the PDF document and updates the internal | 193 | /// Analyzes the AcroForm structure in the PDF document and updates the internal |
| 54 | /// cache with the form fields and their corresponding widget annotations. | 194 | /// cache with the form fields and their corresponding widget annotations. |
| 55 | /// | 195 | /// |
| @@ -374,7 +514,7 @@ namespace qpdf::impl | @@ -374,7 +514,7 @@ namespace qpdf::impl | ||
| 374 | std::string mapping_name() const; | 514 | std::string mapping_name() const; |
| 375 | 515 | ||
| 376 | /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for | 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 | /// This function attempts to retrieve the `/V` attribute. If the `inherit` | 519 | /// This function attempts to retrieve the `/V` attribute. If the `inherit` |
| 380 | /// parameter is set to `true` and the `/V` is not found at the current level, the | 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,11 +689,11 @@ namespace qpdf::impl | ||
| 549 | /// name. | 689 | /// name. |
| 550 | /// | 690 | /// |
| 551 | /// The method accesses the AcroForm dictionary within the root object of the PDF document. | 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 | /// corresponding entry. Otherwise, it returns a default-constructed object handle. | 693 | /// corresponding entry. Otherwise, it returns a default-constructed object handle. |
| 554 | /// | 694 | /// |
| 555 | /// @param name The name of the form field to retrieve. | 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 | /// dictionary. | 697 | /// dictionary. |
| 558 | QPDFObjectHandle const& | 698 | QPDFObjectHandle const& |
| 559 | from_AcroForm(std::string const& name) const | 699 | from_AcroForm(std::string const& name) const |