Commit c8f1e6bf14c4504fc52c9af8b62f31492f43127d

Authored by m-holger
Committed by GitHub
2 parents 8f455ffa d35c34d8

Merge pull request #1615 from m-holger/ffoh

Refactor QPDFAcroFormDocumentHelper
include/qpdf/QPDFAcroFormDocumentHelper.hh
@@ -225,21 +225,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper @@ -225,21 +225,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
225 std::set<QPDFObjGen>* new_fields = nullptr); 225 std::set<QPDFObjGen>* new_fields = nullptr);
226 226
227 private: 227 private:
228 - void analyze();  
229 - bool traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth);  
230 - QPDFObjectHandle getOrCreateAcroForm();  
231 - void adjustInheritedFields(  
232 - QPDFObjectHandle obj,  
233 - bool override_da,  
234 - std::string const& from_default_da,  
235 - bool override_q,  
236 - int from_default_q);  
237 - void adjustDefaultAppearances(  
238 - QPDFObjectHandle obj,  
239 - std::map<std::string, std::map<std::string, std::string>> const& dr_map);  
240 - void adjustAppearanceStream(  
241 - QPDFObjectHandle stream, std::map<std::string, std::map<std::string, std::string>> dr_map);  
242 - 228 + friend class QPDF::Doc;
243 class Members; 229 class Members;
244 230
245 std::shared_ptr<Members> m; 231 std::shared_ptr<Members> m;
libqpdf/QPDF.cc
@@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
11 #include <sstream> 11 #include <sstream>
12 #include <vector> 12 #include <vector>
13 13
  14 +#include <qpdf/AcroForm.hh>
14 #include <qpdf/FileInputSource.hh> 15 #include <qpdf/FileInputSource.hh>
15 #include <qpdf/InputSource_private.hh> 16 #include <qpdf/InputSource_private.hh>
16 #include <qpdf/OffsetInputSource.hh> 17 #include <qpdf/OffsetInputSource.hh>
@@ -147,6 +148,21 @@ QPDF::QPDF() : @@ -147,6 +148,21 @@ QPDF::QPDF() :
147 m->unique_id = unique_id.fetch_add(1ULL); 148 m->unique_id = unique_id.fetch_add(1ULL);
148 } 149 }
149 150
  151 +/// @brief Initializes the AcroForm functionality for the document.
  152 +/// @par
  153 +/// This method creates a unique instance of QPDFAcroFormDocumentHelper and associates it
  154 +/// with the document. It also updates the `acroform_` pointer to reference the AcroForm
  155 +/// instance managed by the helper.
  156 +///
  157 +/// The method has been separated out from `acroform` to avoid it being inlined
  158 +/// unnecessarily.
  159 +void
  160 +QPDF::Doc::init_acroform()
  161 +{
  162 + acroform_dh_ = std::make_unique<QPDFAcroFormDocumentHelper>(qpdf);
  163 + acroform_ = acroform_dh_->m.get();
  164 +}
  165 +
150 // Provide access to disconnect(). Disconnect will in due course be merged into the current ObjCache 166 // Provide access to disconnect(). Disconnect will in due course be merged into the current ObjCache
151 // (future Objects::Entry) to centralize all QPDF access to QPDFObject. 167 // (future Objects::Entry) to centralize all QPDF access to QPDFObject.
152 class Disconnect: BaseHandle 168 class Disconnect: BaseHandle
libqpdf/QPDFAcroFormDocumentHelper.cc
1 #include <qpdf/QPDFAcroFormDocumentHelper.hh> 1 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
2 2
  3 +#include <qpdf/AcroForm.hh>
  4 +
3 #include <qpdf/Pl_Buffer.hh> 5 #include <qpdf/Pl_Buffer.hh>
4 #include <qpdf/QPDFObjectHandle_private.hh> 6 #include <qpdf/QPDFObjectHandle_private.hh>
5 #include <qpdf/QPDFPageDocumentHelper.hh> 7 #include <qpdf/QPDFPageDocumentHelper.hh>
@@ -7,51 +9,38 @@ @@ -7,51 +9,38 @@
7 #include <qpdf/QTC.hh> 9 #include <qpdf/QTC.hh>
8 #include <qpdf/QUtil.hh> 10 #include <qpdf/QUtil.hh>
9 #include <qpdf/ResourceFinder.hh> 11 #include <qpdf/ResourceFinder.hh>
  12 +#include <qpdf/Util.hh>
10 13
11 #include <deque> 14 #include <deque>
12 #include <utility> 15 #include <utility>
13 16
14 using namespace qpdf; 17 using namespace qpdf;
  18 +using namespace qpdf::impl;
