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