1 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
2
3 #include <qpdf/QTC.hh>
4 #include <qpdf/QPDFPageDocumentHelper.hh>
5 #include <qpdf/ResourceFinder.hh>
6 #include <qpdf/Pl_Buffer.hh>
7 #include <qpdf/QUtil.hh>
8
~Members()9 QPDFAcroFormDocumentHelper::Members::~Members()
10 {
11 }
12
Members()13 QPDFAcroFormDocumentHelper::Members::Members() :
14 cache_valid(false)
15 {
16 }
17
QPDFAcroFormDocumentHelper(QPDF & qpdf)18 QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) :
19 QPDFDocumentHelper(qpdf),
20 m(new Members())
21 {
22 // We have to analyze up front. Otherwise, when we are adding
23 // annotations and fields, we are in a temporarily unstable
24 // configuration where some widget annotations are not reachable.
25 analyze();
26 }
27
28 void
invalidateCache()29 QPDFAcroFormDocumentHelper::invalidateCache()
30 {
31 this->m->cache_valid = false;
32 this->m->field_to_annotations.clear();
33 this->m->annotation_to_field.clear();
34 }
35
36 bool
hasAcroForm()37 QPDFAcroFormDocumentHelper::hasAcroForm()
38 {
39 return this->qpdf.getRoot().hasKey("/AcroForm");
40 }
41
42 QPDFObjectHandle
getOrCreateAcroForm()43 QPDFAcroFormDocumentHelper::getOrCreateAcroForm()
44 {
45 auto acroform = this->qpdf.getRoot().getKey("/AcroForm");
46 if (! acroform.isDictionary())
47 {
48 acroform = this->qpdf.makeIndirectObject(
49 QPDFObjectHandle::newDictionary());
50 this->qpdf.getRoot().replaceKey("/AcroForm", acroform);
51 }
52 return acroform;
53 }
54
55 void
addFormField(QPDFFormFieldObjectHelper ff)56 QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
57 {
58 auto acroform = getOrCreateAcroForm();
59 auto fields = acroform.getKey("/Fields");
60 if (! fields.isArray())
61 {
62 fields = QPDFObjectHandle::newArray();
63 acroform.replaceKey("/Fields", fields);
64 }
65 fields.appendItem(ff.getObjectHandle());
66 std::set<QPDFObjGen> visited;
67 traverseField(
68 ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited);
69 }
70
71 void
addAndRenameFormFields(std::vector<QPDFObjectHandle> fields)72 QPDFAcroFormDocumentHelper::addAndRenameFormFields(
73 std::vector<QPDFObjectHandle> fields)
74 {
75 analyze();
76 std::map<std::string, std::string> renames;
77 std::list<QPDFObjectHandle> queue;
78 queue.insert(queue.begin(), fields.begin(), fields.end());
79 std::set<QPDFObjGen> seen;
80 while (! queue.empty())
81 {
82 QPDFObjectHandle obj = queue.front();
83 queue.pop_front();
84 auto og = obj.getObjGen();
85 if (seen.count(og))
86 {
87 // loop
88 continue;
89 }
90 seen.insert(og);
91 auto kids = obj.getKey("/Kids");
92 if (kids.isArray())
93 {
94 for (auto kid: kids.aitems())
95 {
96 queue.push_back(kid);
97 }
98 }
99
100 if (obj.hasKey("/T"))
101 {
102 // Find something we can append to the partial name that
103 // makes the fully qualified name unique. When we find
104 // something, reuse the same suffix for all fields in this
105 // group with the same name. We can only change the name
106 // of fields that have /T, and this field's /T is always
107 // at the end of the fully qualified name, appending to /T
108 // has the effect of appending the same thing to the fully
109 // qualified name.
110 std::string old_name =
111 QPDFFormFieldObjectHelper(obj).getFullyQualifiedName();
112 if (renames.count(old_name) == 0)
113 {
114 std::string new_name = old_name;
115 int suffix = 0;
116 std::string append;
117 while (! getFieldsWithQualifiedName(new_name).empty())
118 {
119 ++suffix;
120 append = "+" + QUtil::int_to_string(suffix);
121 new_name = old_name + append;
122 }
123 renames[old_name] = append;
124 }
125 std::string append = renames[old_name];
126 if (! append.empty())
127 {
128 obj.replaceKey(
129 "/T", QPDFObjectHandle::newUnicodeString(
130 obj.getKey("/T").getUTF8Value() + append));
131 }
132 }
133 }
134
135 for (auto i: fields)
136 {
137 addFormField(i);
138 }
139 }
140
141 void
removeFormFields(std::set<QPDFObjGen> const & to_remove)142 QPDFAcroFormDocumentHelper::removeFormFields(
143 std::set<QPDFObjGen> const& to_remove)
144 {
145 auto acroform = this->qpdf.getRoot().getKey("/AcroForm");
146 if (! acroform.isDictionary())
147 {
148 return;
149 }
150 auto fields = acroform.getKey("/Fields");
151 if (! fields.isArray())
152 {
153 return;
154 }
155
156 for (auto const& og: to_remove)
157 {
158 auto annotations = this->m->field_to_annotations.find(og);
159 if (annotations != this->m->field_to_annotations.end())
160 {
161 for (auto aoh: annotations->second)
162 {
163 this->m->annotation_to_field.erase(
164 aoh.getObjectHandle().getObjGen());
165 }
166 this->m->field_to_annotations.erase(og);
167 }
168 auto name = this->m->field_to_name.find(og);
169 if (name != this->m->field_to_name.end())
170 {
171 this->m->name_to_fields[name->second].erase(og);
172 if (this->m->name_to_fields[name->second].empty())
173 {
174 this->m->name_to_fields.erase(name->second);
175 }
176 this->m->field_to_name.erase(og);
177 }
178 }
179
180 int i = 0;
181 while (i < fields.getArrayNItems())
182 {
183 auto field = fields.getArrayItem(i);
184 if (to_remove.count(field.getObjGen()))
185 {
186 fields.eraseItem(i);
187 }
188 else
189 {
190 ++i;
191 }
192 }
193 }
194
195 void
setFormFieldName(QPDFFormFieldObjectHelper ff,std::string const & name)196 QPDFAcroFormDocumentHelper::setFormFieldName(
197 QPDFFormFieldObjectHelper ff, std::string const& name)
198 {
199 ff.setFieldAttribute("/T", name);
200 std::set<QPDFObjGen> visited;
201 auto ff_oh = ff.getObjectHandle();
202 traverseField(ff_oh, ff_oh.getKey("/Parent"), 0, visited);
203 }
204
205 std::vector<QPDFFormFieldObjectHelper>
getFormFields()206 QPDFAcroFormDocumentHelper::getFormFields()
207 {
208 analyze();
209 std::vector<QPDFFormFieldObjectHelper> result;
210 for (std::map<QPDFObjGen,
211 std::vector<QPDFAnnotationObjectHelper> >::iterator iter =
212 this->m->field_to_annotations.begin();
213 iter != this->m->field_to_annotations.end(); ++iter)
214 {
215 result.push_back(this->qpdf.getObjectByObjGen((*iter).first));
216 }
217 return result;
218 }
219
220 std::set<QPDFObjGen>
getFieldsWithQualifiedName(std::string const & name)221 QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(
222 std::string const& name)
223 {
224 analyze();
225 // Keep from creating an empty entry
226 std::set<QPDFObjGen> result;
227 auto iter = this->m->name_to_fields.find(name);
228 if (iter != this->m->name_to_fields.end())
229 {
230 result = iter->second;
231 }
232 return result;
233 }
234
235 std::vector<QPDFAnnotationObjectHelper>
getAnnotationsForField(QPDFFormFieldObjectHelper h)236 QPDFAcroFormDocumentHelper::getAnnotationsForField(QPDFFormFieldObjectHelper h)
237 {
238 analyze();
239 std::vector<QPDFAnnotationObjectHelper> result;
240 QPDFObjGen og(h.getObjectHandle().getObjGen());
241 if (this->m->field_to_annotations.count(og))
242 {
243 result = this->m->field_to_annotations[og];
244 }
245 return result;
246 }
247
248 std::vector<QPDFAnnotationObjectHelper>
getWidgetAnnotationsForPage(QPDFPageObjectHelper h)249 QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h)
250 {
251 return h.getAnnotations("/Widget");
252 }
253
254 std::vector<QPDFFormFieldObjectHelper>
getFormFieldsForPage(QPDFPageObjectHelper ph)255 QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph)
256 {
257 analyze();
258 std::set<QPDFObjGen> added;
259 std::vector<QPDFFormFieldObjectHelper> result;
260 auto widget_annotations = getWidgetAnnotationsForPage(ph);
261 for (auto annot: widget_annotations)
262 {
263 auto field = getFieldForAnnotation(annot);
264 field = field.getTopLevelField();
265 auto og = field.getObjectHandle().getObjGen();
266 if (! added.count(og))
267 {
268 added.insert(og);
269 if (field.getObjectHandle().isDictionary())
270 {
271 result.push_back(field);
272 }
273 }
274 }
275 return result;
276 }
277
278 QPDFFormFieldObjectHelper
getFieldForAnnotation(QPDFAnnotationObjectHelper h)279 QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h)
280 {
281 QPDFObjectHandle oh = h.getObjectHandle();
282 QPDFFormFieldObjectHelper result(QPDFObjectHandle::newNull());
283 if (! (oh.isDictionary() &&
284 oh.getKey("/Subtype").isName() &&
285 (oh.getKey("/Subtype").getName() == "/Widget")))
286 {
287 return result;
288 }
289 analyze();
290 QPDFObjGen og(oh.getObjGen());
291 if (this->m->annotation_to_field.count(og))
292 {
293 result = this->m->annotation_to_field[og];
294 }
295 return result;
296 }
297
298 void
analyze()299 QPDFAcroFormDocumentHelper::analyze()
300 {
301 if (this->m->cache_valid)
302 {
303 return;
304 }
305 this->m->cache_valid = true;
306 QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm");
307 if (! (acroform.isDictionary() && acroform.hasKey("/Fields")))
308 {
309 return;
310 }
311 QPDFObjectHandle fields = acroform.getKey("/Fields");
312 if (! fields.isArray())
313 {
314 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper fields not array");
315 acroform.warnIfPossible(
316 "/Fields key of /AcroForm dictionary is not an array; ignoring");
317 fields = QPDFObjectHandle::newArray();
318 }
319
320 // Traverse /AcroForm to find annotations and map them
321 // bidirectionally to fields.
322
323 std::set<QPDFObjGen> visited;
324 int nfields = fields.getArrayNItems();
325 QPDFObjectHandle null(QPDFObjectHandle::newNull());
326 for (int i = 0; i < nfields; ++i)
327 {
328 traverseField(fields.getArrayItem(i), null, 0, visited);
329 }
330
331 // All Widget annotations should have been encountered by
332 // traversing /AcroForm, but in case any weren't, find them by
333 // walking through pages, and treat any widget annotation that is
334 // not associated with a field as its own field. This just ensures
335 // that requesting the field for any annotation we find through a
336 // page's /Annots list will have some associated field. Note that
337 // a file that contains this kind of error will probably not
338 // actually work with most viewers.
339
340 QPDFPageDocumentHelper dh(this->qpdf);
341 std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
342 for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
343 iter != pages.end(); ++iter)
344 {
345 QPDFPageObjectHelper ph(*iter);
346 std::vector<QPDFAnnotationObjectHelper> annots =
347 getWidgetAnnotationsForPage(ph);
348 for (std::vector<QPDFAnnotationObjectHelper>::iterator i2 =
349 annots.begin();
350 i2 != annots.end(); ++i2)
351 {
352 QPDFObjectHandle annot((*i2).getObjectHandle());
353 QPDFObjGen og(annot.getObjGen());
354 if (this->m->annotation_to_field.count(og) == 0)
355 {
356 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper orphaned widget");
357 // This is not supposed to happen, but it's easy
358 // enough for us to handle this case. Treat the
359 // annotation as its own field. This could allow qpdf
360 // to sensibly handle a case such as a PDF creator
361 // adding a self-contained annotation (merged with the
362 // field dictionary) to the page's /Annots array and
363 // forgetting to also put it in /AcroForm.
364 annot.warnIfPossible(
365 "this widget annotation is not"
366 " reachable from /AcroForm in the document catalog");
367 this->m->annotation_to_field[og] =
368 QPDFFormFieldObjectHelper(annot);
369 this->m->field_to_annotations[og].push_back(
370 QPDFAnnotationObjectHelper(annot));
371 }
372 }
373 }
374 }
375
376 void
traverseField(QPDFObjectHandle field,QPDFObjectHandle parent,int depth,std::set<QPDFObjGen> & visited)377 QPDFAcroFormDocumentHelper::traverseField(
378 QPDFObjectHandle field, QPDFObjectHandle parent, int depth,
379 std::set<QPDFObjGen>& visited)
380 {
381 if (depth > 100)
382 {
383 // Arbitrarily cut off recursion at a fixed depth to avoid
384 // specially crafted files that could cause stack overflow.
385 return;
386 }
387 if (! field.isIndirect())
388 {
389 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper direct field");
390 field.warnIfPossible(
391 "encountered a direct object as a field or annotation while"
392 " traversing /AcroForm; ignoring field or annotation");
393 return;
394 }
395 if (! field.isDictionary())
396 {
397 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper non-dictionary field");
398 field.warnIfPossible(
399 "encountered a non-dictionary as a field or annotation while"
400 " traversing /AcroForm; ignoring field or annotation");
401 return;
402 }
403 QPDFObjGen og(field.getObjGen());
404 if (visited.count(og) != 0)
405 {
406 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop");
407 field.warnIfPossible("loop detected while traversing /AcroForm");
408 return;
409 }
410 visited.insert(og);
411
412 // A dictionary encountered while traversing the /AcroForm field
413 // may be a form field, an annotation, or the merger of the two. A
414 // field that has no fields below it is a terminal. If a terminal
415 // field looks like an annotation, it is an annotation because
416 // annotation dictionary fields can be merged with terminal field
417 // dictionaries. Otherwise, the annotation fields might be there
418 // to be inherited by annotations below it.
419
420 bool is_annotation = false;
421 bool is_field = (0 == depth);
422 QPDFObjectHandle kids = field.getKey("/Kids");
423 if (kids.isArray())
424 {
425 is_field = true;
426 int nkids = kids.getArrayNItems();
427 for (int k = 0; k < nkids; ++k)
428 {
429 traverseField(kids.getArrayItem(k), field, 1 + depth, visited);
430 }
431 }
432 else
433 {
434 if (field.hasKey("/Parent"))
435 {
436 is_field = true;
437 }
438 if (field.hasKey("/Subtype") ||
439 field.hasKey("/Rect") ||
440 field.hasKey("/AP"))
441 {
442 is_annotation = true;
443 }
444 }
445
446 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found",
447 (depth == 0) ? 0 : 1);
448 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found",
449 (is_field ? 0 : 1));
450
451 if (is_annotation)
452 {
453 QPDFObjectHandle our_field = (is_field ? field : parent);
454 this->m->field_to_annotations[our_field.getObjGen()].push_back(
455 QPDFAnnotationObjectHelper(field));
456 this->m->annotation_to_field[og] =
457 QPDFFormFieldObjectHelper(our_field);
458 }
459
460 if (is_field && (field.hasKey("/T")))
461 {
462 QPDFFormFieldObjectHelper foh(field);
463 auto f_og = field.getObjGen();
464 std::string name = foh.getFullyQualifiedName();
465 auto old = this->m->field_to_name.find(f_og);
466 if (old != this->m->field_to_name.end())
467 {
468 // We might be updating after a name change, so remove any
469 // old information
470 std::string old_name = old->second;
471 this->m->name_to_fields[old_name].erase(f_og);
472 }
473 this->m->field_to_name[f_og] = name;
474 this->m->name_to_fields[name].insert(f_og);
475 }
476 }
477
478 bool
getNeedAppearances()479 QPDFAcroFormDocumentHelper::getNeedAppearances()
480 {
481 bool result = false;
482 QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm");
483 if (acroform.isDictionary() &&
484 acroform.getKey("/NeedAppearances").isBool())
485 {
486 result = acroform.getKey("/NeedAppearances").getBoolValue();
487 }
488 return result;
489 }
490
491 void
setNeedAppearances(bool val)492 QPDFAcroFormDocumentHelper::setNeedAppearances(bool val)
493 {
494 QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm");
495 if (! acroform.isDictionary())
496 {
497 this->qpdf.getRoot().warnIfPossible(
498 "ignoring call to QPDFAcroFormDocumentHelper::setNeedAppearances"
499 " on a file that lacks an /AcroForm dictionary");
500 return;
501 }
502 if (val)
503 {
504 acroform.replaceKey("/NeedAppearances",
505 QPDFObjectHandle::newBool(true));
506 }
507 else
508 {
509 acroform.removeKey("/NeedAppearances");
510 }
511 }
512
513 void
generateAppearancesIfNeeded()514 QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded()
515 {
516 if (! getNeedAppearances())
517 {
518 return;
519 }
520
521 QPDFPageDocumentHelper pdh(this->qpdf);
522 std::vector<QPDFPageObjectHelper> pages = pdh.getAllPages();
523 for (std::vector<QPDFPageObjectHelper>::iterator page_iter =
524 pages.begin();
525 page_iter != pages.end(); ++page_iter)
526 {
527 std::vector<QPDFAnnotationObjectHelper> annotations =
528 getWidgetAnnotationsForPage(*page_iter);
529 for (std::vector<QPDFAnnotationObjectHelper>::iterator annot_iter =
530 annotations.begin();
531 annot_iter != annotations.end(); ++annot_iter)
532 {
533 QPDFAnnotationObjectHelper& aoh = *annot_iter;
534 QPDFFormFieldObjectHelper ffh =
535 getFieldForAnnotation(aoh);
536 if (ffh.getFieldType() == "/Btn")
537 {
538 // Rather than generating appearances for button
539 // fields, rely on what's already there. Just make
540 // sure /AS is consistent with /V, which we can do by
541 // resetting the value of the field back to itself.
542 // This code is referenced in a comment in
543 // QPDFFormFieldObjectHelper::generateAppearance.
544 if (ffh.isRadioButton() || ffh.isCheckbox())
545 {
546 ffh.setV(ffh.getValue());
547 }
548 }
549 else
550 {
551 ffh.generateAppearance(aoh);
552 }
553 }
554 }
555 setNeedAppearances(false);
556 }
557
558 void
adjustInheritedFields(QPDFObjectHandle obj,bool override_da,std::string const & from_default_da,bool override_q,int from_default_q)559 QPDFAcroFormDocumentHelper::adjustInheritedFields(
560 QPDFObjectHandle obj,
561 bool override_da, std::string const& from_default_da,
562 bool override_q, int from_default_q)
563 {
564 // Override /Q or /DA if needed. If this object has a field type,
565 // directly or inherited, it is a field and not just an
566 // annotation. In that case, we need to override if we are getting
567 // a value from the document that is different from the value we
568 // would have gotten from the old document. We must take care not
569 // to override an explicit value. It's possible that /FT may be
570 // inherited by lower fields that may explicitly set /DA or /Q or
571 // that this is a field whose type does not require /DA or /Q and
572 // we may be put a value on the field that is unused. This is
573 // harmless, so it's not worth trying to work around.
574
575 auto has_explicit = [](QPDFFormFieldObjectHelper& field,
576 std::string const& key) {
577 if (field.getObjectHandle().hasKey(key))
578 {
579 return true;
580 }
581 auto oh = field.getInheritableFieldValue(key);
582 if (! oh.isNull())
583 {
584 return true;
585 }
586 return false;
587 };
588
589 if (override_da || override_q)
590 {
591 QPDFFormFieldObjectHelper cur_field(obj);
592 if (override_da && (! has_explicit(cur_field, "/DA")))
593 {
594 std::string da = cur_field.getDefaultAppearance();
595 if (da != from_default_da)
596 {
597 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper override da");
598 obj.replaceKey(
599 "/DA",
600 QPDFObjectHandle::newUnicodeString(
601 from_default_da));
602 }
603 }
604 if (override_q && (! has_explicit(cur_field, "/Q")))
605 {
606 int q = cur_field.getQuadding();
607 if (q != from_default_q)
608 {
609 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper override q");
610 obj.replaceKey(
611 "/Q",
612 QPDFObjectHandle::newInteger(from_default_q));
613 }
614 }
615 }
616 }
617
618 class ResourceReplacer: public QPDFObjectHandle::TokenFilter
619 {
620 public:
621 ResourceReplacer(
622 std::map<std::string,
623 std::map<std::string,
624 std::string>> const& dr_map,
625 std::map<std::string,
626 std::map<std::string,
627 std::set<size_t>>> const& rnames);
628 virtual ~ResourceReplacer() = default;
629 virtual void handleToken(QPDFTokenizer::Token const&) override;
630
631 private:
632 size_t offset;
633 std::map<std::string, std::map<size_t, std::string>> to_replace;
634 };
635
ResourceReplacer(std::map<std::string,std::map<std::string,std::string>> const & dr_map,std::map<std::string,std::map<std::string,std::set<size_t>>> const & rnames)636 ResourceReplacer::ResourceReplacer(
637 std::map<std::string,
638 std::map<std::string, std::string>> const& dr_map,
639 std::map<std::string,
640 std::map<std::string, std::set<size_t>>> const& rnames)
641 :
642 offset(0)
643 {
644 // We have:
645 // * dr_map[resource_type][key] == new_key
646 // * rnames[resource_type][key] == set of offsets
647 //
648 // We want:
649 // * to_replace[key][offset] = new_key
650
651 for (auto const& rn_iter: rnames)
652 {
653 std::string const& rtype = rn_iter.first;
654 auto dr_map_rtype = dr_map.find(rtype);
655 if (dr_map_rtype == dr_map.end())
656 {
657 continue;
658 }
659 auto const& key_offsets = rn_iter.second;
660 for (auto const& ko_iter: key_offsets)
661 {
662 std::string const& old_key = ko_iter.first;
663 auto dr_map_rtype_old = dr_map_rtype->second.find(old_key);
664 if (dr_map_rtype_old == dr_map_rtype->second.end())
665 {
666 continue;
667 }
668 auto const& offsets = ko_iter.second;
669 for (auto const& o_iter: offsets)
670 {
671 to_replace[old_key][o_iter] = dr_map_rtype_old->second;
672 }
673 }
674 }
675 }
676
677 void
handleToken(QPDFTokenizer::Token const & token)678 ResourceReplacer::handleToken(QPDFTokenizer::Token const& token)
679 {
680 bool wrote = false;
681 if (token.getType() == QPDFTokenizer::tt_name)
682 {
683 std::string name =
684 QPDFObjectHandle::newName(token.getValue()).getName();
685 if (to_replace.count(name) &&
686 to_replace[name].count(offset))
687 {
688 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper replaced DA token");
689 write(to_replace[name][offset]);
690 wrote = true;
691 }
692 }
693 this->offset += token.getRawValue().length();
694 if (! wrote)
695 {
696 writeToken(token);
697 }
698 }
699
700 void
adjustDefaultAppearances(QPDFObjectHandle obj,std::map<std::string,std::map<std::string,std::string>> const & dr_map)701 QPDFAcroFormDocumentHelper::adjustDefaultAppearances(
702 QPDFObjectHandle obj,
703 std::map<std::string,
704 std::map<std::string, std::string>> const& dr_map)
705 {
706 // This method is called on a field that has been copied from
707 // another file but whose /DA still refers to resources in the
708 // original file's /DR.
709
710 // When appearance streams are generated for variable text fields
711 // (see ISO 32000 PDF spec section 12.7.3.3), the field's /DA is
712 // used to generate content of the appearance stream. /DA contains
713 // references to resources that may be resolved in the document's
714 // /DR dictionary, which appears in the document's /AcroForm
715 // dictionary. For fields that we copied from other documents, we
716 // need to ensure that resources are mapped correctly in the case
717 // of conflicting names. For example, if a.pdf's /DR has /F1
718 // pointing to one font and b.pdf's /DR also has /F1 but it points
719 // elsewhere, we need to make sure appearance streams of fields
720 // copied from b.pdf into a.pdf use whatever font /F1 meant in
721 // b.pdf, not whatever it means in a.pdf. This method takes care
722 // of that. It is only called on fields copied from foreign files.
723
724 // A few notes:
725 //
726 // * If the from document's /DR and the current document's /DR
727 // have conflicting keys, we have already resolved the conflicts
728 // before calling this method. The dr_map parameter contains the
729 // mapping from old keys to new keys.
730 //
731 // * /DA may be inherited from the document's /AcroForm
732 // dictionary. By the time this method has been called, we have
733 // already copied any document-level values into the fields to
734 // avoid having them inherit from the new document. This was
735 // done in adjustInheritedFields.
736
737 auto DA = obj.getKey("/DA");
738 if (! DA.isString())
739 {
740 return;
741 }
742
743 // Find names in /DA. /DA is a string that contains content
744 // stream-like code, so we create a stream out of the string and
745 // then filter it. We don't attach the stream to anything, so it
746 // will get discarded.
747 ResourceFinder rf;
748 auto da_stream = QPDFObjectHandle::newStream(
749 &this->qpdf, DA.getUTF8Value());
750 try
751 {
752 auto nwarnings = this->qpdf.numWarnings();
753 da_stream.parseAsContents(&rf);
754 if (this->qpdf.numWarnings() > nwarnings)
755 {
756 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper /DA parse error");
757 }
758 }
759 catch (std::exception& e)
760 {
761 // No way to reproduce in test suite right now since error
762 // conditions are converted to warnings.
763 obj.warnIfPossible(
764 std::string("Unable to parse /DA: ") + e.what() +
765 "; this form field may not update properly");
766 return;
767 }
768
769 // Regenerate /DA by filtering its tokens.
770 ResourceReplacer rr(dr_map, rf.getNamesByResourceType());
771 Pl_Buffer buf_pl("filtered DA");
772 da_stream.filterAsContents(&rr, &buf_pl);
773 PointerHolder<Buffer> buf = buf_pl.getBuffer();
774 std::string new_da(
775 reinterpret_cast<char*>(buf->getBuffer()), buf->getSize());
776 obj.replaceKey("/DA", QPDFObjectHandle::newString(new_da));
777 }
778
779 void
adjustAppearanceStream(QPDFObjectHandle stream,std::map<std::string,std::map<std::string,std::string>> dr_map)780 QPDFAcroFormDocumentHelper::adjustAppearanceStream(
781 QPDFObjectHandle stream,
782 std::map<std::string, std::map<std::string, std::string>> dr_map)
783 {
784 // We don't have to modify appearance streams or their resource
785 // dictionaries for them to display properly, but we need to do so
786 // to make them save to regenerate. Suppose an appearance stream
787 // as a font /F1 that is different from /F1 in /DR, and that when
788 // we copy the field, /F1 is remapped to /F1_1. When the field is
789 // regenerated, /F1_1 won't appear in the stream's resource
790 // dictionary, so the regenerated appearance stream will revert to
791 // the /F1_1 in /DR. If we adjust existing appearance streams, we
792 // are protected from this problem.
793
794 auto dict = stream.getDict();
795 auto resources = dict.getKey("/Resources");
796
797 // Make sure this stream has its own private resource dictionary.
798 bool was_indirect = resources.isIndirect();
799 resources = resources.shallowCopy();
800 if (was_indirect)
801 {
802 resources = this->qpdf.makeIndirectObject(resources);
803 }
804 dict.replaceKey("/Resources", resources);
805 // Create a dictionary with top-level keys so we can use
806 // mergeResources to force them to be unshared. We will also use
807 // this to resolve conflicts that may already be in the resource
808 // dictionary.
809 auto merge_with = QPDFObjectHandle::newDictionary();
810 for (auto const& top_key: dr_map)
811 {
812 merge_with.replaceKey(
813 top_key.first, QPDFObjectHandle::newDictionary());
814 }
815 resources.mergeResources(merge_with);
816 // Rename any keys in the resource dictionary that we
817 // remapped.
818 for (auto const& i1: dr_map)
819 {
820 std::string const& top_key = i1.first;
821 auto subdict = resources.getKey(top_key);
822 if (! subdict.isDictionary())
823 {
824 continue;
825 }
826 for (auto const& i2: i1.second)
827 {
828 std::string const& old_key = i2.first;
829 std::string const& new_key = i2.second;
830 auto existing_new = subdict.getKey(new_key);
831 if (! existing_new.isNull())
832 {
833 // The resource dictionary already has a key in it
834 // matching what we remapped an old key to, so we'll
835 // have to move it out of the way. Stick it in
836 // merge_with, which we will re-merge with the
837 // dictionary when we're done. We know merge_with
838 // already has dictionaries for all the top keys.
839 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper ap conflict");
840 merge_with.getKey(top_key).replaceKey(new_key, existing_new);
841 }
842 auto existing_old = subdict.getKey(old_key);
843 if (! existing_old.isNull())
844 {
845 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper ap rename");
846 subdict.replaceKey(new_key, existing_old);
847 subdict.removeKey(old_key);
848 }
849 }
850 }
851 // Deal with any any conflicts by re-merging with merge_with and
852 // updating our local copy of dr_map, which we will use to modify
853 // the stream contents.
854 resources.mergeResources(merge_with, &dr_map);
855 // Remove empty subdictionaries
856 for (auto iter: resources.ditems())
857 {
858 if (iter.second.isDictionary() &&
859 (iter.second.getKeys().size() == 0))
860 {
861 resources.removeKey(iter.first);
862 }
863 }
864
865 // Now attach a token filter to replace the actual resources.
866 ResourceFinder rf;
867 try
868 {
869 auto nwarnings = this->qpdf.numWarnings();
870 stream.parseAsContents(&rf);
871 if (this->qpdf.numWarnings() > nwarnings)
872 {
873 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper AP parse error");
874 }
875 auto rr = new ResourceReplacer(dr_map, rf.getNamesByResourceType());
876 PointerHolder<QPDFObjectHandle::TokenFilter> tf = rr;
877 stream.addTokenFilter(tf);
878 }
879 catch (std::exception& e)
880 {
881 // No way to reproduce in test suite right now since error
882 // conditions are converted to warnings.
883 stream.warnIfPossible(
884 std::string("Unable to parse appearance stream: ") + e.what());
885 }
886 }
887
888 void
transformAnnotations(QPDFObjectHandle old_annots,std::vector<QPDFObjectHandle> & new_annots,std::vector<QPDFObjectHandle> & new_fields,std::set<QPDFObjGen> & old_fields,QPDFMatrix const & cm,QPDF * from_qpdf,QPDFAcroFormDocumentHelper * from_afdh)889 QPDFAcroFormDocumentHelper::transformAnnotations(
890 QPDFObjectHandle old_annots,
891 std::vector<QPDFObjectHandle>& new_annots,
892 std::vector<QPDFObjectHandle>& new_fields,
893 std::set<QPDFObjGen>& old_fields,
894 QPDFMatrix const& cm,
895 QPDF* from_qpdf,
896 QPDFAcroFormDocumentHelper* from_afdh)
897 {
898 PointerHolder<QPDFAcroFormDocumentHelper> afdhph;
899 if (! from_qpdf)
900 {
901 // Assume these are from the same QPDF.
902 from_qpdf = &this->qpdf;
903 from_afdh = this;
904 }
905 else if ((from_qpdf != &this->qpdf) && (! from_afdh))
906 {
907 afdhph = new QPDFAcroFormDocumentHelper(*from_qpdf);
908 from_afdh = afdhph.getPointer();
909 }
910 bool foreign = (from_qpdf != &this->qpdf);
911
912 // It's possible that we will transform annotations that don't
913 // include any form fields. This code takes care not to muck
914 // around with /AcroForm unless we have to.
915
916 QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm");
917 QPDFObjectHandle from_acroform = from_qpdf->getRoot().getKey("/AcroForm");
918
919 // /DA and /Q may be inherited from the document-level /AcroForm
920 // dictionary. If we are copying a foreign stream and the stream
921 // is getting one of these values from its document's /AcroForm,
922 // we will need to copy the value explicitly so that it doesn't
923 // start getting its default from the destination document.
924 bool override_da = false;
925 bool override_q = false;
926 std::string from_default_da;
927 int from_default_q = 0;
928 // If we copy any form fields, we will need to merge the source
929 // document's /DR into this document's /DR.
930 QPDFObjectHandle from_dr = QPDFObjectHandle::newNull();
931 if (foreign)
932 {
933 std::string default_da;
934 int default_q = 0;
935 if (acroform.isDictionary())
936 {
937 if (acroform.getKey("/DA").isString())
938 {
939 default_da = acroform.getKey("/DA").getUTF8Value();
940 }
941 if (acroform.getKey("/Q").isInteger())
942 {
943 default_q = acroform.getKey("/Q").getIntValueAsInt();
944 }
945 }
946 if (from_acroform.isDictionary())
947 {
948 if (from_acroform.getKey("/DR").isDictionary())
949 {
950 from_dr = from_acroform.getKey("/DR");
951 if (! from_dr.isIndirect())
952 {
953 from_dr = from_qpdf->makeIndirectObject(from_dr);
954 }
955 from_dr = this->qpdf.copyForeignObject(from_dr);
956 }
957 if (from_acroform.getKey("/DA").isString())
958 {
959 from_default_da = from_acroform.getKey("/DA").getUTF8Value();
960 }
961 if (from_acroform.getKey("/Q").isInteger())
962 {
963 from_default_q = from_acroform.getKey("/Q").getIntValueAsInt();
964 }
965 }
966 if (from_default_da != default_da)
967 {
968 override_da = true;
969 }
970 if (from_default_q != default_q)
971 {
972 override_q = true;
973 }
974 }
975
976 // If we have to merge /DR, we will need a mapping of conflicting
977 // keys for rewriting /DA. Set this up for lazy initialization in
978 // case we encounter any form fields.
979 std::map<std::string, std::map<std::string, std::string>> dr_map;
980 bool initialized_dr_map = false;
981 QPDFObjectHandle dr = QPDFObjectHandle::newNull();
982 auto init_dr_map = [&]() {
983 if (! initialized_dr_map)
984 {
985 initialized_dr_map = true;
986 // Ensure that we have a /DR that is an indirect
987 // dictionary object.
988 if (! acroform.isDictionary())
989 {
990 acroform = getOrCreateAcroForm();
991 }
992 dr = acroform.getKey("/DR");
993 if (! dr.isDictionary())
994 {
995 dr = QPDFObjectHandle::newDictionary();
996 }
997 dr.makeResourcesIndirect(this->qpdf);
998 if (! dr.isIndirect())
999 {
1000 dr = this->qpdf.makeIndirectObject(dr);
1001 acroform.replaceKey("/DR", dr);
1002 }
1003 // Merge the other document's /DR, creating a conflict
1004 // map. mergeResources checks to make sure both objects
1005 // are dictionaries. By this point, if this is foreign,
1006 // from_dr has been copied, so we use the target qpdf as
1007 // the owning qpdf.
1008 from_dr.makeResourcesIndirect(this->qpdf);
1009 dr.mergeResources(from_dr, &dr_map);
1010
1011 if (from_afdh->getNeedAppearances())
1012 {
1013 setNeedAppearances(true);
1014 }
1015 }
1016 };
1017
1018 // This helper prevents us from copying the same object
1019 // multiple times.
1020 std::map<QPDFObjGen, QPDFObjectHandle> orig_to_copy;
1021 auto maybe_copy_object = [&](QPDFObjectHandle& to_copy) {
1022 auto og = to_copy.getObjGen();
1023 if (orig_to_copy.count(og))
1024 {
1025 to_copy = orig_to_copy[og];
1026 return false;
1027 }
1028 else
1029 {
1030 to_copy = this->qpdf.makeIndirectObject(to_copy.shallowCopy());
1031 orig_to_copy[og] = to_copy;
1032 return true;
1033 }
1034 };
1035
1036 // Now do the actual copies.
1037
1038 std::set<QPDFObjGen> added_new_fields;
1039 for (auto annot: old_annots.aitems())
1040 {
1041 if (annot.isStream())
1042 {
1043 annot.warnIfPossible("ignoring annotation that's a stream");
1044 continue;
1045 }
1046
1047 // Make copies of annotations and fields down to the
1048 // appearance streams, preserving all internal referential
1049 // integrity. When the incoming annotations are from a
1050 // different file, we first copy them locally. Then, whether
1051 // local or foreign, we copy them again so that if we bring
1052 // the same annotation in multiple times (e.g. overlaying a
1053 // foreign page onto multiple local pages or a local page onto
1054 // multiple other local pages), we don't create annotations
1055 // that are referenced in more than one place. If we did that,
1056 // the effect of applying transformations would be cumulative,
1057 // which is definitely not what we want. Besides, annotations
1058 // and fields are not intended to be referenced in multiple
1059 // places.
1060
1061 // Determine if this annotation is attached to a form field.
1062 // If so, the annotation may be the same object as the form
1063 // field, or the form field may have the annotation as a kid.
1064 // In either case, we have to walk up the field structure to
1065 // find the top-level field. Within one iteration through a
1066 // set of annotations, we don't want to copy the same item
1067 // more than once. For example, suppose we have field A with
1068 // kids B, C, and D, each of which has annotations BA, CA, and
1069 // DA. When we get to BA, we will find that BA is a kid of B
1070 // which is under A. When we do a copyForeignObject of A, it
1071 // will also copy everything else because of the indirect
1072 // references. When we clone BA, we will want to clone A and
1073 // then update A's clone's kid to point B's clone and B's
1074 // clone's parent to point to A's clone. The same thing holds
1075 // for annotations. Next, when we get to CA, we will again
1076 // discover that A is the top, but we don't want to re-copy A.
1077 // We want CA's clone to be linked to the same clone as BA's.
1078 // Failure to do this will break up things like radio button
1079 // groups, which all have to kids of the same parent.
1080
1081 auto ffield = from_afdh->getFieldForAnnotation(annot);
1082 auto ffield_oh = ffield.getObjectHandle();
1083 QPDFObjectHandle top_field;
1084 bool have_field = false;
1085 bool have_parent = false;
1086 if (ffield_oh.isStream())
1087 {
1088 ffield_oh.warnIfPossible("ignoring form field that's a stream");
1089 }
1090 else if ((! ffield_oh.isNull()) && (! ffield_oh.isIndirect()))
1091 {
1092 ffield_oh.warnIfPossible("ignoring form field not indirect");
1093 }
1094 else if (! ffield_oh.isNull())
1095 {
1096 // A field and its associated annotation can be the same
1097 // object. This matters because we don't want to clone the
1098 // annotation and field separately in this case.
1099 have_field = true;
1100 // Find the top-level field. It may be the field itself.
1101 top_field = ffield.getTopLevelField(
1102 &have_parent).getObjectHandle();
1103 if (foreign)
1104 {
1105 // copyForeignObject returns the same value if called
1106 // multiple times with the same field. Create/retrieve
1107 // the local copy of the original field. This pulls
1108 // over everything the field references including
1109 // annotations and appearance streams, but it's
1110 // harmless to call copyForeignObject on them too.
1111 // They will already be copied, so we'll get the right
1112 // object back.
1113
1114 // top_field and ffield_oh are known to be indirect.
1115 top_field = this->qpdf.copyForeignObject(top_field);
1116 ffield_oh = this->qpdf.copyForeignObject(ffield_oh);
1117 }
1118 else
1119 {
1120 // We don't need to add top_field to old_fields if
1121 // it's foreign because the new copy of the foreign
1122 // field won't be referenced anywhere. It's just the
1123 // starting point for us to make an additional local
1124 // copy of.
1125 old_fields.insert(top_field.getObjGen());
1126 }
1127
1128 // Traverse the field, copying kids, and preserving
1129 // integrity.
1130 std::list<QPDFObjectHandle> queue;
1131 if (maybe_copy_object(top_field))
1132 {
1133 queue.push_back(top_field);
1134 }
1135 std::set<QPDFObjGen> seen;
1136 while (! queue.empty())
1137 {
1138 QPDFObjectHandle obj = queue.front();
1139 queue.pop_front();
1140 auto orig_og = obj.getObjGen();
1141 if (seen.count(orig_og))
1142 {
1143 // loop
1144 break;
1145 }
1146 seen.insert(orig_og);
1147 auto parent = obj.getKey("/Parent");
1148 if (parent.isIndirect())
1149 {
1150 auto parent_og = parent.getObjGen();
1151 if (orig_to_copy.count(parent_og))
1152 {
1153 obj.replaceKey("/Parent", orig_to_copy[parent_og]);
1154 }
1155 else
1156 {
1157 parent.warnIfPossible(
1158 "while traversing field " +
1159 obj.getObjGen().unparse() +
1160 ", found parent (" + parent_og.unparse() +
1161 ") that had not been seen, indicating likely"
1162 " invalid field structure");
1163 }
1164 }
1165 auto kids = obj.getKey("/Kids");
1166 if (kids.isArray())
1167 {
1168 for (int i = 0; i < kids.getArrayNItems(); ++i)
1169 {
1170 auto kid = kids.getArrayItem(i);
1171 if (maybe_copy_object(kid))
1172 {
1173 kids.setArrayItem(i, kid);
1174 queue.push_back(kid);
1175 }
1176 }
1177 }
1178
1179 if (override_da || override_q)
1180 {
1181 adjustInheritedFields(
1182 obj, override_da, from_default_da,
1183 override_q, from_default_q);
1184 }
1185 if (foreign)
1186 {
1187 // Lazily initialize our /DR and the conflict map.
1188 init_dr_map();
1189 // The spec doesn't say anything about /DR on the
1190 // field, but lots of writers put one there, and
1191 // it is frequently the same as the document-level
1192 // /DR. To avoid having the field's /DR point to
1193 // information that we are not maintaining, just
1194 // reset it to that if it exists. Empirical
1195 // evidence suggests that many readers, including
1196 // Acrobat, Adobe Acrobat Reader, chrome, firefox,
1197 // the mac Preview application, and several of the
1198 // free readers on Linux all ignore /DR at the
1199 // field level.
1200 if (obj.hasKey("/DR"))
1201 {
1202 obj.replaceKey("/DR", dr);
1203 }
1204 }
1205 if (foreign && obj.getKey("/DA").isString() &&
1206 (! dr_map.empty()))
1207 {
1208 adjustDefaultAppearances(obj, dr_map);
1209 }
1210 }
1211
1212 // Now switch to copies. We already switched for top_field
1213 maybe_copy_object(ffield_oh);
1214 ffield = QPDFFormFieldObjectHelper(ffield_oh);
1215 }
1216
1217 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper copy annotation",
1218 (have_field ? 1 : 0) | (foreign ? 2 : 0));
1219 if (have_field)
1220 {
1221 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field with parent",
1222 (have_parent ? 1 : 0) | (foreign ? 2 : 0));
1223 }
1224 if (foreign)
1225 {
1226 if (! annot.isIndirect())
1227 {
1228 annot = from_qpdf->makeIndirectObject(annot);
1229 }
1230 annot = this->qpdf.copyForeignObject(annot);
1231 }
1232 maybe_copy_object(annot);
1233
1234 // Now we have copies, so we can safely mutate.
1235 if (have_field && ! added_new_fields.count(top_field.getObjGen()))
1236 {
1237 new_fields.push_back(top_field);
1238 added_new_fields.insert(top_field.getObjGen());
1239 }
1240 new_annots.push_back(annot);
1241
1242 // Identify and copy any appearance streams
1243
1244 auto ah = QPDFAnnotationObjectHelper(annot);
1245 auto apdict = ah.getAppearanceDictionary();
1246 std::vector<QPDFObjectHandle> streams;
1247 auto replace_stream = [](auto& dict, auto& key, auto& old) {
1248 auto new_stream = old.copyStream();
1249 dict.replaceKey(key, new_stream);
1250 return new_stream;
1251 };
1252 if (apdict.isDictionary())
1253 {
1254 for (auto& ap: apdict.ditems())
1255 {
1256 if (ap.second.isStream())
1257 {
1258 streams.push_back(
1259 replace_stream(apdict, ap.first, ap.second));
1260 }
1261 else if (ap.second.isDictionary())
1262 {
1263 for (auto& ap2: ap.second.ditems())
1264 {
1265 if (ap2.second.isStream())
1266 {
1267 streams.push_back(
1268 replace_stream(
1269 ap.second, ap2.first, ap2.second));
1270 }
1271 }
1272 }
1273 }
1274 }
1275
1276 // Now we can safely mutate the annotation and its appearance
1277 // streams.
1278 for (auto& stream: streams)
1279 {
1280 auto dict = stream.getDict();
1281 auto omatrix = dict.getKey("/Matrix");
1282 QPDFMatrix apcm;
1283 if (omatrix.isArray())
1284 {
1285 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper modify ap matrix");
1286 auto m1 = omatrix.getArrayAsMatrix();
1287 apcm = QPDFMatrix(m1);
1288 }
1289 apcm.concat(cm);
1290 auto new_matrix = QPDFObjectHandle::newFromMatrix(apcm);
1291 if (omatrix.isArray() || (apcm != QPDFMatrix()))
1292 {
1293 dict.replaceKey("/Matrix", new_matrix);
1294 }
1295 auto resources = dict.getKey("/Resources");
1296 if ((! dr_map.empty()) && resources.isDictionary())
1297 {
1298 adjustAppearanceStream(stream, dr_map);
1299 }
1300 }
1301 auto rect = cm.transformRectangle(
1302 annot.getKey("/Rect").getArrayAsRectangle());
1303 annot.replaceKey(
1304 "/Rect", QPDFObjectHandle::newFromRectangle(rect));
1305 }
1306 }
1307
1308 void
copyFieldsFromForeignPage(QPDFPageObjectHelper foreign_page,QPDFAcroFormDocumentHelper & foreign_afdh,std::vector<QPDFObjectHandle> * copied_fields)1309 QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage(
1310 QPDFPageObjectHelper foreign_page,
1311 QPDFAcroFormDocumentHelper& foreign_afdh,
1312 std::vector<QPDFObjectHandle>* copied_fields)
1313 {
1314 this->qpdf.warn(
1315 QPDFExc(qpdf_e_unsupported, "", "", 0,
1316 "Non-working version of copyFieldsFromForeignPage"
1317 " from qpdf 10.2 called; application requires updating"));
1318 }
1319
1320 void
fixCopiedAnnotations(QPDFObjectHandle to_page,QPDFObjectHandle from_page,QPDFAcroFormDocumentHelper & from_afdh,std::set<QPDFObjGen> * added_fields)1321 QPDFAcroFormDocumentHelper::fixCopiedAnnotations(
1322 QPDFObjectHandle to_page,
1323 QPDFObjectHandle from_page,
1324 QPDFAcroFormDocumentHelper& from_afdh,
1325 std::set<QPDFObjGen>* added_fields)
1326 {
1327 auto old_annots = from_page.getKey("/Annots");
1328 if ((! old_annots.isArray()) || (old_annots.getArrayNItems() == 0))
1329 {
1330 return;
1331 }
1332
1333 std::vector<QPDFObjectHandle> new_annots;
1334 std::vector<QPDFObjectHandle> new_fields;
1335 std::set<QPDFObjGen> old_fields;
1336 transformAnnotations(old_annots, new_annots, new_fields, old_fields,
1337 QPDFMatrix(), &(from_afdh.getQPDF()),
1338 &from_afdh);
1339
1340 to_page.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots));
1341 addAndRenameFormFields(new_fields);
1342 if (added_fields)
1343 {
1344 for (auto f: new_fields)
1345 {
1346 added_fields->insert(f.getObjGen());
1347 }
1348 }
1349 }
1350