diff --git a/ChangeLog b/ChangeLog index 43c716d6..8693e5d4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2021-02-22 Jay Berkenbilt + * Add QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage to + copy form fields from a foreign page into the current file. + + * Add QPDFFormFieldObjectHelper::getTopLevelField to get the + top-level field for a given form field. + * Update pdf-overlay-page example to include copying of annotations. diff --git a/include/qpdf/QPDFAcroFormDocumentHelper.hh b/include/qpdf/QPDFAcroFormDocumentHelper.hh index eb9da5ad..fd28a579 100644 --- a/include/qpdf/QPDFAcroFormDocumentHelper.hh +++ b/include/qpdf/QPDFAcroFormDocumentHelper.hh @@ -140,6 +140,11 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper std::vector getWidgetAnnotationsForPage(QPDFPageObjectHelper); + // Return form fields for a page. + QPDF_DLL + std::vector + getFormFieldsForPage(QPDFPageObjectHelper); + // Return the terminal field that is associated with this // annotation. If the annotation dictionary is merged with the // field dictionary, the underlying object will be the same, but @@ -204,6 +209,13 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper QPDF* from_qpdf = nullptr, QPDFAcroFormDocumentHelper* from_afdh = nullptr); + // Copy form fields from a page in a different QPDF object to this + // QPDF. + QPDF_DLL + void copyFieldsFromForeignPage( + QPDFPageObjectHelper foreign_page, + QPDFAcroFormDocumentHelper& foreign_afdh); + private: void analyze(); void traverseField(QPDFObjectHandle field, diff --git a/include/qpdf/QPDFFormFieldObjectHelper.hh b/include/qpdf/QPDFFormFieldObjectHelper.hh index b9168d22..edb93df8 100644 --- a/include/qpdf/QPDFFormFieldObjectHelper.hh +++ b/include/qpdf/QPDFFormFieldObjectHelper.hh @@ -54,6 +54,13 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper QPDF_DLL QPDFFormFieldObjectHelper getParent(); + // Return the top-level field for this field. Typically this will + // be the field itself or its parent. If is_different is provided, + // it is set to true if the top-level field is different from the + // field itself; otherwise it is set to false. + QPDF_DLL + QPDFFormFieldObjectHelper getTopLevelField(bool* is_different = nullptr); + // Get a field value, possibly inheriting the value from an // ancestor node. QPDF_DLL diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index 84ddd432..dce413bd 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -132,6 +132,23 @@ QPDFAcroFormDocumentHelper::getWidgetAnnotationsForPage(QPDFPageObjectHelper h) return h.getAnnotations("/Widget"); } +std::vector +QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) +{ + std::vector result; + auto widget_annotations = getWidgetAnnotationsForPage(ph); + for (auto annot: widget_annotations) + { + auto field = getFieldForAnnotation(annot); + field = field.getTopLevelField(); + if (field.getObjectHandle().isDictionary()) + { + result.push_back(field); + } + } + return result; +} + QPDFFormFieldObjectHelper QPDFAcroFormDocumentHelper::getFieldForAnnotation(QPDFAnnotationObjectHelper h) { @@ -501,19 +518,8 @@ QPDFAcroFormDocumentHelper::transformAnnotations( // annotation and field separately in this case. have_field = true; // Find the top-level field. It may be the field itself. - top_field = ffield_oh; - std::set seen; - while (! top_field.getKey("/Parent").isNull()) - { - top_field = top_field.getKey("/Parent"); - have_parent = true; - auto og = top_field.getObjGen(); - if (seen.count(og)) - { - break; - } - seen.insert(og); - } + top_field = ffield.getTopLevelField( + &have_parent).getObjectHandle(); if (foreign) { // copyForeignObject returns the same value if called @@ -537,7 +543,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( { queue.push_back(top_field); } - seen.clear(); + std::set seen; while (! queue.empty()) { QPDFObjectHandle obj = queue.front(); @@ -664,3 +670,16 @@ QPDFAcroFormDocumentHelper::transformAnnotations( "/Rect", QPDFObjectHandle::newFromRectangle(rect)); } } + +void +QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage( + QPDFPageObjectHelper foreign_page, + QPDFAcroFormDocumentHelper& foreign_afdh) +{ + for (auto field: foreign_afdh.getFormFieldsForPage(foreign_page)) + { + auto new_field = this->qpdf.copyForeignObject( + field.getObjectHandle()); + addFormField(new_field); + } +} diff --git a/libqpdf/QPDFFormFieldObjectHelper.cc b/libqpdf/QPDFFormFieldObjectHelper.cc index 97257c85..6933cb54 100644 --- a/libqpdf/QPDFFormFieldObjectHelper.cc +++ b/libqpdf/QPDFFormFieldObjectHelper.cc @@ -39,6 +39,29 @@ QPDFFormFieldObjectHelper::getParent() return this->oh.getKey("/Parent"); // may be null } +QPDFFormFieldObjectHelper +QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) +{ + auto top_field = this->oh; + std::set seen; + while (top_field.isDictionary() && + (! top_field.getKey("/Parent").isNull())) + { + top_field = top_field.getKey("/Parent"); + if (is_different) + { + *is_different = true; + } + auto og = top_field.getObjGen(); + if (seen.count(og)) + { + break; + } + seen.insert(og); + } + return QPDFFormFieldObjectHelper(top_field); +} + QPDFObjectHandle QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) {