Commit 5e7b37e15cb366f75dea16a2fc8c1db4d71da0a3

Authored by m-holger
Committed by GitHub
2 parents ef14676b e6200bbc

Merge pull request #1530 from m-holger/afdh

Cache QPDFAcroFormDocumentHelpers
include/qpdf/QPDF.hh
... ... @@ -57,6 +57,7 @@ class BitWriter;
57 57 class BufferInputSource;
58 58 class QPDFLogger;
59 59 class QPDFParser;
  60 +class QPDFAcroFormDocumentHelper;
60 61  
61 62 class QPDF
62 63 {
... ... @@ -792,6 +793,7 @@ class QPDF
792 793 class JobSetter;
793 794  
794 795 inline bool reconstructed_xref() const;
  796 + inline QPDFAcroFormDocumentHelper& acroform();
795 797  
796 798 // For testing only -- do not add to DLL
797 799 static bool test_json_validators();
... ...
include/qpdf/QPDFAcroFormDocumentHelper.hh
... ... @@ -68,6 +68,21 @@
68 68 class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
69 69 {
70 70 public:
  71 + // Get a shared document helper for a given QPDF object.
  72 + //
  73 + // Retrieving a document helper for a QPDF object rather than creating a new one avoids repeated
  74 + // validation of the Acroform structure, which can be expensive.
  75 + QPDF_DLL
  76 + static QPDFAcroFormDocumentHelper& get(QPDF& qpdf);
  77 +
  78 + // Re-validate the AcroForm structure. This is useful if you have modified the structure of the
  79 + // AcroForm dictionary in a way that would invalidate the cache.
  80 + //
  81 + // If repair is true, the document will be repaired if possible if the validation encounters
  82 + // errors.
  83 + QPDF_DLL
  84 + void validate(bool repair = true);
  85 +
71 86 QPDF_DLL
72 87 QPDFAcroFormDocumentHelper(QPDF&);
73 88  
... ... @@ -226,23 +241,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
226 241 void adjustAppearanceStream(
227 242 QPDFObjectHandle stream, std::map<std::string, std::map<std::string, std::string>> dr_map);
228 243  
229   - class Members
230   - {
231   - friend class QPDFAcroFormDocumentHelper;
232   -
233   - public:
234   - ~Members() = default;
235   -
236   - private:
237   - Members() = default;
238   - Members(Members const&) = delete;
239   -
240   - bool cache_valid{false};
241   - std::map<QPDFObjGen, std::vector<QPDFAnnotationObjectHelper>> field_to_annotations;
242   - std::map<QPDFObjGen, QPDFFormFieldObjectHelper> annotation_to_field;
243   - std::map<QPDFObjGen, std::string> field_to_name;
244   - std::map<std::string, std::set<QPDFObjGen>> name_to_fields;
245   - };
  244 + class Members;
246 245  
247 246 std::shared_ptr<Members> m;
248 247 };
... ...
libqpdf/QPDFAcroFormDocumentHelper.cc
... ... @@ -3,6 +3,7 @@
3 3 #include <qpdf/Pl_Buffer.hh>
4 4 #include <qpdf/QPDFObjectHandle_private.hh>
5 5 #include <qpdf/QPDFPageDocumentHelper.hh>
  6 +#include <qpdf/QPDF_private.hh>
6 7 #include <qpdf/QTC.hh>
7 8 #include <qpdf/QUtil.hh>
8 9 #include <qpdf/ResourceFinder.hh>
... ... @@ -12,15 +13,42 @@
12 13 using namespace qpdf;
13 14 using namespace std::literals;
14 15  
  16 +class QPDFAcroFormDocumentHelper::Members
  17 +{
  18 + public:
  19 + Members() = default;
  20 + Members(Members const&) = delete;
  21 + ~Members() = default;
  22 +
  23 + bool cache_valid{false};
  24 + std::map<QPDFObjGen, std::vector<QPDFAnnotationObjectHelper>> field_to_annotations;
  25 + std::map<QPDFObjGen, QPDFFormFieldObjectHelper> annotation_to_field;
  26 + std::map<QPDFObjGen, std::string> field_to_name;
  27 + std::map<std::string, std::set<QPDFObjGen>> name_to_fields;
  28 +};
  29 +
15 30 QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) :
16 31 QPDFDocumentHelper(qpdf),
17   - m(new Members())
  32 + m(std::make_shared<Members>())