15 using namespace std::literals; 19 using namespace std::literals;
16 20
17 -class QPDFAcroFormDocumentHelper::Members  
18 -{  
19 - public:  
20 - Members() = default;  
21 - Members(Members const&) = delete;  
22 - ~Members() = default;  
23 -  
24 - struct FieldData  
25 - {  
26 - std::vector<QPDFAnnotationObjectHelper> annotations;  
27 - std::string name;  
28 - };  
29 -  
30 - bool cache_valid{false};  
31 - std::map<QPDFObjGen, FieldData> field_to;  
32 - std::map<QPDFObjGen, QPDFFormFieldObjectHelper> annotation_to_field;  
33 - std::map<std::string, std::set<QPDFObjGen>> name_to_fields;  
34 - std::set<QPDFObjGen> bad_fields;  
35 -}; 21 +using AcroForm = impl::AcroForm;
36 22
37 QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) : 23 QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) :
38 QPDFDocumentHelper(qpdf), 24 QPDFDocumentHelper(qpdf),
39 - m(std::make_shared<Members>()) 25 + m(std::make_shared<Members>(qpdf))
40 { 26 {
41 - // We have to analyze up front. Otherwise, when we are adding annotations and fields, we are in  
42 - // a temporarily unstable configuration where some widget annotations are not reachable.  
43 - analyze();  
44 } 27 }
45 28
46 QPDFAcroFormDocumentHelper& 29 QPDFAcroFormDocumentHelper&
47 QPDFAcroFormDocumentHelper::get(QPDF& qpdf) 30 QPDFAcroFormDocumentHelper::get(QPDF& qpdf)
48 { 31 {
49 - return qpdf.doc().acroform(); 32 + return qpdf.doc().acroform_dh();
50 } 33 }
51 34
52 void 35 void
53 QPDFAcroFormDocumentHelper::validate(bool repair) 36 QPDFAcroFormDocumentHelper::validate(bool repair)
54 { 37 {
  38 + m->validate(repair);
  39 +}
  40 +
  41 +void
  42 +AcroForm::validate(bool repair)
  43 +{
55 invalidateCache(); 44 invalidateCache();
56 analyze(); 45 analyze();
57 } 46 }
@@ -59,19 +48,33 @@ QPDFAcroFormDocumentHelper::validate(bool repair) @@ -59,19 +48,33 @@ QPDFAcroFormDocumentHelper::validate(bool repair)
59 void 48 void
60 QPDFAcroFormDocumentHelper::invalidateCache() 49 QPDFAcroFormDocumentHelper::invalidateCache()
61 { 50 {
62 - m->cache_valid = false;  
63 - m->field_to.clear();  
64 - m->annotation_to_field.clear(); 51 + m->invalidateCache();
  52 +}
  53 +
  54 +void
  55 +AcroForm::invalidateCache()
  56 +{
  57 + cache_valid_ = false;
  58 + fields_.clear();
  59 + annotation_to_field_.clear();
  60 + bad_fields_.clear();
  61 + name_to_fields_.clear();
65 } 62 }
66 63
67 bool 64 bool
68 QPDFAcroFormDocumentHelper::hasAcroForm() 65 QPDFAcroFormDocumentHelper::hasAcroForm()
69 { 66 {
  67 + return m->hasAcroForm();
  68 +}
  69 +
  70 +bool
  71 +AcroForm::hasAcroForm()
  72 +{
70 return qpdf.getRoot().hasKey("/AcroForm"); 73 return qpdf.getRoot().hasKey("/AcroForm");
71 } 74 }
72 75
73 QPDFObjectHandle 76 QPDFObjectHandle
74 -QPDFAcroFormDocumentHelper::getOrCreateAcroForm() 77 +AcroForm::getOrCreateAcroForm()
75 { 78 {
76 auto acroform = qpdf.getRoot().getKey("/AcroForm"); 79 auto acroform = qpdf.getRoot().getKey("/AcroForm");
77 if (!acroform.isDictionary()) { 80 if (!acroform.isDictionary()) {
@@ -84,6 +87,12 @@ QPDFAcroFormDocumentHelper::getOrCreateAcroForm() @@ -84,6 +87,12 @@ QPDFAcroFormDocumentHelper::getOrCreateAcroForm()
84 void 87 void
85 QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) 88 QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
86 { 89 {
  90 + m->addFormField(ff);
  91 +}
  92 +
  93 +void
  94 +AcroForm::addFormField(QPDFFormFieldObjectHelper ff)
  95 +{
87 auto acroform = getOrCreateAcroForm(); 96 auto acroform = getOrCreateAcroForm();
88 auto fields = acroform.getKey("/Fields"); 97 auto fields = acroform.getKey("/Fields");
89 if (!fields.isArray()) { 98 if (!fields.isArray()) {
@@ -96,6 +105,12 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) @@ -96,6 +105,12 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
96 void 105 void
97 QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields) 106 QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields)
98 { 107 {
  108 + m->addAndRenameFormFields(fields);
  109 +}
  110 +
  111 +void
  112 +AcroForm::addAndRenameFormFields(std::vector<QPDFObjectHandle> fields)
  113 +{
99 analyze(); 114 analyze();
100 std::map<std::string, std::string> renames; 115 std::map<std::string, std::string> renames;
101 QPDFObjGen::set seen; 116 QPDFObjGen::set seen;
@@ -147,6 +162,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector&lt;QPDFObjectHandle&gt; @@ -147,6 +162,12 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(std::vector&lt;QPDFObjectHandle&gt;
147 void 162 void
148 QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remove) 163 QPDFAcroFormDocumentHelper::removeFormFields(std::set<QPDFObjGen> const& to_remove)
149 { 164 {
  165 + m->removeFormFields(to_remove);
  166 +}
  167 +
  168 +void
  169 +AcroForm::removeFormFields(std::set<QPDFObjGen> const& to_remove)
  170 +{
150 auto acroform = qpdf.getRoot().getKey("/AcroForm"); 171 auto acroform = qpdf.getRoot().getKey("/AcroForm");
151 if (!acroform.isDictionary()) { 172 if (!acroform.isDictionary()) {
152 return; 173 return;
@@ -157,19 +178,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set&lt;QPDFObjGen&gt; const&amp; to_remo @@ -157,19 +178,19 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set&lt;QPDFObjGen&gt; const&amp; to_remo
157 } 178 }
158 179
159 for (auto const& og: to_remove) { 180 for (auto const& og: to_remove) {
160 - auto it = m->field_to.find(og);  
161 - if (it != m->field_to.end()) { 181 + auto it = fields_.find(og);
  182 + if (it != fields_.end()) {
162 for (auto aoh: it->second.annotations) { 183 for (auto aoh: it->second.annotations) {
163 - m->annotation_to_field.erase(aoh.getObjectHandle().getObjGen()); 184 + annotation_to_field_.erase(aoh.getObjectHandle().getObjGen());
164 } 185 }
165 auto const& name = it->second.name; 186 auto const& name = it->second.name;
166 if (!name.empty()) { 187 if (!name.empty()) {
167 - m->name_to_fields[name].erase(og);  
168 - if (m->name_to_fields[name].empty()) {  
169 - m->name_to_fields.erase(name); 188 + name_to_fields_[name].erase(og);
  189 + if (name_to_fields_[name].empty()) {
  190 + name_to_fields_.erase(name);
170 } 191 }
171 } 192 }
172 - m->field_to.erase(og); 193 + fields_.erase(og);
173 } 194 }
174 } 195 }
175 196
@@ -187,6 +208,12 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set&lt;QPDFObjGen&gt; const&amp; to_remo @@ -187,6 +208,12 @@ QPDFAcroFormDocumentHelper::removeFormFields(std::set&lt;QPDFObjGen&gt; const&amp; to_remo
187 void 208 void
188 QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) 209 QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name)
189 { 210 {
  211 + m->setFormFieldName(ff, name);
  212 +}
  213 +
  214 +void
  215 +AcroForm::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name)
  216 +{
190 ff.setFieldAttribute("/T", name); 217 ff.setFieldAttribute("/T", name);
191 traverseField(ff, ff["/Parent"], 0); 218 traverseField(ff, ff["/Parent"], 0);
192 } 219 }
@@ -194,9 +221,15 @@ QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std:: @@ -194,9 +221,15 @@ QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::
194 std::vector<QPDFFormFieldObjectHelper> 221 std::vector<QPDFFormFieldObjectHelper>
195 QPDFAcroFormDocumentHelper::getFormFields() 222 QPDFAcroFormDocumentHelper::getFormFields()
196 { 223 {
  224 + return m->getFormFields();
  225 +}
  226 +
  227 +std::vector<QPDFFormFieldObjectHelper>
  228 +AcroForm::getFormFields()
  229 +{
197 analyze(); 230 analyze();
198 std::vector<QPDFFormFieldObjectHelper> result; 231 std::vector<QPDFFormFieldObjectHelper> result;
199 - for (auto const& [og, data]: m->field_to) { 232 + for (auto const& [og, data]: fields_) {
200 if (!data.annotations.empty()) { 233 if (!data.annotations.empty()) {
201 result.emplace_back(qpdf.getObject(og)); 234 result.emplace_back(qpdf.getObject(og));
202 } 235 }
@@ -207,10 +240,16 @@ QPDFAcroFormDocumentHelper::getFormFields() @@ -207,10 +240,16 @@ QPDFAcroFormDocumentHelper::getFormFields()
207 std::set<QPDFObjGen> 240 std::set<QPDFObjGen>
208 QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) 241 QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name)
209 { 242 {
  243 + return m->getFieldsWithQualifiedName(name);
  244 +}
  245 +
  246 +std::set<QPDFObjGen>
  247 +AcroForm::getFieldsWithQualifiedName(std::string const& name)
  248 +{
210 analyze(); 249 analyze();
211 // Keep from creating an empty entry 250 // Keep from creating an empty entry
212 - auto iter = m->name_to_fields.find(name);  
213 - if (iter != m->name_to_fields.end()) { 251 + auto iter = name_to_fields_.find(name);
  252 + if (iter != name_to_fields_.end()) {
214 return iter->second; 253 return iter->second;
215 } 254 }
216 return {}; 255 return {};
@@ -219,11 +258,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const&amp; name) @@ -219,11 +258,17 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const&amp; name)
219 std::vector<QPDFAnnotationObjectHelper> 258 std::vector<QPDFAnnotationObjectHelper>
220 QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) 259 QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h)
221 { 260 {
  261 + return m->getAnnotationsForField(h);
  262 +}
  263 +
  264 +std::vector<QPDFAnnotationObjectHelper>
  265 +AcroForm::getAnnotationsForField(QPDFFormFieldObjectHelper h)
  266 +{
222 analyze(); 267 analyze();
223 std::vector<QPDFAnnotationObjectHelper> result; 268 std::vector<QPDFAnnotationObjectHelper> result;
224 QPDFObjGen og(h.getObjectHandle().getObjGen()); 269 QPDFObjGen og(h.getObjectHandle().getObjGen());
225 - if (m->field_to.contains(og)) {  
226 - result = m->field_to[og].annotations; 270 + if (fields_.contains(og)) {
  271 + result = fields_[og].annotations;
227 } 272 }
228 return result; 273 return result;
229 } 274 }
@@ -231,12 +276,24 @@ QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h) @@ -231,12 +276,24 @@ QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h)
231 std::vector<QPDFAnnotationObjectHelper> 276 std::vector<QPDFAnnotationObjectHelper>
232 QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) 277 QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h)
233 { 278 {
  279 + return m->getWidgetAnnotationsForPage(h);
  280 +}
  281 +
  282 +std::vector<QPDFAnnotationObjectHelper>
  283 +AcroForm::getWidgetAnnotationsForPage(QPDFPageObjectHelper h)
  284 +{
234 return h.getAnnotations("/Widget"); 285 return h.getAnnotations("/Widget");
235 } 286 }
236 287
237 std::vector<QPDFFormFieldObjectHelper> 288 std::vector<QPDFFormFieldObjectHelper>
238 QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) 289 QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph)
239 { 290 {
  291 + return m->getFormFieldsForPage(ph);
  292 +}
  293 +
  294 +std::vector<QPDFFormFieldObjectHelper>
  295 +AcroForm::getFormFieldsForPage(QPDFPageObjectHelper ph)
  296 +{
240 analyze(); 297 analyze();
241 QPDFObjGen::set todo; 298 QPDFObjGen::set todo;
242 std::vector<QPDFFormFieldObjectHelper> result; 299 std::vector<QPDFFormFieldObjectHelper> result;
@@ -252,25 +309,31 @@ QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) @@ -252,25 +309,31 @@ QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph)
252 QPDFFormFieldObjectHelper 309 QPDFFormFieldObjectHelper
253 QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) 310 QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h)
254 { 311 {
  312 + return m->getFieldForAnnotation(h);
  313 +}
  314 +
  315 +QPDFFormFieldObjectHelper
  316 +AcroForm::getFieldForAnnotation(QPDFAnnotationObjectHelper h)
  317 +{
255 QPDFObjectHandle oh = h.getObjectHandle(); 318 QPDFObjectHandle oh = h.getObjectHandle();
256 if (!oh.isDictionaryOfType("", "/Widget")) { 319 if (!oh.isDictionaryOfType("", "/Widget")) {
257 return Null::temp(); 320 return Null::temp();
258 } 321 }
259 analyze(); 322 analyze();
260 QPDFObjGen og(oh.getObjGen()); 323 QPDFObjGen og(oh.getObjGen());
261 - if (m->annotation_to_field.contains(og)) {  
262 - return m->annotation_to_field[og]; 324 + if (annotation_to_field_.contains(og)) {
  325 + return annotation_to_field_[og];
263 } 326 }
264 return Null::temp(); 327 return Null::temp();
265 } 328 }
266 329
267 void 330 void
268 -QPDFAcroFormDocumentHelper::analyze() 331 +AcroForm::analyze()
269 { 332 {
270 - if (m->cache_valid) { 333 + if (cache_valid_) {
271 return; 334 return;
272 } 335 }
273 - m->cache_valid = true; 336 + cache_valid_ = true;
274 QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); 337 QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm");
275 if (!(acroform.isDictionary() && acroform.hasKey("/Fields"))) { 338 if (!(acroform.isDictionary() && acroform.hasKey("/Fields"))) {
276 return; 339 return;
@@ -293,11 +356,11 @@ QPDFAcroFormDocumentHelper::analyze() @@ -293,11 +356,11 @@ QPDFAcroFormDocumentHelper::analyze()
293 // a file that contains this kind of error will probably not 356 // a file that contains this kind of error will probably not
294 // actually work with most viewers. 357 // actually work with most viewers.
295 358
296 - for (auto const& ph: QPDFPageDocumentHelper(qpdf).getAllPages()) { 359 + for (QPDFPageObjectHelper ph: pages) {
297 for (auto const& iter: getWidgetAnnotationsForPage(ph)) { 360 for (auto const& iter: getWidgetAnnotationsForPage(ph)) {
298 QPDFObjectHandle annot(iter.getObjectHandle()); 361 QPDFObjectHandle annot(iter.getObjectHandle());
299 QPDFObjGen og(annot.getObjGen()); 362 QPDFObjGen og(annot.getObjGen());
300 - if (!m->annotation_to_field.contains(og)) { 363 + if (!annotation_to_field_.contains(og)) {
301 // This is not supposed to happen, but it's easy enough for us to handle this case. 364 // This is not supposed to happen, but it's easy enough for us to handle this case.
302 // Treat the annotation as its own field. This could allow qpdf to sensibly handle a 365 // Treat the annotation as its own field. This could allow qpdf to sensibly handle a
303 // case such as a PDF creator adding a self-contained annotation (merged with the 366 // case such as a PDF creator adding a self-contained annotation (merged with the
@@ -306,16 +369,15 @@ QPDFAcroFormDocumentHelper::analyze() @@ -306,16 +369,15 @@ QPDFAcroFormDocumentHelper::analyze()
306 annot.warn( 369 annot.warn(
307 "this widget annotation is not reachable from /AcroForm in the document " 370 "this widget annotation is not reachable from /AcroForm in the document "
308 "catalog"); 371 "catalog");
309 - m->annotation_to_field[og] = QPDFFormFieldObjectHelper(annot);  
310 - m->field_to[og].annotations.emplace_back(annot); 372 + annotation_to_field_[og] = QPDFFormFieldObjectHelper(annot);
  373 + fields_[og].annotations.emplace_back(annot);
311 } 374 }
312 } 375 }
313 } 376 }
314 } 377 }
315 378
316 bool 379 bool
317 -QPDFAcroFormDocumentHelper::traverseField(  
318 - QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth) 380 +AcroForm::traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth)
319 { 381 {
320 if (depth > 100) { 382 if (depth > 100) {
321 // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that 383 // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that
@@ -339,8 +401,7 @@ QPDFAcroFormDocumentHelper::traverseField( @@ -339,8 +401,7 @@ QPDFAcroFormDocumentHelper::traverseField(
339 return false; 401 return false;
340 } 402 }
341 QPDFObjGen og(field.getObjGen()); 403 QPDFObjGen og(field.getObjGen());
342 - if (m->field_to.contains(og) || m->annotation_to_field.contains(og) ||  
343 - m->bad_fields.contains(og)) { 404 + if (fields_.contains(og) || annotation_to_field_.contains(og) || bad_fields_.contains(og)) {
344 field.warn("loop detected while traversing /AcroForm"); 405 field.warn("loop detected while traversing /AcroForm");
345 return false; 406 return false;
346 } 407 }
@@ -368,8 +429,8 @@ QPDFAcroFormDocumentHelper::traverseField( @@ -368,8 +429,8 @@ QPDFAcroFormDocumentHelper::traverseField(
368 429
369 if (is_annotation) { 430 if (is_annotation) {
370 QPDFObjectHandle our_field = (is_field ? field : parent); 431 QPDFObjectHandle our_field = (is_field ? field : parent);
371 - m->field_to[our_field.getObjGen()].annotations.emplace_back(field);  
372 - m->annotation_to_field[og] = QPDFFormFieldObjectHelper(our_field); 432 + fields_[our_field.getObjGen()].annotations.emplace_back(field);
  433 + annotation_to_field_[og] = QPDFFormFieldObjectHelper(our_field);
373 } 434 }
374 435
375 if (is_field && depth != 0 && field["/Parent"] != parent) { 436 if (is_field && depth != 0 && field["/Parent"] != parent) {
@@ -392,22 +453,22 @@ QPDFAcroFormDocumentHelper::traverseField( @@ -392,22 +453,22 @@ QPDFAcroFormDocumentHelper::traverseField(
392 if (is_field && field.hasKey("/T")) { 453 if (is_field && field.hasKey("/T")) {
393 QPDFFormFieldObjectHelper foh(field); 454 QPDFFormFieldObjectHelper foh(field);
394 std::string name = foh.getFullyQualifiedName(); 455 std::string name = foh.getFullyQualifiedName();
395 - auto old = m->field_to.find(og);  
396 - if (old != m->field_to.end() && !old->second.name.empty()) { 456 + auto old = fields_.find(og);
  457 + if (old != fields_.end() && !old->second.name.empty()) {
397 // We might be updating after a name change, so remove any old information 458 // We might be updating after a name change, so remove any old information
398 - m->name_to_fields[old->second.name].erase(og); 459 + name_to_fields_[old->second.name].erase(og);
399 } 460 }
400 - m->field_to[og].name = name;  
401 - m->name_to_fields[name].insert(og); 461 + fields_[og].name = name;
  462 + name_to_fields_[name].insert(og);
402 } 463 }
403 464
404 for (auto const& kid: Kids) { 465 for (auto const& kid: Kids) {
405 - if (m->bad_fields.contains(kid)) { 466 + if (bad_fields_.contains(kid)) {
406 continue; 467 continue;
407 } 468 }
408 469
409 if (!traverseField(kid, field, 1 + depth)) { 470 if (!traverseField(kid, field, 1 + depth)) {
410 - m->bad_fields.insert(kid); 471 + bad_fields_.insert(kid);
411 } 472 }
412 } 473 }
413 return true; 474 return true;
@@ -416,6 +477,12 @@ QPDFAcroFormDocumentHelper::traverseField( @@ -416,6 +477,12 @@ QPDFAcroFormDocumentHelper::traverseField(
416 bool 477 bool
417 QPDFAcroFormDocumentHelper::getNeedAppearances() 478 QPDFAcroFormDocumentHelper::getNeedAppearances()
418 { 479 {
  480 + return m->getNeedAppearances();
  481 +}
  482 +
  483 +bool
  484 +AcroForm::getNeedAppearances()
  485 +{
419 bool result = false; 486 bool result = false;
420 QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); 487 QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm");
421 if (acroform.isDictionary() && acroform.getKey("/NeedAppearances").isBool()) { 488 if (acroform.isDictionary() && acroform.getKey("/NeedAppearances").isBool()) {
@@ -427,6 +494,12 @@ QPDFAcroFormDocumentHelper::getNeedAppearances() @@ -427,6 +494,12 @@ QPDFAcroFormDocumentHelper::getNeedAppearances()
427 void 494 void
428 QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) 495 QPDFAcroFormDocumentHelper::setNeedAppearances(bool val)
429 { 496 {
  497 + m->setNeedAppearances(val);
  498 +}
  499 +
  500 +void
  501 +AcroForm::setNeedAppearances(bool val)
  502 +{
430 QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); 503 QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm");
431 if (!acroform.isDictionary()) { 504 if (!acroform.isDictionary()) {
432 qpdf.getRoot().warn( 505 qpdf.getRoot().warn(
@@ -444,6 +517,12 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val) @@ -444,6 +517,12 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val)
444 void 517 void
445 QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() 518 QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded()
446 { 519 {
  520 + m->generateAppearancesIfNeeded();
  521 +}
  522 +
  523 +void
  524 +AcroForm::generateAppearancesIfNeeded()
  525 +{
447 if (!getNeedAppearances()) { 526 if (!getNeedAppearances()) {
448 return; 527 return;
449 } 528 }
@@ -470,6 +549,12 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded() @@ -470,6 +549,12 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded()
470 void 549 void
471 QPDFAcroFormDocumentHelper::disableDigitalSignatures() 550 QPDFAcroFormDocumentHelper::disableDigitalSignatures()
472 { 551 {
  552 + m->disableDigitalSignatures();
  553 +}
  554 +
  555 +void
  556 +AcroForm::disableDigitalSignatures()
  557 +{
473 qpdf.removeSecurityRestrictions(); 558 qpdf.removeSecurityRestrictions();
474 std::set<QPDFObjGen> to_remove; 559 std::set<QPDFObjGen> to_remove;
475 auto fields = getFormFields(); 560 auto fields = getFormFields();
@@ -491,7 +576,7 @@ QPDFAcroFormDocumentHelper::disableDigitalSignatures() @@ -491,7 +576,7 @@ QPDFAcroFormDocumentHelper::disableDigitalSignatures()
491 } 576 }
492 577
493 void 578 void
494 -QPDFAcroFormDocumentHelper::adjustInheritedFields( 579 +AcroForm::adjustInheritedFields(
495 QPDFObjectHandle obj, 580 QPDFObjectHandle obj,
496 bool override_da, 581 bool override_da,
497 std::string const& from_default_da, 582 std::string const& from_default_da,
@@ -598,7 +683,7 @@ ResourceReplacer::handleToken(QPDFTokenizer::Token const&amp; token) @@ -598,7 +683,7 @@ ResourceReplacer::handleToken(QPDFTokenizer::Token const&amp; token)
598 } 683 }
599 684
600 void 685 void
601 -QPDFAcroFormDocumentHelper::adjustDefaultAppearances( 686 +AcroForm::adjustDefaultAppearances(
602 QPDFObjectHandle obj, std::map<std::string, std::map<std::string, std::string>> const& dr_map) 687 QPDFObjectHandle obj, std::map<std::string, std::map<std::string, std::string>> const& dr_map)
603 { 688 {
604 // This method is called on a field that has been copied from another file but whose /DA still 689 // This method is called on a field that has been copied from another file but whose /DA still
@@ -656,7 +741,7 @@ QPDFAcroFormDocumentHelper::adjustDefaultAppearances( @@ -656,7 +741,7 @@ QPDFAcroFormDocumentHelper::adjustDefaultAppearances(
656 } 741 }
657 742
658 void 743 void
659 -QPDFAcroFormDocumentHelper::adjustAppearanceStream( 744 +AcroForm::adjustAppearanceStream(
660 QPDFObjectHandle stream, std::map<std::string, std::map<std::string, std::string>> dr_map) 745 QPDFObjectHandle stream, std::map<std::string, std::map<std::string, std::string>> dr_map)
661 { 746 {
662 // We don't have to modify appearance streams or their resource dictionaries for them to display 747 // We don't have to modify appearance streams or their resource dictionaries for them to display
@@ -748,7 +833,6 @@ QPDFAcroFormDocumentHelper::transformAnnotations( @@ -748,7 +833,6 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
748 QPDF* from_qpdf, 833 QPDF* from_qpdf,
749 QPDFAcroFormDocumentHelper* from_afdh) 834 QPDFAcroFormDocumentHelper* from_afdh)
750 { 835 {
751 - Array old_annots = std::move(a_old_annots);  
752 if (!from_qpdf) { 836 if (!from_qpdf) {
753 // Assume these are from the same QPDF. 837 // Assume these are from the same QPDF.
754 from_qpdf = &qpdf; 838 from_qpdf = &qpdf;
@@ -756,6 +840,23 @@ QPDFAcroFormDocumentHelper::transformAnnotations( @@ -756,6 +840,23 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
756 } else if (from_qpdf != &qpdf && !from_afdh) { 840 } else if (from_qpdf != &qpdf && !from_afdh) {
757 from_afdh = &QPDFAcroFormDocumentHelper::get(*from_qpdf); 841 from_afdh = &QPDFAcroFormDocumentHelper::get(*from_qpdf);
758 } 842 }
  843 + m->transformAnnotations(
  844 + a_old_annots, new_annots, new_fields, old_fields, cm, from_qpdf, from_afdh->m.get());
  845 +}
  846 +
  847 +void
  848 +AcroForm::transformAnnotations(
  849 + QPDFObjectHandle a_old_annots,
  850 + std::vector<QPDFObjectHandle>& new_annots,
  851 + std::vector<QPDFObjectHandle>& new_fields,
  852 + std::set<QPDFObjGen>& old_fields,
  853 + QPDFMatrix const& cm,
  854 + QPDF* from_qpdf,
  855 + AcroForm* from_afdh)
  856 +{
  857 + qpdf_expect(from_qpdf);
  858 + qpdf_expect(from_afdh);
  859 + Array old_annots = std::move(a_old_annots);
759 const bool foreign = from_qpdf != &qpdf; 860 const bool foreign = from_qpdf != &qpdf;
760 861
761 // It's possible that we will transform annotations that don't include any form fields. This 862 // It's possible that we will transform annotations that don't include any form fields. This
@@ -1056,6 +1157,16 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( @@ -1056,6 +1157,16 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations(
1056 QPDFAcroFormDocumentHelper& from_afdh, 1157 QPDFAcroFormDocumentHelper& from_afdh,
1057 std::set<QPDFObjGen>* added_fields) 1158 std::set<QPDFObjGen>* added_fields)
1058 { 1159 {
  1160 + m->fixCopiedAnnotations(to_page, from_page, *from_afdh.m, added_fields);
  1161 +}
  1162 +
  1163 +void
  1164 +AcroForm::fixCopiedAnnotations(
  1165 + QPDFObjectHandle to_page,
  1166 + QPDFObjectHandle from_page,
  1167 + AcroForm& from_afdh,
  1168 + std::set<QPDFObjGen>* added_fields)
  1169 +{
1059 auto old_annots = from_page.getKey("/Annots"); 1170 auto old_annots = from_page.getKey("/Annots");
1060 if (old_annots.empty() || !old_annots.isArray()) { 1171 if (old_annots.empty() || !old_annots.isArray()) {
1061 return; 1172 return;
@@ -1070,7 +1181,7 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations( @@ -1070,7 +1181,7 @@ QPDFAcroFormDocumentHelper::fixCopiedAnnotations(
1070 new_fields, 1181 new_fields,
1071 old_fields, 1182 old_fields,
1072 QPDFMatrix(), 1183 QPDFMatrix(),
1073 - &(from_afdh.getQPDF()), 1184 + &(from_afdh.qpdf),
1074 &from_afdh); 1185 &from_afdh);
1075 1186
1076 to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); 1187 to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots));
libqpdf/QPDFFormFieldObjectHelper.cc
1 #include <qpdf/QPDFFormFieldObjectHelper.hh> 1 #include <qpdf/QPDFFormFieldObjectHelper.hh>
2 2
3 -#include <qpdf/FormField.hh> 3 +#include <qpdf/AcroForm.hh>
4 4
5 #include <qpdf/Pl_QPDFTokenizer.hh> 5 #include <qpdf/Pl_QPDFTokenizer.hh>
6 #include <qpdf/QIntC.hh> 6 #include <qpdf/QIntC.hh>
@@ -16,15 +16,15 @@ @@ -16,15 +16,15 @@
16 16
17 using namespace qpdf; 17 using namespace qpdf;
18 18
19 -using FormField = qpdf::impl::FormField; 19 +using FormNode = qpdf::impl::FormNode;
20 20
21 -const QPDFObjectHandle FormField::null_oh; 21 +const QPDFObjectHandle FormNode::null_oh;
22 22
23 -class QPDFFormFieldObjectHelper::Members: public FormField 23 +class QPDFFormFieldObjectHelper::Members: public FormNode
24 { 24 {
25 public: 25 public:
26 Members(QPDFObjectHandle const& oh) : 26 Members(QPDFObjectHandle const& oh) :
27 - FormField(oh) 27 + FormNode(oh)
28 { 28 {
29 } 29 }
30 }; 30 };
@@ -59,8 +59,8 @@ QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) @@ -59,8 +59,8 @@ QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different)
59 return Null::if_null(m->root_field(is_different).oh()); 59 return Null::if_null(m->root_field(is_different).oh());
60 } 60 }
61 61
62 -FormField  
63 -FormField::root_field(bool* is_different) 62 +FormNode
  63 +FormNode::root_field(bool* is_different)
64 { 64 {
65 if (is_different) { 65 if (is_different) {
66 *is_different = false; 66 *is_different = false;
@@ -87,7 +87,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const&amp; name) @@ -87,7 +87,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const&amp; name)
87 } 87 }
88 88
89 QPDFObjectHandle const& 89 QPDFObjectHandle const&
90 -FormField::inherited(std::string const& name, bool acroform) const 90 +FormNode::inherited(std::string const& name, bool acroform) const
91 { 91 {
92 if (!obj) { 92 if (!obj) {
93 return null_oh; 93 return null_oh;
@@ -111,7 +111,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const&amp; n @@ -111,7 +111,7 @@ QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const&amp; n
111 } 111 }
112 112
113 std::string 113 std::string
114 -FormField::inheritable_string(std::string const& name) const 114 +FormNode::inheritable_string(std::string const& name) const
115 { 115 {
116 if (auto fv = inheritable_value<String>(name)) { 116 if (auto fv = inheritable_value<String>(name)) {
117 return fv.utf8_value(); 117 return fv.utf8_value();
@@ -144,7 +144,7 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() @@ -144,7 +144,7 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName()
144 } 144 }
145 145
146 std::string 146 std::string
147 -FormField::fully_qualified_name() const 147 +FormNode::fully_qualified_name() const
148 { 148 {
149 std::string result; 149 std::string result;
150 auto node = *this; 150 auto node = *this;
@@ -169,7 +169,7 @@ QPDFFormFieldObjectHelper::getPartialName() @@ -169,7 +169,7 @@ QPDFFormFieldObjectHelper::getPartialName()
169 } 169 }
170 170
171 std::string 171 std::string
172 -FormField::partial_name() const 172 +FormNode::partial_name() const
173 { 173 {
174 if (auto pn = T()) { 174 if (auto pn = T()) {
175 return pn.utf8_value(); 175 return pn.utf8_value();
@@ -184,7 +184,7 @@ QPDFFormFieldObjectHelper::getAlternativeName() @@ -184,7 +184,7 @@ QPDFFormFieldObjectHelper::getAlternativeName()
184 } 184 }
185 185
186 std::string 186 std::string
187 -FormField::alternative_name() const 187 +FormNode::alternative_name() const
188 { 188 {
189 if (auto an = TU()) { 189 if (auto an = TU()) {
190 return an.utf8_value(); 190 return an.utf8_value();
@@ -199,7 +199,7 @@ QPDFFormFieldObjectHelper::getMappingName() @@ -199,7 +199,7 @@ QPDFFormFieldObjectHelper::getMappingName()
199 } 199 }
200 200
201 std::string 201 std::string
202 -FormField::mapping_name() const 202 +FormNode::mapping_name() const
203 { 203 {
204 if (auto mn = TM()) { 204 if (auto mn = TM()) {
205 return mn.utf8_value(); 205 return mn.utf8_value();
@@ -220,7 +220,7 @@ QPDFFormFieldObjectHelper::getValueAsString() @@ -220,7 +220,7 @@ QPDFFormFieldObjectHelper::getValueAsString()
220 } 220 }
221 221
222 std::string 222 std::string
223 -FormField::value() const 223 +FormNode::value() const
224 { 224 {
225 return inheritable_string("/V"); 225 return inheritable_string("/V");
226 } 226 }
@@ -238,7 +238,7 @@ QPDFFormFieldObjectHelper::getDefaultValueAsString() @@ -238,7 +238,7 @@ QPDFFormFieldObjectHelper::getDefaultValueAsString()
238 } 238 }
239 239
240 std::string 240 std::string
241 -FormField::default_value() const 241 +FormNode::default_value() const
242 { 242 {
243 return inheritable_string("/DV"); 243 return inheritable_string("/DV");
244 } 244 }
@@ -250,7 +250,7 @@ QPDFFormFieldObjectHelper::getDefaultResources() @@ -250,7 +250,7 @@ QPDFFormFieldObjectHelper::getDefaultResources()
250 } 250 }
251 251
252 QPDFObjectHandle 252 QPDFObjectHandle
253 -FormField::getDefaultResources() 253 +FormNode::getDefaultResources()
254 { 254 {
255 return from_AcroForm("/DR"); 255 return from_AcroForm("/DR");
256 } 256 }
@@ -262,7 +262,7 @@ QPDFFormFieldObjectHelper::getDefaultAppearance() @@ -262,7 +262,7 @@ QPDFFormFieldObjectHelper::getDefaultAppearance()
262 } 262 }
263 263
264 std::string 264 std::string
265 -FormField::default_appearance() const 265 +FormNode::default_appearance() const
266 { 266 {
267 if (auto DA = inheritable_value<String>("/DA")) { 267 if (auto DA = inheritable_value<String>("/DA")) {
268 return DA.utf8_value(); 268 return DA.utf8_value();
@@ -280,7 +280,7 @@ QPDFFormFieldObjectHelper::getQuadding() @@ -280,7 +280,7 @@ QPDFFormFieldObjectHelper::getQuadding()
280 } 280 }
281 281
282 int 282 int
283 -FormField::getQuadding() 283 +FormNode::getQuadding()
284 { 284 {
285 auto fv = inheritable_value<QPDFObjectHandle>("/Q"); 285 auto fv = inheritable_value<QPDFObjectHandle>("/Q");
286 bool looked_in_acroform = false; 286 bool looked_in_acroform = false;
@@ -302,7 +302,7 @@ QPDFFormFieldObjectHelper::getFlags() @@ -302,7 +302,7 @@ QPDFFormFieldObjectHelper::getFlags()
302 } 302 }
303 303
304 int 304 int
305 -FormField::getFlags() 305 +FormNode::getFlags()
306 { 306 {
307 auto f = inheritable_value<QPDFObjectHandle>("/Ff"); 307 auto f = inheritable_value<QPDFObjectHandle>("/Ff");
308 return f.isInteger() ? f.getIntValueAsInt() : 0; 308 return f.isInteger() ? f.getIntValueAsInt() : 0;
@@ -315,7 +315,7 @@ QPDFFormFieldObjectHelper::isText() @@ -315,7 +315,7 @@ QPDFFormFieldObjectHelper::isText()
315 } 315 }
316 316
317 bool 317 bool
318 -FormField::isText() 318 +FormNode::isText()
319 { 319 {
320 return FT() == "/Tx"; 320 return FT() == "/Tx";
321 } 321 }
@@ -327,7 +327,7 @@ QPDFFormFieldObjectHelper::isCheckbox() @@ -327,7 +327,7 @@ QPDFFormFieldObjectHelper::isCheckbox()
327 } 327 }
328 328
329 bool 329 bool
330 -FormField::isCheckbox() 330 +FormNode::isCheckbox()
331 { 331 {
332 return FT() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0; 332 return FT() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0;
333 } 333 }
@@ -339,7 +339,7 @@ QPDFFormFieldObjectHelper::isChecked() @@ -339,7 +339,7 @@ QPDFFormFieldObjectHelper::isChecked()
339 } 339 }
340 340
341 bool 341 bool
342 -FormField::isChecked() 342 +FormNode::isChecked()
343 { 343 {
344 return isCheckbox() && V<Name>() != "/Off"; 344 return isCheckbox() && V<Name>() != "/Off";
345 } 345 }
@@ -351,7 +351,7 @@ QPDFFormFieldObjectHelper::isRadioButton() @@ -351,7 +351,7 @@ QPDFFormFieldObjectHelper::isRadioButton()
351 } 351 }
352 352
353 bool 353 bool
354 -FormField::isRadioButton() 354 +FormNode::isRadioButton()
355 { 355 {
356 return FT() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio; 356 return FT() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio;
357 } 357 }
@@ -363,7 +363,7 @@ QPDFFormFieldObjectHelper::isPushbutton() @@ -363,7 +363,7 @@ QPDFFormFieldObjectHelper::isPushbutton()
363 } 363 }
364 364
365 bool 365 bool
366 -FormField::isPushbutton() 366 +FormNode::isPushbutton()
367 { 367 {
368 return FT() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton; 368 return FT() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton;
369 } 369 }
@@ -375,7 +375,7 @@ QPDFFormFieldObjectHelper::isChoice() @@ -375,7 +375,7 @@ QPDFFormFieldObjectHelper::isChoice()
375 } 375 }
376 376
377 bool 377 bool
378 -FormField::isChoice() 378 +FormNode::isChoice()
379 { 379 {
380 return FT() == "/Ch"; 380 return FT() == "/Ch";
381 } 381 }
@@ -387,7 +387,7 @@ QPDFFormFieldObjectHelper::getChoices() @@ -387,7 +387,7 @@ QPDFFormFieldObjectHelper::getChoices()
387 } 387 }
388 388
389 std::vector<std::string> 389 std::vector<std::string>
390 -FormField::getChoices() 390 +FormNode::getChoices()
391 { 391 {
392 if (!isChoice()) { 392 if (!isChoice()) {
393 return {}; 393 return {};
@@ -413,7 +413,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const&amp; key, QPDFObjectH @@ -413,7 +413,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const&amp; key, QPDFObjectH
413 } 413 }
414 414
415 void 415 void
416 -FormField::setFieldAttribute(std::string const& key, QPDFObjectHandle value) 416 +FormNode::setFieldAttribute(std::string const& key, QPDFObjectHandle value)
417 { 417 {
418 oh().replaceKey(key, value); 418 oh().replaceKey(key, value);
419 } 419 }
@@ -425,7 +425,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const&amp; key, std::string @@ -425,7 +425,7 @@ QPDFFormFieldObjectHelper::setFieldAttribute(std::string const&amp; key, std::string
425 } 425 }
426 426
427 void 427 void
428 -FormField::setFieldAttribute(std::string const& key, std::string const& utf8_value) 428 +FormNode::setFieldAttribute(std::string const& key, std::string const& utf8_value)
429 { 429 {
430 oh().replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); 430 oh().replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value));
431 } 431 }
@@ -437,7 +437,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) @@ -437,7 +437,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances)
437 } 437 }
438 438
439 void 439 void
440 -FormField::setV(QPDFObjectHandle value, bool need_appearances) 440 +FormNode::setV(QPDFObjectHandle value, bool need_appearances)
441 { 441 {
442 Name name = value; 442 Name name = value;
443 if (FT() == "/Btn") { 443 if (FT() == "/Btn") {
@@ -485,13 +485,13 @@ QPDFFormFieldObjectHelper::setV(std::string const&amp; utf8_value, bool need_appeara @@ -485,13 +485,13 @@ QPDFFormFieldObjectHelper::setV(std::string const&amp; utf8_value, bool need_appeara
485 } 485 }
486 486
487 void 487 void
488 -FormField::setV(std::string const& utf8_value, bool need_appearances) 488 +FormNode::setV(std::string const& utf8_value, bool need_appearances)
489 { 489 {
490 setV(QPDFObjectHandle::newUnicodeString(utf8_value), need_appearances); 490 setV(QPDFObjectHandle::newUnicodeString(utf8_value), need_appearances);
491 } 491 }
492 492
493 void 493 void
494 -FormField::setRadioButtonValue(QPDFObjectHandle name) 494 +FormNode::setRadioButtonValue(QPDFObjectHandle name)
495 { 495 {
496 // Set the value of a radio button field. This has the following specific behavior: 496 // Set the value of a radio button field. This has the following specific behavior:
497 // * If this is a radio button field that has a parent that is also a radio button field and has 497 // * If this is a radio button field that has a parent that is also a radio button field and has
@@ -503,7 +503,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name) @@ -503,7 +503,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name)
503 // Note that we never turn on /NeedAppearances when setting a radio button field. 503 // Note that we never turn on /NeedAppearances when setting a radio button field.
504 QPDFObjectHandle parent = oh().getKey("/Parent"); 504 QPDFObjectHandle parent = oh().getKey("/Parent");
505 if (parent.isDictionary() && parent.getKey("/Parent").null()) { 505 if (parent.isDictionary() && parent.getKey("/Parent").null()) {
506 - FormField ph(parent); 506 + FormNode ph(parent);
507 if (ph.isRadioButton()) { 507 if (ph.isRadioButton()) {
508 // This is most likely one of the individual buttons. Try calling on the parent. 508 // This is most likely one of the individual buttons. Try calling on the parent.
509 ph.setRadioButtonValue(name); 509 ph.setRadioButtonValue(name);
@@ -546,7 +546,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name) @@ -546,7 +546,7 @@ FormField::setRadioButtonValue(QPDFObjectHandle name)
546 } 546 }
547 547
548 void 548 void
549 -FormField::setCheckBoxValue(bool value) 549 +FormNode::setCheckBoxValue(bool value)
550 { 550 {
551 QPDFObjectHandle AP = oh().getKey("/AP"); 551 QPDFObjectHandle AP = oh().getKey("/AP");
552 QPDFObjectHandle annot; 552 QPDFObjectHandle annot;
@@ -601,7 +601,7 @@ QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper&amp; aoh) @@ -601,7 +601,7 @@ QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper&amp; aoh)
601 } 601 }
602 602
603 void 603 void
604 -FormField::generateAppearance(QPDFAnnotationObjectHelper& aoh) 604 +FormNode::generateAppearance(QPDFAnnotationObjectHelper& aoh)
605 { 605 {
606 // Ignore field types we don't know how to generate appearances for. Button fields don't really 606 // Ignore field types we don't know how to generate appearances for. Button fields don't really
607 // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded. 607 // need them -- see code in QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded.
@@ -877,7 +877,7 @@ namespace @@ -877,7 +877,7 @@ namespace
877 } // namespace 877 } // namespace
878 878
879 QPDFObjectHandle 879 QPDFObjectHandle
880 -FormField::getFontFromResource(QPDFObjectHandle resources, std::string const& name) 880 +FormNode::getFontFromResource(QPDFObjectHandle resources, std::string const& name)
881 { 881 {
882 QPDFObjectHandle result; 882 QPDFObjectHandle result;
883 if (resources.isDictionary() && resources.getKey("/Font").isDictionary() && 883 if (resources.isDictionary() && resources.getKey("/Font").isDictionary() &&
@@ -888,7 +888,7 @@ FormField::getFontFromResource(QPDFObjectHandle resources, std::string const&amp; na @@ -888,7 +888,7 @@ FormField::getFontFromResource(QPDFObjectHandle resources, std::string const&amp; na
888 } 888 }
889 889
890 void 890 void
891 -FormField::generateTextAppearance(QPDFAnnotationObjectHelper& aoh) 891 +FormNode::generateTextAppearance(QPDFAnnotationObjectHelper& aoh)
892 { 892 {
893 QPDFObjectHandle AS = aoh.getAppearanceStream("/N"); 893 QPDFObjectHandle AS = aoh.getAppearanceStream("/N");
894 if (AS.null()) { 894 if (AS.null()) {
libqpdf/QPDFJob.cc
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 #include <iostream> 4 #include <iostream>
5 #include <memory> 5 #include <memory>
6 6
  7 +#include <qpdf/AcroForm.hh>
7 #include <qpdf/ClosedFileInputSource.hh> 8 #include <qpdf/ClosedFileInputSource.hh>
8 #include <qpdf/FileInputSource.hh> 9 #include <qpdf/FileInputSource.hh>
9 #include <qpdf/Pipeline_private.hh> 10 #include <qpdf/Pipeline_private.hh>
@@ -1866,7 +1867,7 @@ QPDFJob::doUnderOverlayForPage( @@ -1866,7 +1867,7 @@ QPDFJob::doUnderOverlayForPage(
1866 if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) { 1867 if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) {
1867 return ""; 1868 return "";
1868 } 1869 }
1869 - auto& dest_afdh = dest_page.qpdf()->doc().acroform(); 1870 + auto& dest_afdh = dest_page.qpdf()->doc().acroform_dh();
1870 1871
1871 auto const& pages = uo.pdf->doc().pages().all(); 1872 auto const& pages = uo.pdf->doc().pages().all();
1872 std::string content; 1873 std::string content;
@@ -1887,7 +1888,8 @@ QPDFJob::doUnderOverlayForPage( @@ -1887,7 +1888,8 @@ QPDFJob::doUnderOverlayForPage(
1887 QPDFMatrix cm; 1888 QPDFMatrix cm;
1888 std::string new_content = dest_page.placeFormXObject( 1889 std::string new_content = dest_page.placeFormXObject(
1889 fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); 1890 fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm);
1890 - dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->doc().acroform()); 1891 + dest_page.copyAnnotations(
  1892 + from_page, cm, &dest_afdh, &from_page.qpdf()->doc().acroform_dh());
1891 if (!new_content.empty()) { 1893 if (!new_content.empty()) {
1892 resources.mergeResources(Dictionary({{"/XObject", Dictionary::empty()}})); 1894 resources.mergeResources(Dictionary({{"/XObject", Dictionary::empty()}}));
1893 auto xobject = resources.getKey("/XObject"); 1895 auto xobject = resources.getKey("/XObject");
@@ -2107,7 +2109,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf) @@ -2107,7 +2109,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf)
2107 QPDFAcroFormDocumentHelper* afdh_ptr = nullptr; 2109 QPDFAcroFormDocumentHelper* afdh_ptr = nullptr;
2108 auto afdh = [&]() -> QPDFAcroFormDocumentHelper& { 2110 auto afdh = [&]() -> QPDFAcroFormDocumentHelper& {
2109 if (!afdh_ptr) { 2111 if (!afdh_ptr) {
2110 - afdh_ptr = &pdf.doc().acroform(); 2112 + afdh_ptr = &pdf.doc().acroform_dh();
2111 } 2113 }
2112 return *afdh_ptr; 2114 return *afdh_ptr;
2113 }; 2115 };
@@ -2978,8 +2980,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf) @@ -2978,8 +2980,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
2978 QPDF outpdf; 2980 QPDF outpdf;
2979 outpdf.doc().config(m->d_cfg); 2981 outpdf.doc().config(m->d_cfg);
2980 outpdf.emptyPDF(); 2982 outpdf.emptyPDF();
2981 - QPDFAcroFormDocumentHelper* out_afdh =  
2982 - afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr; 2983 + impl::AcroForm* out_afdh = afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr;
2983 for (size_t pageno = first; pageno <= last; ++pageno) { 2984 for (size_t pageno = first; pageno <= last; ++pageno) {
2984 QPDFObjectHandle page = pages.at(pageno - 1); 2985 QPDFObjectHandle page = pages.at(pageno - 1);
2985 outpdf.addPage(page, false); 2986 outpdf.addPage(page, false);
libqpdf/QPDF_pages.cc
1 #include <qpdf/QPDFPageDocumentHelper.hh> 1 #include <qpdf/QPDFPageDocumentHelper.hh>
2 #include <qpdf/QPDF_private.hh> 2 #include <qpdf/QPDF_private.hh>
3 3
4 -#include <qpdf/QPDFAcroFormDocumentHelper.hh> 4 +#include <qpdf/AcroForm.hh>
5 #include <qpdf/QPDFExc.hh> 5 #include <qpdf/QPDFExc.hh>
6 #include <qpdf/QPDFObjectHandle_private.hh> 6 #include <qpdf/QPDFObjectHandle_private.hh>
7 #include <qpdf/QTC.hh> 7 #include <qpdf/QTC.hh>
@@ -660,7 +660,7 @@ void @@ -660,7 +660,7 @@ void
660 Pages::flatten_annotations_for_page( 660 Pages::flatten_annotations_for_page(
661 QPDFPageObjectHelper& page, 661 QPDFPageObjectHelper& page,
662 QPDFObjectHandle& resources, 662 QPDFObjectHandle& resources,
663 - QPDFAcroFormDocumentHelper& afdh, 663 + impl::AcroForm& afdh,
664 int required_flags, 664 int required_flags,
665 int forbidden_flags) 665 int forbidden_flags)
666 { 666 {
libqpdf/qpdf/AcroForm.hh 0 โ†’ 100644
  1 +#ifndef ACRO_FORM_HH
  2 +#define ACRO_FORM_HH
  3 +
  4 +#include <qpdf/QPDFObjectHandle_private.hh>
  5 +#include <qpdf/QPDFObjectHelper.hh>
  6 +#include <qpdf/QPDF_private.hh>
  7 +
  8 +#include <vector>
  9 +
  10 +class QPDFAnnotationObjectHelper;
  11 +
  12 +namespace qpdf::impl
  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
  27 + class AcroForm: public Doc::Common
  28 + {
  29 + public:
  30 + AcroForm() = delete;
  31 + AcroForm(AcroForm const&) = delete;
  32 + AcroForm(AcroForm&&) = delete;
  33 + AcroForm& operator=(AcroForm const&) = delete;
  34 + AcroForm& operator=(AcroForm&&) = delete;
  35 + ~AcroForm() = default;
  36 +
  37 + AcroForm(impl::Doc& doc) :
  38 + Common(doc)
  39 + {
  40 + // We have to analyze up front. Otherwise, when we are adding annotations and fields, we
  41 + // are in a temporarily unstable configuration where some widget annotations are not
  42 + // reachable.
  43 + validate();
  44 + }
  45 +
  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);
  98 +
  99 + /// Retrieves a list of widget annotations for the specified page.
  100 + ///
  101 + /// A widget annotation represents the visual part of a form field in a PDF.
  102 + /// This function filters annotations on the given page, returning only those
  103 + /// annotations whose subtype is "/Widget".
  104 + ///
  105 + /// @param page A `QPDFPageObjectHelper` representing the page from which to
  106 + /// extract widget annotations.
  107 + ///
  108 + /// @return A vector of `QPDFAnnotationObjectHelper` objects corresponding to
  109 + /// the widget annotations found on the specified page.
  110 + std::vector<QPDFAnnotationObjectHelper>
  111 + getWidgetAnnotationsForPage(QPDFPageObjectHelper page);
  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 +
  193 + /// Analyzes the AcroForm structure in the PDF document and updates the internal
  194 + /// cache with the form fields and their corresponding widget annotations.
  195 + ///
  196 + /// The function performs the following steps:
  197 + /// - Checks if the cache is valid. If it is, the function exits early.
  198 + /// - Retrieves the `/AcroForm` dictionary from the PDF and checks if it contains
  199 + /// a `/Fields` key.
  200 + /// - If `/Fields` exist and is an array, iterates through the fields and traverses
  201 + /// them to map annotations bidirectionally to form fields.
  202 + /// - Logs a warning if the `/Fields` key is present but not an array, and initializes
  203 + /// it to an empty array.
  204 + /// - Ensures that all widget annotations are processed, including any annotations
  205 + /// that might not be reachable from the `/AcroForm`. Treats such annotations as
  206 + /// their own fields.
  207 + /// - Provides a workaround for PDF documents containing inconsistencies, such as
  208 + /// widget annotations on a page not being referenced in `/AcroForm`.
  209 + ///
  210 + /// This function allows precise navigation and manipulation of form fields and
  211 + /// their related annotations, facilitating advanced PDF document processing.
  212 + void analyze();
  213 +
  214 + /// Recursively traverses the structure of form fields and annotations in a PDF's /AcroForm.
  215 + ///
  216 + /// The method is designed to process form fields in a hierarchical /AcroForm structure.
  217 + /// It captures field and annotation data, resolves parent-child relationships, detects
  218 + /// loops, and avoids stack overflow from excessive recursion depth.
  219 + ///
  220 + /// @param field The current field or annotation to process.
  221 + /// @param parent The parent field object. If the current field is a top-level field, parent
  222 + /// will be a null object.
  223 + /// @param depth The current recursion depth to limit stack usage and avoid infinite loops.
  224 + ///
  225 + /// @return True if the field was processed successfully, false otherwise.
  226 + ///
  227 + /// - Recursion is limited to a depth of 100 to prevent stack overflow with maliciously
  228 + /// crafted files.
  229 + /// - The function skips non-indirect and invalid objects (e.g., non-dictionaries or objects
  230 + /// with invalid parent references).
  231 + /// - Detects and warns about loops in the /AcroForm hierarchy.
  232 + /// - Differentiates between terminal fields, annotations, and composite fields based on
  233 + /// dictionary keys.
  234 + /// - Tracks processed fields and annotations using internal maps to prevent reprocessing
  235 + /// and detect loops.
  236 + /// - Updates name-to-field mappings for terminal fields with a valid fully qualified name.
  237 + /// - Ensures the integrity of parent-child relationships within the field hierarchy.
  238 + /// - Any invalid child objects are logged and skipped during traversal.
  239 + bool traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth);
  240 +
  241 + /// Retrieves or creates the /AcroForm dictionary in the PDF document's root.
  242 + ///
  243 + /// - If the /AcroForm key exists in the document root and is a dictionary,
  244 + /// it is returned as is.
  245 + /// - If the /AcroForm key does not exist or is not a dictionary, a new
  246 + /// dictionary is created, stored as the /AcroForm entry in the document root,
  247 + /// and then returned.
  248 + ///
  249 + /// @return A QPDFObjectHandle representing the /AcroForm dictionary.
  250 + QPDFObjectHandle getOrCreateAcroForm();
  251 +
  252 + /// Adjusts inherited field properties for an AcroForm field object.
  253 + ///
  254 + /// This method ensures that the `/DA` (default appearance) and `/Q` (quadding) keys
  255 + /// of the specified field object are overridden if necessary, based on the provided
  256 + /// parameters. The overriding is performed only if the respective `override_da` or
  257 + /// `override_q` flags are set to true, and when the original object's values differ from
  258 + /// the provided defaults. No changes are made to fields that have explicit values for `/DA`
  259 + /// or `/Q`.
  260 + ///
  261 + /// The function is primarily used for adjusting inherited form field properties in cases
  262 + /// where the document structure or inherited values have changed (e.g., when working with
  263 + /// fields in a PDF document).
  264 + ///
  265 + /// @param obj The `QPDFObjectHandle` instance representing the form field object to be
  266 + /// adjusted.
  267 + /// @param override_da A boolean flag indicating whether to override the `/DA` key.
  268 + /// @param from_default_da The default appearance string to apply if overriding the `/DA`
  269 + /// key.
  270 + /// @param override_q A boolean flag indicating whether to override the `/Q` key.
  271 + /// @param from_default_q The default quadding value (alignment) to apply if overriding the
  272 + /// `/Q` key.
  273 + void adjustInheritedFields(
  274 + QPDFObjectHandle obj,
  275 + bool override_da,
  276 + std::string const& from_default_da,
  277 + bool override_q,
  278 + int from_default_q);
  279 +
  280 + /// Adjusts the default appearances (/DA) of an AcroForm field object.
  281 + ///
  282 + /// This method ensures that form fields copied from another PDF document
  283 + /// have their default appearances resource references updated to correctly
  284 + /// point to the appropriate resources in the current document's resource
  285 + /// dictionary (/DR). It resolves name conflicts between the dictionaries
  286 + /// of the source and destination documents by using a mapping provided in
  287 + /// `dr_map`.
  288 + ///
  289 + /// The method parses the /DA string, processes its resource references,
  290 + /// and regenerates the /DA with updated references.
  291 + ///
  292 + /// @param obj The AcroForm field object whose /DA is being adjusted.
  293 + /// @param dr_map A mapping between resource names in the source document's
  294 + /// resource dictionary and their corresponding names in the current
  295 + /// document's resource dictionary.
  296 + void adjustDefaultAppearances(
  297 + QPDFObjectHandle obj,
  298 + std::map<std::string, std::map<std::string, std::string>> const& dr_map);
  299 +
  300 + /// Modifies the appearance stream of an AcroForm field to ensure its resources
  301 + /// align with the resource dictionary and appearance settings. This method
  302 + /// ensures proper resource handling to avoid any conflicts when regenerating
  303 + /// the appearance stream.
  304 + ///
  305 + /// Adjustments include:
  306 + /// - Creating a private resource dictionary for the stream if not already present.
  307 + /// - Merging top-level resource keys into the stream's resource dictionary.
  308 + /// - Resolving naming conflicts between existing and remapped resource keys.
  309 + /// - Removing empty sub-dictionaries from the resource dictionary.
  310 + /// - Attaching a token filter to rewrite resource references in the stream content.
  311 + ///
  312 + /// If conflicts between keys are encountered or the stream cannot be parsed successfully,
  313 + /// appropriate warnings will be generated instead of halting execution.
  314 + ///
  315 + /// @param stream The QPDFObjectHandle representation of the PDF appearance stream to be
  316 + /// adjusted.
  317 + /// @param dr_map A mapping of resource types and their corresponding name remappings
  318 + /// used for resolving resource conflicts and regenerating appearances.
  319 + void adjustAppearanceStream(
  320 + QPDFObjectHandle stream,
  321 + std::map<std::string, std::map<std::string, std::string>> dr_map);
  322 +
  323 + std::map<QPDFObjGen, FieldData> fields_;
  324 + std::map<QPDFObjGen, QPDFFormFieldObjectHelper> annotation_to_field_;
  325 + std::map<std::string, std::set<QPDFObjGen>> name_to_fields_;
  326 + std::set<QPDFObjGen> bad_fields_;
  327 + bool cache_valid_{false};
  328 +
  329 + }; // class Acroform
  330 +
  331 + /// @class FormNode
  332 + /// @brief Represents a node in the interactive forms tree of a PDF document.
  333 + ///
  334 + /// This class models nodes that may be either form field dictionaries or widget annotation
  335 + /// dictionaries, as defined in the PDF specification (sections 12.7 and 12.5.6.19).
  336 + ///
  337 + /// For a detailed description of the attributes that this class can expose, refer to the
  338 + /// corresponding tables in the PDF 2.0 (Table 226) or PDF 1.7 (Table 220) specifications.
  339 + class FormNode: public qpdf::BaseDictionary
  340 + {
  341 + public:
  342 + FormNode() = default;
  343 + FormNode(FormNode const&) = default;
  344 + FormNode& operator=(FormNode const&) = default;
  345 + FormNode(FormNode&&) = default;
  346 + FormNode& operator=(FormNode&&) = default;
  347 + ~FormNode() = default;
  348 +
  349 + FormNode(QPDFObjectHandle const& oh) :
  350 + BaseDictionary(oh)
  351 + {
  352 + }
  353 +
  354 + FormNode(QPDFObjectHandle&& oh) :
  355 + BaseDictionary(std::move(oh))
  356 + {
  357 + }
  358 +
  359 + /// Retrieves the /Parent form field of the current field.
  360 + ///
  361 + /// This function accesses the parent field in the hierarchical structure of form fields, if
  362 + /// it exists. The parent is determined based on the /Parent attribute in the field
  363 + /// dictionary.
  364 + ///
  365 + /// @return A FormNode object representing the parent field. If the current field has no
  366 + /// parent, an empty FormNode object is returned.
  367 + FormNode
  368 + Parent()
  369 + {
  370 + return {get("/Parent")};
  371 + }
  372 +
  373 + /// @brief Returns the top-level field associated with the current field.
  374 + ///
  375 + /// The function traverses the hierarchy of parent fields to identify the highest-level
  376 + /// field in the tree. Typically, this will be the current field itself unless it has a
  377 + /// parent field. Optionally, it can indicate whether the top-level field is different from
  378 + /// the current field.
  379 + ///
  380 + /// @param is_different A pointer to a boolean that, if provided, will be set to true if the
  381 + /// top-level field differs from the current field; otherwise, it will be set to
  382 + /// false.
  383 + ///
  384 + /// @return The top-level field in the form field hierarchy.
  385 + FormNode root_field(bool* is_different = nullptr);
  386 +
  387 + /// @brief Retrieves the inherited value of the specified attribute.
  388 + ///
  389 + /// @param name The name of the attribute to retrieve.
  390 + /// @param acroform If true, checks the document's /AcroForm dictionary for the attribute
  391 + /// if it is not found in the field hierarchy.
  392 + ///
  393 + /// @return A constant reference to the QPDFObjectHandle representing the value of the
  394 + /// specified attribute, if found. If the attribute is not found in the field
  395 + /// hierarchy or the /AcroForm dictionary (when `acroform` is true), returns a
  396 + /// reference to a static null object handle.
  397 + QPDFObjectHandle const& inherited(std::string const& name, bool acroform = false) const;
  398 +
  399 + /// @brief Retrieves the value of a specified field, accounting for inheritance through the
  400 + /// hierarchy of ancestor nodes in the form field tree.
  401 + ///
  402 + /// This function attempts to retrieve the value of the specified field. If the `inherit`
  403 + /// parameter is set to `true` and the field value is not found at the current level, the
  404 + /// method traverses up the parent hierarchy to find the value. The traversal stops when a
  405 + /// value is found, when the root node is reached, or when a loop detection mechanism
  406 + /// prevents further traversal.
  407 + ///
  408 + /// @tparam T The return type of the field value.
  409 + /// @param name The name of the field to retrieve the value for.
  410 + /// @param inherit If set to `true`, the function will attempt to retrieve the value by
  411 + /// inheritance from the parent hierarchy of the form field. Defaults to `true`.
  412 + /// @return Returns the field's value if found; otherwise, returns a default-constructed
  413 + /// value of type `T`.
  414 + template <class T>
  415 + T
  416 + inheritable_value(std::string const& name, bool inherit = true, bool acroform = false) const
  417 + {
  418 + if (auto& v = get(name)) {
  419 + return {v};
  420 + }
  421 + return {inherit ? inherited(name, acroform) : null_oh};
  422 + }
  423 +
  424 + /// @brief Retrieves an inherited field string attribute as a string.
  425 + ///
  426 + /// @param name The name of the field for which the value is to be retrieved.
  427 + /// @return The inherited field value as a UTF-8 encoded string, or an empty string if the
  428 + /// value does not exist or is not of String type.
  429 + std::string inheritable_string(std::string const& name) const;
  430 +
  431 + /// @brief Retrieves the field type (/FT attribute).
  432 + ///
  433 + /// @param inherit If set to `true`, the function will attempt to retrieve the value by
  434 + /// inheritance from the parent hierarchy of the form field. Defaults to `true`.
  435 + /// @return Returns the field type if found; otherwise, returns a default-constructed
  436 + /// `Name`.
  437 + Name
  438 + FT(bool inherit = true) const
  439 + {
  440 + return inheritable_value<Name>("/FT");
  441 + }
  442 +
  443 + /// @brief Retrieves the partial field name (/T attribute).
  444 + ///
  445 + /// @return Returns the partial field name if found; otherwise, returns a
  446 + /// default-constructed `String`.
  447 + String
  448 + T() const
  449 + {
  450 + return {get("/T")};
  451 + }
  452 +
  453 + /// @brief Retrieves the alternative name (/TU attribute).
  454 + ///
  455 + /// @return Returns the alternative name if found; otherwise, returns a default-constructed
  456 + /// `String`.
  457 + String
  458 + TU() const
  459 + {
  460 + return {get("/TU")};
  461 + }
  462 +
  463 + /// @brief Retrieves the mapping name (/TM attribute).
  464 + ///
  465 + /// @return Returns the mapping name if found; otherwise, returns a default-constructed
  466 + /// `String`.
  467 + String
  468 + TM() const
  469 + {
  470 + return {get("/TM")};
  471 + }
  472 +
  473 + /// @brief Retrieves the fully qualified name of the form field.
  474 + ///
  475 + /// This method constructs the fully qualified name of the form field by traversing through
  476 + /// its parent hierarchy. The fully qualified name is constructed by concatenating the /T
  477 + /// (field name) attribute of each parent node with periods as separators, starting from the
  478 + /// root of the hierarchy.
  479 + ///
  480 + /// If the field has no parent hierarchy, the result will simply be the /T attribute of the
  481 + /// current field. In cases of potential circular references, loop detection is applied.
  482 + ///
  483 + /// @return A string representing the fully qualified name of the field.
  484 + std::string fully_qualified_name() const;
  485 +
  486 + /// @brief Retrieves the partial name (/T attribute) of the form field.
  487 + ///
  488 + /// This method returns the value of the field's /T attribute, which is the partial name
  489 + /// used to identify the field within its parent hierarchy. If the attribute is not set, an
  490 + /// empty string is returned.
  491 + ///
  492 + /// @return A string representing the partial name of the field in UTF-8 encoding, or an
  493 + /// empty string if the /T attribute is not present.
  494 + std::string partial_name() const;
  495 +
  496 + /// @brief Retrieves the alternative name for the form field.
  497 + ///
  498 + /// This method attempts to return the alternative name (/TU) of the form field, which is
  499 + /// the field name intended to be presented, to users as a UTF-8 string, if it exists. If
  500 + /// the alternative name is not present, the method falls back to the fully qualified name
  501 + /// of the form field.
  502 + ///
  503 + /// @return The alternative name of the form field as a string, or the
  504 + /// fully qualified name if the alternative name is unavailable.
  505 + std::string alternative_name() const;
  506 +
  507 + /// @brief Retrieves the mapping field name (/TM) for the form field.
  508 + ///
  509 + /// If the mapping name (/TM) is present, it is returned as a UTF-8 string. If not, it falls
  510 + /// back to the 'alternative name', which is obtained using the `alternative_name()` method.
  511 + ///
  512 + /// @return The mapping field name (/TM) as a UTF-8 string or the alternative name if the
  513 + /// mapping name is absent.
  514 + std::string mapping_name() const;
  515 +
  516 + /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for
  517 + /// inheritance through the hierarchy of ancestor nodes in the form field tree.
  518 + ///
  519 + /// This function attempts to retrieve the `/V` attribute. If the `inherit`
  520 + /// parameter is set to `true` and the `/V` is not found at the current level, the
  521 + /// method traverses up the parent hierarchy to find the value. The traversal stops when
  522 + /// `/V` is found, when the root node is reached, or when a loop detection mechanism
  523 + /// prevents further traversal.
  524 + ///
  525 + /// @tparam T The return type.
  526 + /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by
  527 + /// inheritance from the parent hierarchy of the form field. Defaults to `true`.
  528 + /// @return Returns the field's value if found; otherwise, returns a default-constructed
  529 + /// value of type `T`.
  530 + template <class T>
  531 + T
  532 + V(bool inherit = true) const
  533 + {
  534 + return inheritable_value<T>("/V", inherit);
  535 + }
  536 +
  537 + /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for
  538 + /// inheritance through the hierarchy of ancestor nodes in the form field tree.
  539 + ///
  540 + /// This function attempts to retrieve the `/V` attribute. If the `inherit`
  541 + /// parameter is set to `true` and the `/V` is not found at the current level, the
  542 + /// method traverses up the parent hierarchy to find the value. The traversal stops when
  543 + /// `/V` is found, when the root node is reached, or when a loop detection mechanism
  544 + /// prevents further traversal.
  545 + ///
  546 + /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by
  547 + /// inheritance from the parent hierarchy of the form field. Defaults to `true`.
  548 + /// @return Returns the field's value if found; otherwise, returns a default-constructed
  549 + /// object handle.
  550 + QPDFObjectHandle const&
  551 + V(bool inherit = true) const
  552 + {
  553 + if (auto& v = get("/V")) {
  554 + return v;
  555 + }
  556 + return {inherit ? inherited("/V") : null_oh};
  557 + }
  558 +
  559 + /// @brief Retrieves the field value `/V` attribute of the form field, considering
  560 + /// inheritance, if the value is a String.
  561 + ///
  562 + /// This function extracts the value of the form field, accounting for potential inheritance
  563 + /// through the form hierarchy. It returns the value if it is a String, and an empty string
  564 + /// otherwise.
  565 + ///
  566 + /// @return A string containing the actual or inherited `/V` attribute of the form field, or
  567 + /// an empty string if the value is not present or not a String.
  568 + std::string value() const;
  569 +
  570 + /// @brief Retrieves the field default value (`/DV` attribute) of a specified field,
  571 + /// accounting for inheritance through the hierarchy of ancestor nodes in the form
  572 + /// field tree.
  573 + ///
  574 + /// This function attempts to retrieve the `/DV` attribute. If the `inherit` parameter is
  575 + /// set to `true` and the `/DV` is not found at the current level, the method traverses up
  576 + /// the parent hierarchy to find the value. The traversal stops when
  577 + /// `/DV` is found, when the root node is reached, or when a loop detection mechanism
  578 + /// prevents further traversal.
  579 + ///
  580 + /// @tparam T The return type.
  581 + /// @param inherit If set to `true`, the function will attempt to retrieve `/DV` by
  582 + /// inheritance from the parent hierarchy of the form field. Defaults to `true`.
  583 + /// @return Returns the field's default value if found; otherwise, returns a
  584 + /// default-constructed value of type `T`.
  585 + QPDFObjectHandle const&
  586 + DV(bool inherit = true) const
  587 + {
  588 + if (auto& v = get("/DV")) {
  589 + return v;
  590 + }
  591 + return {inherit ? inherited("/DV") : null_oh};
  592 + }
  593 +
  594 + /// @brief Retrieves the default value `/DV` attribute of the form field, considering
  595 + /// inheritance, if the default value is a String.
  596 + ///
  597 + /// This function extracts the default value of the form field, accounting for potential
  598 + /// inheritance through the form hierarchy. It returns the value if it is a String, and an
  599 + /// empty string otherwise.
  600 + ///
  601 + /// @return A string containing the actual or inherited `/DV` attribute of the form field,
  602 + /// or an empty string if the value is not present or not a String.
  603 + std::string default_value() const;
  604 +
  605 + /// @brief Returns the default appearance string for the form field, considering inheritance
  606 + /// from the field tree hierarchy and the document's /AcroForm dictionary.
  607 + ///
  608 + /// This method retrieves the field's /DA (default appearance) attribute. If the attribute
  609 + /// is not directly available, it checks the parent fields in the hierarchy for an inherited
  610 + /// value. If no value is found in the field hierarchy, it attempts to retrieve the /DA
  611 + /// attribute from the document's /AcroForm dictionary. The method returns an empty string
  612 + /// if no default appearance string is available or applicable.
  613 + ///
  614 + /// @return A string representing the default appearance, or an empty string if
  615 + /// no value is found.
  616 + std::string default_appearance() const;
  617 +
  618 + // Return the default resource dictionary for the field. This comes not from the field but
  619 + // from the document-level /AcroForm dictionary. While several PDF generates put a /DR key
  620 + // in the form field's dictionary, experimentation suggests that many popular readers,
  621 + // including Adobe Acrobat and Acrobat Reader, ignore any /DR item on the field.
  622 + QPDFObjectHandle getDefaultResources();
  623 +
  624 + // Return the quadding value, taking inheritance from the field tree into account. Returns 0
  625 + // if quadding is not specified. Look in /AcroForm if not found in the field hierarchy.
  626 + int getQuadding();
  627 +
  628 + // Return field flags from /Ff. The value is a logical or of pdf_form_field_flag_e as
  629 + // defined in qpdf/Constants.h//
  630 + int getFlags();
  631 +
  632 + // Methods for testing for particular types of form fields
  633 +
  634 + // Returns true if field is of type /Tx
  635 + bool isText();
  636 + // Returns true if field is of type /Btn and flags do not indicate some other type of
  637 + // button.
  638 + bool isCheckbox();
  639 +
  640 + // Returns true if field is a checkbox and is checked.
  641 + bool isChecked();
  642 +
  643 + // Returns true if field is of type /Btn and flags indicate that it is a radio button
  644 + bool isRadioButton();
  645 +
  646 + // Returns true if field is of type /Btn and flags indicate that it is a pushbutton
  647 + bool isPushbutton();
  648 +
  649 + // Returns true if fields if of type /Ch
  650 + bool isChoice();
  651 +
  652 + // Returns choices display values as UTF-8 strings
  653 + std::vector<std::string> getChoices();
  654 +
  655 + // Set an attribute to the given value. If you have a QPDFAcroFormDocumentHelper and you
  656 + // want to set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName
  657 + // instead.
  658 + void setFieldAttribute(std::string const& key, QPDFObjectHandle value);
  659 +
  660 + // Set an attribute to the given value as a Unicode string (UTF-16 BE encoded). The input
  661 + // string should be UTF-8 encoded. If you have a QPDFAcroFormDocumentHelper and you want to
  662 + // set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName instead.
  663 + void setFieldAttribute(std::string const& key, std::string const& utf8_value);
  664 +
  665 + // Set /V (field value) to the given value. If need_appearances is true and the field type
  666 + // is either /Tx (text) or /Ch (choice), set /NeedAppearances to true. You can explicitly
  667 + // tell this method not to set /NeedAppearances if you are going to generate an appearance
  668 + // stream yourself. Starting with qpdf 8.3.0, this method handles fields of type /Btn
  669 + // (checkboxes, radio buttons, pushbuttons) specially. When setting a checkbox value, any
  670 + // value other than /Off will be treated as on, and the actual value set will be based on
  671 + // the appearance stream's /N dictionary, so the value that ends up in /V may not exactly
  672 + // match the value you pass in.
  673 + void setV(QPDFObjectHandle value, bool need_appearances = true);
  674 +
  675 + // Set /V (field value) to the given string value encoded as a Unicode string. The input
  676 + // value should be UTF-8 encoded. See comments above about /NeedAppearances.
  677 + void setV(std::string const& utf8_value, bool need_appearances = true);
  678 +
  679 + // Update the appearance stream for this field. Note that qpdf's ability to generate
  680 + // appearance streams is limited. We only generate appearance streams for streams of type
  681 + // text or choice. The appearance uses the default parameters provided in the file, and it
  682 + // only supports ASCII characters. Quadding is currently ignored. While this functionality
  683 + // is limited, it should do a decent job on properly constructed PDF files when field values
  684 + // are restricted to ASCII characters.
  685 + void generateAppearance(QPDFAnnotationObjectHelper&);
  686 +
  687 + private:
  688 + /// @brief Retrieves an entry from the document's /AcroForm dictionary using the specified
  689 + /// name.
  690 + ///
  691 + /// The method accesses the AcroForm dictionary within the root object of the PDF document.
  692 + /// If the AcroForm dictionary contains the given field name, it retrieves the
  693 + /// corresponding entry. Otherwise, it returns a default-constructed object handle.
  694 + ///
  695 + /// @param name The name of the form field to retrieve.
  696 + /// @return An object handle corresponding to the specified name within the AcroForm
  697 + /// dictionary.
  698 + QPDFObjectHandle const&
  699 + from_AcroForm(std::string const& name) const
  700 + {
  701 + return {qpdf() ? qpdf()->getRoot()["/AcroForm"][name] : null_oh};
  702 + }
  703 +
  704 + void setRadioButtonValue(QPDFObjectHandle name);
  705 + void setCheckBoxValue(bool value);
  706 + void generateTextAppearance(QPDFAnnotationObjectHelper&);
  707 + QPDFObjectHandle
  708 + getFontFromResource(QPDFObjectHandle resources, std::string const& font_name);
  709 +
  710 + static const QPDFObjectHandle null_oh;
  711 + }; // class FormNode
  712 +} // namespace qpdf::impl
  713 +
  714 +class QPDFAcroFormDocumentHelper::Members: public qpdf::impl::AcroForm
  715 +{
  716 + public:
  717 + Members(QPDF& qpdf) :
  718 + AcroForm(qpdf.doc())
  719 + {
  720 + }
  721 +};
  722 +
  723 +#endif // ACRO_FORM_HH
libqpdf/qpdf/FormField.hh deleted
1 -#ifndef FORMFIELD_HH  
2 -#define FORMFIELD_HH  
3 -  
4 -#include <qpdf/QPDFObjectHandle_private.hh>  
5 -#include <qpdf/QPDFObjectHelper.hh>  
6 -  
7 -#include <vector>  
8 -  
9 -class QPDFAnnotationObjectHelper;  
10 -  
11 -namespace qpdf::impl  
12 -{  
13 - // This object helper helps with form fields for interactive forms. Please see comments in  
14 - // QPDFAcroFormDocumentHelper.hh for additional details.  
15 - class FormField: public qpdf::BaseDictionary  
16 - {  
17 - public:  
18 - FormField() = default;  
19 - FormField(FormField const&) = default;  
20 - FormField& operator=(FormField const&) = default;  
21 - FormField(FormField&&) = default;  
22 - FormField& operator=(FormField&&) = default;  
23 - ~FormField() = default;  
24 -  
25 - FormField(QPDFObjectHandle const& oh) :  
26 - BaseDictionary(oh)  
27 - {  
28 - }  
29 -  
30 - FormField(QPDFObjectHandle&& oh) :  
31 - BaseDictionary(std::move(oh))  
32 - {  
33 - }  
34 -  
35 - /// Retrieves the /Parent form field of the current field.  
36 - ///  
37 - /// This function accesses the parent field in the hierarchical structure of form fields, if  
38 - /// it exists. The parent is determined based on the /Parent attribute in the field  
39 - /// dictionary.  
40 - ///  
41 - /// @return A FormField object representing the parent field. If the current field has no  
42 - /// parent, an empty FormField object is returned.  
43 - FormField  
44 - Parent()  
45 - {  
46 - return {get("/Parent")};  
47 - }  
48 -  
49 - /// @brief Returns the top-level field associated with the current field.  
50 - ///  
51 - /// The function traverses the hierarchy of parent fields to identify the highest-level  
52 - /// field in the tree. Typically, this will be the current field itself unless it has a  
53 - /// parent field. Optionally, it can indicate whether the top-level field is different from  
54 - /// the current field.  
55 - ///  
56 - /// @param is_different A pointer to a boolean that, if provided, will be set to true if the  
57 - /// top-level field differs from the current field; otherwise, it will be set to  
58 - /// false.  
59 - ///  
60 - /// @return The top-level field in the form field hierarchy.  
61 - FormField root_field(bool* is_different = nullptr);  
62 -  
63 - /// @brief Retrieves the inherited value of the specified attribute.  
64 - ///  
65 - /// @param name The name of the attribute to retrieve.  
66 - /// @param acroform If true, checks the document's /AcroForm dictionary for the attribute  
67 - /// if it is not found in the field hierarchy.  
68 - ///  
69 - /// @return A constant reference to the QPDFObjectHandle representing the value of the  
70 - /// specified attribute, if found. If the attribute is not found in the field  
71 - /// hierarchy or the /AcroForm dictionary (when `acroform` is true), returns a  
72 - /// reference to a static null object handle.  
73 - QPDFObjectHandle const& inherited(std::string const& name, bool acroform = false) const;  
74 -  
75 - /// @brief Retrieves the value of a specified field, accounting for inheritance through the  
76 - /// hierarchy of ancestor nodes in the form field tree.  
77 - ///  
78 - /// This function attempts to retrieve the value of the specified field. If the `inherit`  
79 - /// parameter is set to `true` and the field value is not found at the current level, the  
80 - /// method traverses up the parent hierarchy to find the value. The traversal stops when a  
81 - /// value is found, when the root node is reached, or when a loop detection mechanism  
82 - /// prevents further traversal.  
83 - ///  
84 - /// @tparam T The return type of the field value.  
85 - /// @param name The name of the field to retrieve the value for.  
86 - /// @param inherit If set to `true`, the function will attempt to retrieve the value by  
87 - /// inheritance from the parent hierarchy of the form field. Defaults to `true`.  
88 - /// @return Returns the field's value if found; otherwise, returns a default-constructed  
89 - /// value of type `T`.  
90 - template <class T>  
91 - T  
92 - inheritable_value(std::string const& name, bool inherit = true, bool acroform = false) const  
93 - {  
94 - if (auto& v = get(name)) {  
95 - return {v};  
96 - }  
97 - return {inherit ? inherited(name, acroform) : null_oh};  
98 - }  
99 -  
100 - /// @brief Retrieves an inherited field string attribute as a string.  
101 - ///  
102 - /// @param name The name of the field for which the value is to be retrieved.  
103 - /// @return The inherited field value as a UTF-8 encoded string, or an empty string if the  
104 - /// value does not exist or is not of String type.  
105 - std::string inheritable_string(std::string const& name) const;  
106 -  
107 - /// @brief Retrieves the field type (/FT attribute).  
108 - ///  
109 - /// @param inherit If set to `true`, the function will attempt to retrieve the value by  
110 - /// inheritance from the parent hierarchy of the form field. Defaults to `true`.  
111 - /// @return Returns the field type if found; otherwise, returns a default-constructed  
112 - /// `Name`.  
113 - Name  
114 - FT(bool inherit = true) const  
115 - {  
116 - return inheritable_value<Name>("/FT");  
117 - }  
118 -  
119 - /// @brief Retrieves the partial field name (/T attribute).  
120 - ///  
121 - /// @return Returns the partial field name if found; otherwise, returns a  
122 - /// default-constructed `String`.  
123 - String  
124 - T() const  
125 - {  
126 - return {get("/T")};  
127 - }  
128 -  
129 - /// @brief Retrieves the alternative name (/TU attribute).  
130 - ///  
131 - /// @return Returns the alternative name if found; otherwise, returns a default-constructed  
132 - /// `String`.  
133 - String  
134 - TU() const  
135 - {  
136 - return {get("/TU")};  
137 - }  
138 -  
139 - /// @brief Retrieves the mapping name (/TM attribute).  
140 - ///  
141 - /// @return Returns the mapping name if found; otherwise, returns a default-constructed  
142 - /// `String`.  
143 - String  
144 - TM() const  
145 - {  
146 - return {get("/TM")};  
147 - }  
148 -  
149 - /// @brief Retrieves the fully qualified name of the form field.  
150 - ///  
151 - /// This method constructs the fully qualified name of the form field by traversing through  
152 - /// its parent hierarchy. The fully qualified name is constructed by concatenating the /T  
153 - /// (field name) attribute of each parent node with periods as separators, starting from the  
154 - /// root of the hierarchy.  
155 - ///  
156 - /// If the field has no parent hierarchy, the result will simply be the /T attribute of the  
157 - /// current field. In cases of potential circular references, loop detection is applied.  
158 - ///  
159 - /// @return A string representing the fully qualified name of the field.  
160 - std::string fully_qualified_name() const;  
161 -  
162 - /// @brief Retrieves the partial name (/T attribute) of the form field.  
163 - ///  
164 - /// This method returns the value of the field's /T attribute, which is the partial name  
165 - /// used to identify the field within its parent hierarchy. If the attribute is not set, an  
166 - /// empty string is returned.  
167 - ///  
168 - /// @return A string representing the partial name of the field in UTF-8 encoding, or an  
169 - /// empty string if the /T attribute is not present.  
170 - std::string partial_name() const;  
171 -  
172 - /// @brief Retrieves the alternative name for the form field.  
173 - ///  
174 - /// This method attempts to return the alternative name (/TU) of the form field, which is  
175 - /// the field name intended to be presented, to users as a UTF-8 string, if it exists. If  
176 - /// the alternative name is not present, the method falls back to the fully qualified name  
177 - /// of the form field.  
178 - ///  
179 - /// @return The alternative name of the form field as a string, or the  
180 - /// fully qualified name if the alternative name is unavailable.  
181 - std::string alternative_name() const;  
182 -  
183 - /// @brief Retrieves the mapping field name (/TM) for the form field.  
184 - ///  
185 - /// If the mapping name (/TM) is present, it is returned as a UTF-8 string. If not, it falls  
186 - /// back to the 'alternative name', which is obtained using the `alternative_name()` method.  
187 - ///  
188 - /// @return The mapping field name (/TM) as a UTF-8 string or the alternative name if the  
189 - /// mapping name is absent.  
190 - std::string mapping_name() const;  
191 -  
192 - /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for  
193 - /// inheritance through thehierarchy of ancestor nodes in the form field tree.  
194 - ///  
195 - /// This function attempts to retrieve the `/V` attribute. If the `inherit`  
196 - /// parameter is set to `true` and the `/V` is not found at the current level, the  
197 - /// method traverses up the parent hierarchy to find the value. The traversal stops when  
198 - /// `/V` is found, when the root node is reached, or when a loop detection mechanism  
199 - /// prevents further traversal.  
200 - ///  
201 - /// @tparam T The return type.  
202 - /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by  
203 - /// inheritance from the parent hierarchy of the form field. Defaults to `true`.  
204 - /// @return Returns the field's value if found; otherwise, returns a default-constructed  
205 - /// value of type `T`.  
206 - template <class T>  
207 - T  
208 - V(bool inherit = true) const  
209 - {  
210 - return inheritable_value<T>("/V", inherit);  
211 - }  
212 -  
213 - /// @brief Retrieves the field value (`/V` attribute) of a specified field, accounting for  
214 - /// inheritance through the hierarchy of ancestor nodes in the form field tree.  
215 - ///  
216 - /// This function attempts to retrieve the `/V` attribute. If the `inherit`  
217 - /// parameter is set to `true` and the `/V` is not found at the current level, the  
218 - /// method traverses up the parent hierarchy to find the value. The traversal stops when  
219 - /// `/V` is found, when the root node is reached, or when a loop detection mechanism  
220 - /// prevents further traversal.  
221 - ///  
222 - /// @param inherit If set to `true`, the function will attempt to retrieve `/V` by  
223 - /// inheritance from the parent hierarchy of the form field. Defaults to `true`.  
224 - /// @return Returns the field's value if found; otherwise, returns a default-constructed  
225 - /// object handle.  
226 - QPDFObjectHandle const&  
227 - V(bool inherit = true) const  
228 - {  
229 - if (auto& v = get("/V")) {  
230 - return v;  
231 - }  
232 - return {inherit ? inherited("/V") : null_oh};  
233 - }  
234 -  
235 - /// @brief Retrieves the field value `/V` attribute of the form field, considering  
236 - /// inheritance, if the value is a String.  
237 - ///  
238 - /// This function extracts the value of the form field, accounting for potential inheritance  
239 - /// through the form hierarchy. It returns the value if it is a String, and an empty string  
240 - /// otherwise.  
241 - ///  
242 - /// @return A string containing the actual or inherited `/V` attribute of the form field, or  
243 - /// an empty string if the value is not present or not a String.  
244 - std::string value() const;  
245 -  
246 - /// @brief Retrieves the field default value (`/DV` attribute) of a specified field,  
247 - /// accounting for inheritance through the hierarchy of ancestor nodes in the form  
248 - /// field tree.  
249 - ///  
250 - /// This function attempts to retrieve the `/DV` attribute. If the `inherit` parameter is  
251 - /// set to `true` and the `/DV` is not found at the current level, the method traverses up  
252 - /// the parent hierarchy to find the value. The traversal stops when  
253 - /// `/DV` is found, when the root node is reached, or when a loop detection mechanism  
254 - /// prevents further traversal.  
255 - ///  
256 - /// @tparam T The return type.  
257 - /// @param inherit If set to `true`, the function will attempt to retrieve `/DV` by  
258 - /// inheritance from the parent hierarchy of the form field. Defaults to `true`.  
259 - /// @return Returns the field's default value if found; otherwise, returns a  
260 - /// default-constructed value of type `T`.  
261 - QPDFObjectHandle const&  
262 - DV(bool inherit = true) const  
263 - {  
264 - if (auto& v = get("/DV")) {  
265 - return v;  
266 - }  
267 - return {inherit ? inherited("/DV") : null_oh};  
268 - }  
269 -  
270 - /// @brief Retrieves the default value `/DV` attribute of the form field, considering  
271 - /// inheritance, if the default value is a String.  
272 - ///  
273 - /// This function extracts the default value of the form field, accounting for potential  
274 - /// inheritance through the form hierarchy. It returns the value if it is a String, and an  
275 - /// empty string otherwise.  
276 - ///  
277 - /// @return A string containing the actual or inherited `/V` attribute of the form field, or  
278 - /// an empty string if the value is not present or not a String.  
279 - std::string default_value() const;  
280 -  
281 - /// @brief Returns the default appearance string for the form field, considering inheritance  
282 - /// from the field tree hierarchy and the document's /AcroForm dictionary.  
283 - ///  
284 - /// This method retrieves the field's /DA (default appearance) attribute. If the attribute  
285 - /// is not directly available, it checks the parent fields in the hierarchy for an inherited  
286 - /// value. If no value is found in the field hierarchy, it attempts to retrieve the /DA  
287 - /// attribute from the document's /AcroForm dictionary. The method returns an empty string  
288 - /// if no default appearance string is available or applicable.  
289 - ///  
290 - /// @return A string representing the default appearance, or an empty string if  
291 - /// no value is found.  
292 - std::string default_appearance() const;  
293 -  
294 - // Return the default resource dictionary for the field. This comes not from the field but  
295 - // from the document-level /AcroForm dictionary. While several PDF generates put a /DR key  
296 - // in the form field's dictionary, experimentation suggests that many popular readers,  
297 - // including Adobe Acrobat and Acrobat Reader, ignore any /DR item on the field.  
298 - QPDFObjectHandle getDefaultResources();  
299 -  
300 - // Return the quadding value, taking inheritance from the field tree into account. Returns 0  
301 - // if quadding is not specified. Look in /AcroForm if not found in the field hierarchy.  
302 - int getQuadding();  
303 -  
304 - // Return field flags from /Ff. The value is a logical or of pdf_form_field_flag_e as  
305 - // defined in qpdf/Constants.h//  
306 - int getFlags();  
307 -  
308 - // Methods for testing for particular types of form fields  
309 -  
310 - // Returns true if field is of type /Tx  
311 - bool isText();  
312 - // Returns true if field is of type /Btn and flags do not indicate some other type of  
313 - // button.  
314 - bool isCheckbox();  
315 -  
316 - // Returns true if field is a checkbox and is checked.  
317 - bool isChecked();  
318 -  
319 - // Returns true if field is of type /Btn and flags indicate that it is a radio button  
320 - bool isRadioButton();  
321 -  
322 - // Returns true if field is of type /Btn and flags indicate that it is a pushbutton  
323 - bool isPushbutton();  
324 -  
325 - // Returns true if fields if of type /Ch  
326 - bool isChoice();  
327 -  
328 - // Returns choices display values as UTF-8 strings  
329 - std::vector<std::string> getChoices();  
330 -  
331 - // Set an attribute to the given value. If you have a QPDFAcroFormDocumentHelper and you  
332 - // want to set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName  
333 - // instead.  
334 - void setFieldAttribute(std::string const& key, QPDFObjectHandle value);  
335 -  
336 - // Set an attribute to the given value as a Unicode string (UTF-16 BE encoded). The input  
337 - // string should be UTF-8 encoded. If you have a QPDFAcroFormDocumentHelper and you want to  
338 - // set the name of a field, use QPDFAcroFormDocumentHelper::setFormFieldName instead.  
339 - void setFieldAttribute(std::string const& key, std::string const& utf8_value);  
340 -  
341 - // Set /V (field value) to the given value. If need_appearances is true and the field type  
342 - // is either /Tx (text) or /Ch (choice), set /NeedAppearances to true. You can explicitly  
343 - // tell this method not to set /NeedAppearances if you are going to generate an appearance  
344 - // stream yourself. Starting with qpdf 8.3.0, this method handles fields of type /Btn  
345 - // (checkboxes, radio buttons, pushbuttons) specially. When setting a checkbox value, any  
346 - // value other than /Off will be treated as on, and the actual value set will be based on  
347 - // the appearance stream's /N dictionary, so the value that ends up in /V may not exactly  
348 - // match the value you pass in.  
349 - void setV(QPDFObjectHandle value, bool need_appearances = true);  
350 -  
351 - // Set /V (field value) to the given string value encoded as a Unicode string. The input  
352 - // value should be UTF-8 encoded. See comments above about /NeedAppearances.  
353 - void setV(std::string const& utf8_value, bool need_appearances = true);  
354 -  
355 - // Update the appearance stream for this field. Note that qpdf's ability to generate  
356 - // appearance streams is limited. We only generate appearance streams for streams of type  
357 - // text or choice. The appearance uses the default parameters provided in the file, and it  
358 - // only supports ASCII characters. Quadding is currently ignored. While this functionality  
359 - // is limited, it should do a decent job on properly constructed PDF files when field values  
360 - // are restricted to ASCII characters.  
361 - void generateAppearance(QPDFAnnotationObjectHelper&);  
362 -  
363 - private:  
364 - /// @brief Retrieves an entry from the document's /AcroForm dictionary using the specified  
365 - /// name.  
366 - ///  
367 - /// The method accesses the AcroForm dictionary within the root object of the PDF document.  
368 - /// If the the AcroForm dictionary contains the given field name, it retrieves the  
369 - /// corresponding entry. Otherwise, it returns a default-constructed object handle.  
370 - ///  
371 - /// @param name The name of the form field to retrieve.  
372 - /// @return A object handle corresponding to the specified name within the AcroForm  
373 - /// dictionary.  
374 - QPDFObjectHandle const&  
375 - from_AcroForm(std::string const& name) const  
376 - {  
377 - return {qpdf() ? qpdf()->getRoot()["/AcroForm"][name] : null_oh};  
378 - }  
379 -  
380 - void setRadioButtonValue(QPDFObjectHandle name);  
381 - void setCheckBoxValue(bool value);  
382 - void generateTextAppearance(QPDFAnnotationObjectHelper&);  
383 - QPDFObjectHandle  
384 - getFontFromResource(QPDFObjectHandle resources, std::string const& font_name);  
385 -  
386 - static const QPDFObjectHandle null_oh;  
387 - };  
388 -} // namespace qpdf::impl  
389 -  
390 -#endif // FORMFIELD_HH  
libqpdf/qpdf/QPDF_private.hh
@@ -28,8 +28,9 @@ namespace qpdf @@ -28,8 +28,9 @@ namespace qpdf
28 28
29 namespace impl 29 namespace impl
30 { 30 {
  31 + class AcroForm;
31 using Doc = QPDF::Doc; 32 using Doc = QPDF::Doc;
32 - } 33 + } // namespace impl
33 34
34 class Doc: public QPDF 35 class Doc: public QPDF
35 { 36 {
@@ -374,11 +375,33 @@ class QPDF::Doc @@ -374,11 +375,33 @@ class QPDF::Doc
374 bool reconstructed_xref() const; 375 bool reconstructed_xref() const;
375 376
376 QPDFAcroFormDocumentHelper& 377 QPDFAcroFormDocumentHelper&
  378 + acroform_dh()
  379 + {
  380 + if (!acroform_) {
  381 + no_inspection();
  382 + init_acroform();
  383 + }
  384 + return *acroform_dh_;
  385 + }
  386 +
  387 + /// @brief Retrieves the shared impl::AcroForm instance associated with the document.
  388 + ///
  389 + /// @note The AcroForm class caches the form field structure for efficiency. If any part
  390 + /// of the form field structure is modified directly the `validate` method MUST be
  391 + /// called before calling any other AcroForm methods in order to refresh the cache.
  392 + ///
  393 + /// If the AcroForm instance has not already been initialized, the `init_acroform()`
  394 + /// function is called to initialize it.
  395 + ///
  396 + /// @return A reference to the shared AcroForm object of the document.
  397 + ///
  398 + /// @since 12.3
  399 + impl::AcroForm&
377 acroform() 400 acroform()
378 { 401 {
379 if (!acroform_) { 402 if (!acroform_) {
380 no_inspection(); 403 no_inspection();
381 - acroform_ = std::make_unique<QPDFAcroFormDocumentHelper>(qpdf); 404 + init_acroform();
382 } 405 }
383 return *acroform_; 406 return *acroform_;
384 } 407 }
@@ -438,8 +461,11 @@ class QPDF::Doc @@ -438,8 +461,11 @@ class QPDF::Doc
438 qpdf::Doc::Config cf; 461 qpdf::Doc::Config cf;
439 462
440 private: 463 private:
  464 + void init_acroform();
  465 +
441 // Document Helpers; 466 // Document Helpers;
442 - std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_; 467 + std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_dh_;
  468 + impl::AcroForm* acroform_{nullptr};
443 std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files_; 469 std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files_;
444 std::unique_ptr<QPDFOutlineDocumentHelper> outlines_; 470 std::unique_ptr<QPDFOutlineDocumentHelper> outlines_;
445 std::unique_ptr<QPDFPageDocumentHelper> page_dh_; 471 std::unique_ptr<QPDFPageDocumentHelper> page_dh_;
@@ -1173,7 +1199,7 @@ class QPDF::Doc::Pages: Common @@ -1173,7 +1199,7 @@ class QPDF::Doc::Pages: Common
1173 void flatten_annotations_for_page( 1199 void flatten_annotations_for_page(
1174 QPDFPageObjectHelper& page, 1200 QPDFPageObjectHelper& page,
1175 QPDFObjectHandle& resources, 1201 QPDFObjectHandle& resources,
1176 - QPDFAcroFormDocumentHelper& afdh, 1202 + impl::AcroForm& afdh,
1177 int required_flags, 1203 int required_flags,
1178 int forbidden_flags); 1204 int forbidden_flags);
1179 1205
qpdf/test_driver.cc
@@ -3567,7 +3567,6 @@ test_101(QPDF&amp; pdf, char const* arg2) @@ -3567,7 +3567,6 @@ test_101(QPDF&amp; pdf, char const* arg2)
3567 std::cout << oh.unparseResolved() << '\n'; 3567 std::cout << oh.unparseResolved() << '\n';
3568 } 3568 }
3569 3569
3570 -  
3571 auto test_helper_throws = [&qpdf](auto helper_func) { 3570 auto test_helper_throws = [&qpdf](auto helper_func) {
3572 bool thrown = false; 3571 bool thrown = false;
3573 try { 3572 try {