Commit 1f35ec9988eaf1ad3705655c434701d175c5b49f

Authored by Jay Berkenbilt
1 parent f02aa74b

Add methods for copying form fields

ChangeLog
1 2021-02-22 Jay Berkenbilt <ejb@ql.org> 1 2021-02-22 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Add QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage to
  4 + copy form fields from a foreign page into the current file.
  5 +
  6 + * Add QPDFFormFieldObjectHelper::getTopLevelField to get the
  7 + top-level field for a given form field.
  8 +
3 * Update pdf-overlay-page example to include copying of 9 * Update pdf-overlay-page example to include copying of
4 annotations. 10 annotations.
5 11
include/qpdf/QPDFAcroFormDocumentHelper.hh
@@ -140,6 +140,11 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper @@ -140,6 +140,11 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
140 std::vector<QPDFAnnotationObjectHelper> 140 std::vector<QPDFAnnotationObjectHelper>
141 getWidgetAnnotationsForPage(QPDFPageObjectHelper); 141 getWidgetAnnotationsForPage(QPDFPageObjectHelper);
142 142
  143 + // Return form fields for a page.
  144 + QPDF_DLL
  145 + std::vector<QPDFFormFieldObjectHelper>
  146 + getFormFieldsForPage(QPDFPageObjectHelper);
  147 +
143 // Return the terminal field that is associated with this 148 // Return the terminal field that is associated with this
144 // annotation. If the annotation dictionary is merged with the 149 // annotation. If the annotation dictionary is merged with the
145 // field dictionary, the underlying object will be the same, but 150 // field dictionary, the underlying object will be the same, but
@@ -204,6 +209,13 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper @@ -204,6 +209,13 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
204 QPDF* from_qpdf = nullptr, 209 QPDF* from_qpdf = nullptr,
205 QPDFAcroFormDocumentHelper* from_afdh = nullptr); 210 QPDFAcroFormDocumentHelper* from_afdh = nullptr);
206 211
  212 + // Copy form fields from a page in a different QPDF object to this
  213 + // QPDF.
  214 + QPDF_DLL
  215 + void copyFieldsFromForeignPage(
  216 + QPDFPageObjectHelper foreign_page,
  217 + QPDFAcroFormDocumentHelper& foreign_afdh);
  218 +
207 private: 219 private:
208 void analyze(); 220 void analyze();
209 void traverseField(QPDFObjectHandle field, 221 void traverseField(QPDFObjectHandle field,
include/qpdf/QPDFFormFieldObjectHelper.hh
@@ -54,6 +54,13 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper @@ -54,6 +54,13 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper
54 QPDF_DLL 54 QPDF_DLL
55 QPDFFormFieldObjectHelper getParent(); 55 QPDFFormFieldObjectHelper getParent();
56 56
  57 + // Return the top-level field for this field. Typically this will
  58 + // be the field itself or its parent. If is_different is provided,
  59 + // it is set to true if the top-level field is different from the
  60 + // field itself; otherwise it is set to false.
  61 + QPDF_DLL
  62 + QPDFFormFieldObjectHelper getTopLevelField(bool* is_different = nullptr);
  63 +
57 // Get a field value, possibly inheriting the value from an 64 // Get a field value, possibly inheriting the value from an
58 // ancestor node. 65 // ancestor node.
59 QPDF_DLL 66 QPDF_DLL
libqpdf/QPDFAcroFormDocumentHelper.cc
@@ -132,6 +132,23 @@ QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) @@ -132,6 +132,23 @@ QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h)
132 return h.getAnnotations("/Widget"); 132 return h.getAnnotations("/Widget");
133 } 133 }
134 134
  135 +std::vector<QPDFFormFieldObjectHelper>
  136 +QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph)
  137 +{
  138 + std::vector<QPDFFormFieldObjectHelper> result;
  139 + auto widget_annotations = getWidgetAnnotationsForPage(ph);
  140 + for (auto annot: widget_annotations)
  141 + {
  142 + auto field = getFieldForAnnotation(annot);
  143 + field = field.getTopLevelField();
  144 + if (field.getObjectHandle().isDictionary())
  145 + {
  146 + result.push_back(field);
  147 + }
  148 + }
  149 + return result;
  150 +}
  151 +