18 33 {
19 34 // We have to analyze up front. Otherwise, when we are adding annotations and fields, we are in
20 35 // a temporarily unstable configuration where some widget annotations are not reachable.
21 36 analyze();
22 37 }
23 38  
  39 +QPDFAcroFormDocumentHelper&
  40 +QPDFAcroFormDocumentHelper::get(QPDF& qpdf)
  41 +{
  42 + return qpdf.acroform();
  43 +}
  44 +
  45 +void
  46 +QPDFAcroFormDocumentHelper::validate(bool repair)
  47 +{
  48 + invalidateCache();
  49 + analyze();
  50 +}
  51 +
24 52 void
25 53 QPDFAcroFormDocumentHelper::invalidateCache()
26 54 {
... ...
libqpdf/QPDFFormFieldObjectHelper.cc
... ... @@ -5,6 +5,7 @@
5 5 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
6 6 #include <qpdf/QPDFAnnotationObjectHelper.hh>
7 7 #include <qpdf/QPDFObjectHandle_private.hh>
  8 +#include <qpdf/QPDF_private.hh>
8 9 #include <qpdf/QTC.hh>
9 10 #include <qpdf/QUtil.hh>
10 11 #include <cstdlib>
... ... @@ -88,23 +89,21 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const&amp; name)
88 89 std::string
89 90 QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& name)
90 91 {
91   - QPDFObjectHandle fv = getInheritableFieldValue(name);
92   - std::string result;
  92 + auto fv = getInheritableFieldValue(name);
93 93 if (fv.isString()) {
94   - result = fv.getUTF8Value();
  94 + return fv.getUTF8Value();
95 95 }
96   - return result;
  96 + return {};
97 97 }
98 98  
99 99 std::string
100 100 QPDFFormFieldObjectHelper::getInheritableFieldValueAsName(std::string const& name)
101 101 {
102   - QPDFObjectHandle fv = getInheritableFieldValue(name);
103   - std::string result;
  102 + auto fv = getInheritableFieldValue(name);
104 103 if (fv.isName()) {
105   - result = fv.getName();
  104 + return fv.getName();
106 105 }
107   - return result;
  106 + return {};
108 107 }
109 108  
110 109 std::string
... ... @@ -203,12 +202,11 @@ QPDFFormFieldObjectHelper::getDefaultAppearance()
203 202 value = getFieldFromAcroForm("/DA");
204 203 looked_in_acroform = true;
205 204 }
206   - std::string result;
207 205 if (value.isString()) {
208 206 QTC::TC("qpdf", "QPDFFormFieldObjectHelper DA present", looked_in_acroform ? 0 : 1);
209   - result = value.getUTF8Value();
  207 + return value.getUTF8Value();
210 208 }
211   - return result;
  209 + return {};
212 210 }
213 211  
214 212 int
... ... @@ -220,12 +218,11 @@ QPDFFormFieldObjectHelper::getQuadding()
220 218 fv = getFieldFromAcroForm("/Q");
221 219 looked_in_acroform = true;
222 220 }
223   - int result = 0;
224 221 if (fv.isInteger()) {
225 222 QTC::TC("qpdf", "QPDFFormFieldObjectHelper Q present", looked_in_acroform ? 0 : 1);
226   - result = QIntC::to_int(fv.getIntValue());
  223 + return QIntC::to_int(fv.getIntValue());
227 224 }
228   - return result;
  225 + return 0;
