Commit 8d36ca4d851ce62356a58f5455b23de5f84d8dbf

Authored by m-holger
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.
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&amp; qpdf) @@ -42,23 +42,41 @@ QPDFAcroFormDocumentHelper::get(QPDF&amp; 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&lt;QPDFObjectHandle&gt; @@ -139,6 +169,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector&lt;QPDFObjectHandle&gt;
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&lt;QPDFObjGen&gt; const&amp; to_remo @@ -149,19 +185,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set&lt;QPDFObjGen&gt; const&amp; 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&lt;QPDFObjGen&gt; const&amp; to_remo @@ -179,16 +215,28 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set&lt;QPDFObjGen&gt; const&amp; 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&amp; name) @@ -211,11 +265,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const&amp; 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&amp; parent, @@ -412,6 +484,12 @@ AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const&amp; 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