135 QPDFFormFieldObjectHelper 152 QPDFFormFieldObjectHelper
136 QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) 153 QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h)
137 { 154 {
@@ -501,19 +518,8 @@ QPDFAcroFormDocumentHelper::transformAnnotations( @@ -501,19 +518,8 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
501 // annotation and field separately in this case. 518 // annotation and field separately in this case.
502 have_field = true; 519 have_field = true;
503 // Find the top-level field. It may be the field itself. 520 // Find the top-level field. It may be the field itself.
504 - top_field = ffield_oh;  
505 - std::set<QPDFObjGen> seen;  
506 - while (! top_field.getKey("/Parent").isNull())  
507 - {  
508 - top_field = top_field.getKey("/Parent");  
509 - have_parent = true;  
510 - auto og = top_field.getObjGen();  
511 - if (seen.count(og))  
512 - {  
513 - break;  
514 - }  
515 - seen.insert(og);  
516 - } 521 + top_field = ffield.getTopLevelField(
  522 + &have_parent).getObjectHandle();
517 if (foreign) 523 if (foreign)
518 { 524 {
519 // copyForeignObject returns the same value if called 525 // copyForeignObject returns the same value if called
@@ -537,7 +543,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( @@ -537,7 +543,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
537 { 543 {
538 queue.push_back(top_field); 544 queue.push_back(top_field);
539 } 545 }
540 - seen.clear(); 546 + std::set<QPDFObjGen> seen;
541 while (! queue.empty()) 547 while (! queue.empty())
542 { 548 {
543 QPDFObjectHandle obj = queue.front(); 549 QPDFObjectHandle obj = queue.front();
@@ -664,3 +670,16 @@ QPDFAcroFormDocumentHelper::transformAnnotations( @@ -664,3 +670,16 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
664 "/Rect", QPDFObjectHandle::newFromRectangle(rect)); 670 "/Rect", QPDFObjectHandle::newFromRectangle(rect));
665 } 671 }
666 } 672 }
  673 +
  674 +void
  675 +QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage(
  676 + QPDFPageObjectHelper foreign_page,
  677 + QPDFAcroFormDocumentHelper& foreign_afdh)
  678 +{
  679 + for (auto field: foreign_afdh.getFormFieldsForPage(foreign_page))
  680 + {
  681 + auto new_field = this->qpdf.copyForeignObject(
  682 + field.getObjectHandle());
  683 + addFormField(new_field);
  684 + }
  685 +}
libqpdf/QPDFFormFieldObjectHelper.cc
@@ -39,6 +39,29 @@ QPDFFormFieldObjectHelper::getParent() @@ -39,6 +39,29 @@ QPDFFormFieldObjectHelper::getParent()
39 return this->oh.getKey("/Parent"); // may be null 39 return this->oh.getKey("/Parent"); // may be null
40 } 40 }
41 41
  42 +QPDFFormFieldObjectHelper
  43 +QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different)
  44 +{
  45 + auto top_field = this->oh;
  46 + std::set<QPDFObjGen> seen;
  47 + while (top_field.isDictionary() &&
  48 + (! top_field.getKey("/Parent").isNull()))
  49 + {
  50 + top_field = top_field.getKey("/Parent");
  51 + if (is_different)
  52 + {
  53 + *is_different = true;
  54 + }
  55 + auto og = top_field.getObjGen();
  56 + if (seen.count(og))
  57 + {
  58 + break;
  59 + }
  60 + seen.insert(og);
  61 + }
  62 + return QPDFFormFieldObjectHelper(top_field);
  63 +}
  64 +
42 QPDFObjectHandle 65 QPDFObjectHandle
43 QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) 66 QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name)
44 { 67 {