229 226 }
230 227  
231 228 int
... ... @@ -238,46 +235,46 @@ QPDFFormFieldObjectHelper::getFlags()
238 235 bool
239 236 QPDFFormFieldObjectHelper::isText()
240 237 {
241   - return (getFieldType() == "/Tx");
  238 + return getFieldType() == "/Tx";
242 239 }
243 240  
244 241 bool
245 242 QPDFFormFieldObjectHelper::isCheckbox()
246 243 {
247   - return ((getFieldType() == "/Btn") && ((getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0));
  244 + return getFieldType() == "/Btn" && (getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0;
248 245 }
249 246  
250 247 bool
251 248 QPDFFormFieldObjectHelper::isChecked()
252 249 {
253   - return isCheckbox() && getValue().isName() && (getValue().getName() != "/Off");
  250 + return isCheckbox() && getValue().isName() && getValue().getName() != "/Off";
254 251 }
255 252  
256 253 bool
257 254 QPDFFormFieldObjectHelper::isRadioButton()
258 255 {
259   - return ((getFieldType() == "/Btn") && ((getFlags() & ff_btn_radio) == ff_btn_radio));
  256 + return getFieldType() == "/Btn" && (getFlags() & ff_btn_radio) == ff_btn_radio;
260 257 }
261 258  
262 259 bool
263 260 QPDFFormFieldObjectHelper::isPushbutton()
264 261 {
265   - return ((getFieldType() == "/Btn") && ((getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton));
  262 + return getFieldType() == "/Btn" && (getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton;
266 263 }
267 264  
268 265 bool
269 266 QPDFFormFieldObjectHelper::isChoice()
270 267 {
271   - return (getFieldType() == "/Ch");
  268 + return getFieldType() == "/Ch";
272 269 }
273 270  
274 271 std::vector<std::string>
275 272 QPDFFormFieldObjectHelper::getChoices()
276 273 {
277   - std::vector<std::string> result;
278 274 if (!isChoice()) {
279   - return result;
  275 + return {};
280 276 }
  277 + std::vector<std::string> result;
281 278 for (auto const& item: getInheritableFieldValue("/Opt").as_array()) {
282 279 if (item.isString()) {
283 280 result.emplace_back(item.getUTF8Value());
... ... @@ -308,25 +305,26 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances)
308 305 {
309 306 if (getFieldType() == "/Btn") {
310 307 if (isCheckbox()) {
311   - bool okay = false;
312   - if (value.isName()) {
313   - std::string name = value.getName();
314   - okay = true;
315   - // Accept any value other than /Off to mean checked. Files have been seen that use
316   - // /1 or other values.
317   - setCheckBoxValue((name != "/Off"));
318   - }
319   - if (!okay) {
  308 + if (!value.isName()) {
320 309 warn("ignoring attempt to set a checkbox field to a value whose type is not name");
  310 + return;
321 311 }
322   - } else if (isRadioButton()) {
323   - if (value.isName()) {
324   - setRadioButtonValue(value);
325   - } else {
  312 + std::string name = value.getName();
  313 + // Accept any value other than /Off to mean checked. Files have been seen that use
  314 + // /1 or other values.
  315 + setCheckBoxValue(name != "/Off");
  316 + return;
  317 + }
  318 + if (isRadioButton()) {
  319 + if (!value.isName()) {
326 320 warn(
327 321 "ignoring attempt to set a radio button field to an object that is not a name");
  322 + return;
328 323 }
329   - } else if (isPushbutton()) {
  324 + setRadioButtonValue(value);
  325 + return;
  326 + }
  327 + if (isPushbutton()) {
330 328 warn("ignoring attempt set the value of a pushbutton field");
331 329 }
332 330 return;
... ... @@ -340,7 +338,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances)
340 338 QPDF& qpdf = oh().getQPDF(
341 339 "QPDFFormFieldObjectHelper::setV called with need_appearances = "
342 340 "true on an object that is not associated with an owning QPDF");
343   - QPDFAcroFormDocumentHelper(qpdf).setNeedAppearances(true);
  341 + qpdf.acroform().setNeedAppearances(true);
344 342 }
345 343 }
346 344  
... ...
libqpdf/QPDFJob.cc
... ... @@ -1180,7 +1180,7 @@ void
1180 1180 QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf)
1181 1181 {
1182 1182 JSON j_acroform = JSON::makeDictionary();
1183   - QPDFAcroFormDocumentHelper afdh(pdf);
  1183 + auto& afdh = pdf.acroform();
1184 1184 j_acroform.addDictionaryMember("hasacroform", JSON::makeBool(afdh.hasAcroForm()));
1185 1185 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances()));
1186 1186 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray());
... ... @@ -1888,17 +1888,6 @@ QPDFJob::validateUnderOverlay(QPDF&amp; pdf, UnderOverlay* uo)
1888 1888 }
1889 1889 }
1890 1890  
1891   -static QPDFAcroFormDocumentHelper*
1892   -get_afdh_for_qpdf(
1893   - std::map<unsigned long long, std::shared_ptr<QPDFAcroFormDocumentHelper>>& afdh_map, QPDF* q)
1894   -{
1895   - auto uid = q->getUniqueId();
1896   - if (!afdh_map.contains(uid)) {
1897   - afdh_map[uid] = std::make_shared<QPDFAcroFormDocumentHelper>(*q);
1898   - }
1899   - return afdh_map[uid].get();
1900   -}
1901   -
1902 1891 std::string
1903 1892 QPDFJob::doUnderOverlayForPage(
1904 1893 QPDF& pdf,
... ... @@ -1914,13 +1903,7 @@ QPDFJob::doUnderOverlayForPage(
1914 1903 if (!(pagenos.contains(pageno) && pagenos[pageno].contains(uo_idx))) {
1915 1904 return "";
1916 1905 }
1917   -
1918   - std::map<unsigned long long, std::shared_ptr<QPDFAcroFormDocumentHelper>> afdh;
1919   - auto make_afdh = [&](QPDFPageObjectHelper& ph) {
1920   - QPDF& q = ph.getObjectHandle().getQPDF();
1921   - return get_afdh_for_qpdf(afdh, &q);
1922   - };
1923   - auto dest_afdh = make_afdh(dest_page);
  1906 + auto& dest_afdh = dest_page.qpdf()->acroform();
1924 1907  
1925 1908 std::string content;
1926 1909 int min_suffix = 1;
... ... @@ -1940,7 +1923,7 @@ QPDFJob::doUnderOverlayForPage(
1940 1923 QPDFMatrix cm;
1941 1924 std::string new_content = dest_page.placeFormXObject(
1942 1925 fo[from_pageno][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm);
1943   - dest_page.copyAnnotations(from_page, cm, dest_afdh, make_afdh(from_page));
  1926 + dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->acroform());
1944 1927 if (!new_content.empty()) {
1945 1928 resources.mergeResources("<< /XObject << >> >>"_qpdf);
1946 1929 auto xobject = resources.getKey("/XObject");
... ... @@ -2182,15 +2165,15 @@ void
2182 2165 QPDFJob::handleTransformations(QPDF& pdf)
2183 2166 {
2184 2167 QPDFPageDocumentHelper dh(pdf);
2185   - std::shared_ptr<QPDFAcroFormDocumentHelper> afdh;
2186   - auto make_afdh = [&]() {
2187   - if (!afdh.get()) {
2188   - afdh = std::make_shared<QPDFAcroFormDocumentHelper>(pdf);
  2168 + QPDFAcroFormDocumentHelper* afdh_ptr = nullptr;
  2169 + auto afdh = [&]() -> QPDFAcroFormDocumentHelper& {
  2170 + if (!afdh_ptr) {
  2171 + afdh_ptr = &pdf.acroform();
2189 2172 }
  2173 + return *afdh_ptr;
2190 2174 };
2191 2175 if (m->remove_restrictions) {
2192   - make_afdh();
2193   - afdh->disableDigitalSignatures();
  2176 + afdh().disableDigitalSignatures();
2194 2177 }
2195 2178 if (m->externalize_inline_images || (m->optimize_images && (!m->keep_inline_images))) {
2196 2179 for (auto& ph: dh.getAllPages()) {
... ... @@ -2225,8 +2208,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf)
2225 2208 }
2226 2209 }
2227 2210 if (m->generate_appearances) {
2228   - make_afdh();
2229   - afdh->generateAppearancesIfNeeded();
  2211 + afdh().generateAppearancesIfNeeded();
2230 2212 }
2231 2213 if (m->flatten_annotations) {
2232 2214 dh.flattenAnnotations(m->flatten_annotations_required, m->flatten_annotations_forbidden);
... ... @@ -2237,9 +2219,8 @@ QPDFJob::handleTransformations(QPDF&amp; pdf)
2237 2219 }
2238 2220 }
2239 2221 if (m->flatten_rotation) {
2240   - make_afdh();
2241 2222 for (auto& page: dh.getAllPages()) {
2242   - page.flattenRotation(afdh.get());
  2223 + page.flattenRotation(&afdh());
2243 2224 }
2244 2225 }
2245 2226 if (m->remove_page_labels) {
... ... @@ -2559,8 +2540,7 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2559 2540 std::vector<QPDFObjectHandle> new_labels;
2560 2541 bool any_page_labels = false;
2561 2542 int out_pageno = 0;
2562   - std::map<unsigned long long, std::shared_ptr<QPDFAcroFormDocumentHelper>> afdh_map;
2563   - auto this_afdh = get_afdh_for_qpdf(afdh_map, &pdf);
  2543 + auto& this_afdh = pdf.acroform();
2564 2544 std::set<QPDFObjGen> referenced_fields;
2565 2545 for (auto& page_data: parsed_specs) {
2566 2546 ClosedFileInputSource* cis = nullptr;
... ... @@ -2569,7 +2549,7 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2569 2549 cis->stayOpen(true);
2570 2550 }
2571 2551 QPDFPageLabelDocumentHelper pldh(*page_data.qpdf);
2572   - auto other_afdh = get_afdh_for_qpdf(afdh_map, page_data.qpdf);
  2552 + auto& other_afdh = page_data.qpdf->acroform();
2573 2553 if (pldh.hasPageLabels()) {
2574 2554 any_page_labels = true;
2575 2555 }
... ... @@ -2611,15 +2591,15 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2611 2591 // the original file until all copy operations are completed, any foreign pages that
2612 2592 // conflict with original pages will be adjusted. If we copy any page from the original
2613 2593 // file more than once, that page would be in conflict with the previous copy of itself.
2614   - if ((!this_file && other_afdh->hasAcroForm()) || !first_copy_from_orig) {
  2594 + if ((!this_file && other_afdh.hasAcroForm()) || !first_copy_from_orig) {
2615 2595 if (!this_file) {
2616 2596 QTC::TC("qpdf", "QPDFJob copy fields not this file");
2617 2597 } else if (!first_copy_from_orig) {
2618 2598 QTC::TC("qpdf", "QPDFJob copy fields non-first from orig");
2619 2599 }
2620 2600 try {
2621   - this_afdh->fixCopiedAnnotations(
2622   - new_page, to_copy.getObjectHandle(), *other_afdh, &referenced_fields);
  2601 + this_afdh.fixCopiedAnnotations(
  2602 + new_page, to_copy.getObjectHandle(), other_afdh, &referenced_fields);
2623 2603 } catch (std::exception& e) {
2624 2604 pdf.warn(
2625 2605 qpdf_e_damaged_pdf,
... ... @@ -2647,7 +2627,7 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2647 2627 for (size_t pageno = 0; pageno < orig_pages.size(); ++pageno) {
2648 2628 auto page = orig_pages.at(pageno);
2649 2629 if (selected_from_orig.contains(QIntC::to_int(pageno))) {
2650   - for (auto field: this_afdh->getFormFieldsForPage(page)) {
  2630 + for (auto field: this_afdh.getFormFieldsForPage(page)) {
2651 2631 QTC::TC("qpdf", "QPDFJob pages keeping field from original");
2652 2632 referenced_fields.insert(field.getObjectHandle().getObjGen());
2653 2633 }
... ... @@ -2656,7 +2636,7 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2656 2636 }
2657 2637 }
2658 2638 // Remove unreferenced form fields
2659   - if (this_afdh->hasAcroForm()) {
  2639 + if (this_afdh.hasAcroForm()) {
2660 2640 auto acroform = pdf.getRoot().getKey("/AcroForm");
2661 2641 auto fields = acroform.getKey("/Fields");
2662 2642 if (fields.isArray()) {
... ... @@ -3013,7 +2993,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3013 2993 dh.removeUnreferencedResources();
3014 2994 }
3015 2995 QPDFPageLabelDocumentHelper pldh(pdf);
3016   - QPDFAcroFormDocumentHelper afdh(pdf);
  2996 + auto& afdh = pdf.acroform();
3017 2997 std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
3018 2998 size_t pageno_len = std::to_string(pages.size()).length();
3019 2999 size_t num_pages = pages.size();
... ... @@ -3025,10 +3005,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3025 3005 }
3026 3006 QPDF outpdf;
3027 3007 outpdf.emptyPDF();
3028   - std::shared_ptr<QPDFAcroFormDocumentHelper> out_afdh;
3029   - if (afdh.hasAcroForm()) {
3030   - out_afdh = std::make_shared<QPDFAcroFormDocumentHelper>(outpdf);
3031   - }
  3008 + QPDFAcroFormDocumentHelper* out_afdh = afdh.hasAcroForm() ? &outpdf.acroform() : nullptr;
3032 3009 if (m->suppress_warnings) {
3033 3010 outpdf.setSuppressWarnings(true);
3034 3011 }
... ... @@ -3036,8 +3013,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3036 3013 QPDFObjectHandle page = pages.at(pageno - 1);
3037 3014 outpdf.addPage(page, false);
3038 3015 auto new_page = added_page(outpdf, page);
3039   - if (out_afdh.get()) {
3040   - QTC::TC("qpdf", "QPDFJob copy form fields in split_pages");
  3016 + if (out_afdh) {
3041 3017 try {
3042 3018 out_afdh->fixCopiedAnnotations(new_page, page, afdh);
3043 3019 } catch (std::exception& e) {
... ...
libqpdf/QPDFPageDocumentHelper.cc
1 1 #include <qpdf/QPDFPageDocumentHelper.hh>
2 2  
3 3 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
  4 +#include <qpdf/QPDF_private.hh>
4 5 #include <qpdf/QTC.hh>
5 6 #include <qpdf/QUtil.hh>
6 7  
... ... @@ -55,7 +56,7 @@ QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page)
55 56 void
56 57 QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags)
57 58 {
58   - QPDFAcroFormDocumentHelper afdh(qpdf);
  59 + auto& afdh = qpdf.acroform();
59 60 if (afdh.getNeedAppearances()) {
60 61 qpdf.getRoot()
61 62 .getKey("/AcroForm")
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -3,6 +3,7 @@
3 3  
4 4 #include <qpdf/QPDF.hh>
5 5  
  6 +#include <qpdf/QPDFAcroFormDocumentHelper.hh>
6 7 #include <qpdf/QPDFObject_private.hh>
7 8 #include <qpdf/QPDFTokenizer_private.hh>
8 9  
... ... @@ -547,6 +548,9 @@ class QPDF::Members
547 548 // Optimization data
548 549 std::map<ObjUser, std::set<QPDFObjGen>> obj_user_to_objects;
549 550 std::map<QPDFObjGen, std::set<ObjUser>> object_to_obj_users;
  551 +
  552 + // Document Helpers;
  553 + std::unique_ptr<QPDFAcroFormDocumentHelper> acroform;
550 554 };
551 555  
552 556 // JobSetter class is restricted to QPDFJob.
... ... @@ -569,4 +573,13 @@ QPDF::reconstructed_xref() const
569 573 return m->reconstructed_xref;
570 574 }
571 575  
  576 +inline QPDFAcroFormDocumentHelper&
  577 +QPDF::acroform()
  578 +{
  579 + if (!m->acroform) {
  580 + m->acroform = std::make_unique<QPDFAcroFormDocumentHelper>(*this);
  581 + }
  582 + return *m->acroform;
  583 +}
  584 +
572 585 #endif // QPDF_PRIVATE_HH
... ...
qpdf/qpdf.testcov
... ... @@ -519,7 +519,6 @@ QPDFPageObjectHelper flatten inherit rotate 0
519 519 QPDFAcroFormDocumentHelper copy annotation 3
520 520 QPDFAcroFormDocumentHelper field with parent 3
521 521 QPDFAcroFormDocumentHelper modify ap matrix 0
522   -QPDFJob copy form fields in split_pages 0
523 522 QPDFJob pages keeping field from original 0
524 523 QPDFObjectHandle merge reuse 0
525 524 QPDFObjectHandle merge generate 0
... ...