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,6 +57,7 @@ class BitWriter;
57 class BufferInputSource; 57 class BufferInputSource;
58 class QPDFLogger; 58 class QPDFLogger;
59 class QPDFParser; 59 class QPDFParser;
  60 +class QPDFAcroFormDocumentHelper;
60 61
61 class QPDF 62 class QPDF
62 { 63 {
@@ -792,6 +793,7 @@ class QPDF @@ -792,6 +793,7 @@ class QPDF
792 class JobSetter; 793 class JobSetter;
793 794
794 inline bool reconstructed_xref() const; 795 inline bool reconstructed_xref() const;
  796 + inline QPDFAcroFormDocumentHelper& acroform();
795 797
796 // For testing only -- do not add to DLL 798 // For testing only -- do not add to DLL
797 static bool test_json_validators(); 799 static bool test_json_validators();
include/qpdf/QPDFAcroFormDocumentHelper.hh
@@ -68,6 +68,21 @@ @@ -68,6 +68,21 @@
68 class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper 68 class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
69 { 69 {
70 public: 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 QPDF_DLL 86 QPDF_DLL
72 QPDFAcroFormDocumentHelper(QPDF&); 87 QPDFAcroFormDocumentHelper(QPDF&);
73 88
@@ -226,23 +241,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper @@ -226,23 +241,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
226 void adjustAppearanceStream( 241 void adjustAppearanceStream(
227 QPDFObjectHandle stream, std::map<std::string, std::map<std::string, std::string>> dr_map); 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 std::shared_ptr<Members> m; 246 std::shared_ptr<Members> m;
248 }; 247 };
libqpdf/QPDFAcroFormDocumentHelper.cc
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 #include <qpdf/Pl_Buffer.hh> 3 #include <qpdf/Pl_Buffer.hh>
4 #include <qpdf/QPDFObjectHandle_private.hh> 4 #include <qpdf/QPDFObjectHandle_private.hh>
5 #include <qpdf/QPDFPageDocumentHelper.hh> 5 #include <qpdf/QPDFPageDocumentHelper.hh>
  6 +#include <qpdf/QPDF_private.hh>
6 #include <qpdf/QTC.hh> 7 #include <qpdf/QTC.hh>
7 #include <qpdf/QUtil.hh> 8 #include <qpdf/QUtil.hh>
8 #include <qpdf/ResourceFinder.hh> 9 #include <qpdf/ResourceFinder.hh>
@@ -12,15 +13,42 @@ @@ -12,15 +13,42 @@
12 using namespace qpdf; 13 using namespace qpdf;
13 using namespace std::literals; 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 QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) : 30 QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) :
16 QPDFDocumentHelper(qpdf), 31 QPDFDocumentHelper(qpdf),
17 - m(new Members()) 32 + m(std::make_shared<Members>())
18 { 33 {
19 // We have to analyze up front. Otherwise, when we are adding annotations and fields, we are in 34 // We have to analyze up front. Otherwise, when we are adding annotations and fields, we are in
20 // a temporarily unstable configuration where some widget annotations are not reachable. 35 // a temporarily unstable configuration where some widget annotations are not reachable.
21 analyze(); 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 void 52 void
25 QPDFAcroFormDocumentHelper::invalidateCache() 53 QPDFAcroFormDocumentHelper::invalidateCache()
26 { 54 {
libqpdf/QPDFFormFieldObjectHelper.cc
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 #include <qpdf/QPDFAcroFormDocumentHelper.hh> 5 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
6 #include <qpdf/QPDFAnnotationObjectHelper.hh> 6 #include <qpdf/QPDFAnnotationObjectHelper.hh>
7 #include <qpdf/QPDFObjectHandle_private.hh> 7 #include <qpdf/QPDFObjectHandle_private.hh>
  8 +#include <qpdf/QPDF_private.hh>
8 #include <qpdf/QTC.hh> 9 #include <qpdf/QTC.hh>
9 #include <qpdf/QUtil.hh> 10 #include <qpdf/QUtil.hh>
10 #include <cstdlib> 11 #include <cstdlib>
@@ -88,23 +89,21 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const&amp; name) @@ -88,23 +89,21 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const&amp; name)
88 std::string 89 std::string
89 QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& name) 90 QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(std::string const& name)
90 { 91 {
91 - QPDFObjectHandle fv = getInheritableFieldValue(name);  
92 - std::string result; 92 + auto fv = getInheritableFieldValue(name);
93 if (fv.isString()) { 93 if (fv.isString()) {
94 - result = fv.getUTF8Value(); 94 + return fv.getUTF8Value();
95 } 95 }
96 - return result; 96 + return {};
97 } 97 }
98 98
99 std::string 99 std::string
100 QPDFFormFieldObjectHelper::getInheritableFieldValueAsName(std::string const& name) 100 QPDFFormFieldObjectHelper::getInheritableFieldValueAsName(std::string const& name)
101 { 101 {
102 - QPDFObjectHandle fv = getInheritableFieldValue(name);  
103 - std::string result; 102 + auto fv = getInheritableFieldValue(name);
104 if (fv.isName()) { 103 if (fv.isName()) {
105 - result = fv.getName(); 104 + return fv.getName();
106 } 105 }
107 - return result; 106 + return {};
108 } 107 }
109 108
110 std::string 109 std::string
@@ -203,12 +202,11 @@ QPDFFormFieldObjectHelper::getDefaultAppearance() @@ -203,12 +202,11 @@ QPDFFormFieldObjectHelper::getDefaultAppearance()
203 value = getFieldFromAcroForm("/DA"); 202 value = getFieldFromAcroForm("/DA");
204 looked_in_acroform = true; 203 looked_in_acroform = true;
205 } 204 }
206 - std::string result;  
207 if (value.isString()) { 205 if (value.isString()) {
208 QTC::TC("qpdf", "QPDFFormFieldObjectHelper DA present", looked_in_acroform ? 0 : 1); 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 int 212 int
@@ -220,12 +218,11 @@ QPDFFormFieldObjectHelper::getQuadding() @@ -220,12 +218,11 @@ QPDFFormFieldObjectHelper::getQuadding()
220 fv = getFieldFromAcroForm("/Q"); 218 fv = getFieldFromAcroForm("/Q");
221 looked_in_acroform = true; 219 looked_in_acroform = true;
222 } 220 }
223 - int result = 0;  
224 if (fv.isInteger()) { 221 if (fv.isInteger()) {
225 QTC::TC("qpdf", "QPDFFormFieldObjectHelper Q present", looked_in_acroform ? 0 : 1); 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 int 228 int
@@ -238,46 +235,46 @@ QPDFFormFieldObjectHelper::getFlags() @@ -238,46 +235,46 @@ QPDFFormFieldObjectHelper::getFlags()
238 bool 235 bool
239 QPDFFormFieldObjectHelper::isText() 236 QPDFFormFieldObjectHelper::isText()
240 { 237 {
241 - return (getFieldType() == "/Tx"); 238 + return getFieldType() == "/Tx";
242 } 239 }
243 240
244 bool 241 bool
245 QPDFFormFieldObjectHelper::isCheckbox() 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 bool 247 bool
251 QPDFFormFieldObjectHelper::isChecked() 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 bool 253 bool
257 QPDFFormFieldObjectHelper::isRadioButton() 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 bool 259 bool
263 QPDFFormFieldObjectHelper::isPushbutton() 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 bool 265 bool
269 QPDFFormFieldObjectHelper::isChoice() 266 QPDFFormFieldObjectHelper::isChoice()
270 { 267 {
271 - return (getFieldType() == "/Ch"); 268 + return getFieldType() == "/Ch";
272 } 269 }
273 270
274 std::vector<std::string> 271 std::vector<std::string>
275 QPDFFormFieldObjectHelper::getChoices() 272 QPDFFormFieldObjectHelper::getChoices()
276 { 273 {
277 - std::vector<std::string> result;  
278 if (!isChoice()) { 274 if (!isChoice()) {
279 - return result; 275 + return {};
280 } 276 }
  277 + std::vector<std::string> result;
281 for (auto const& item: getInheritableFieldValue("/Opt").as_array()) { 278 for (auto const& item: getInheritableFieldValue("/Opt").as_array()) {
282 if (item.isString()) { 279 if (item.isString()) {
283 result.emplace_back(item.getUTF8Value()); 280 result.emplace_back(item.getUTF8Value());
@@ -308,25 +305,26 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) @@ -308,25 +305,26 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances)
308 { 305 {
309 if (getFieldType() == "/Btn") { 306 if (getFieldType() == "/Btn") {
310 if (isCheckbox()) { 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 warn("ignoring attempt to set a checkbox field to a value whose type is not name"); 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 warn( 320 warn(
327 "ignoring attempt to set a radio button field to an object that is not a name"); 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 warn("ignoring attempt set the value of a pushbutton field"); 328 warn("ignoring attempt set the value of a pushbutton field");
331 } 329 }
332 return; 330 return;
@@ -340,7 +338,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) @@ -340,7 +338,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances)
340 QPDF& qpdf = oh().getQPDF( 338 QPDF& qpdf = oh().getQPDF(
341 "QPDFFormFieldObjectHelper::setV called with need_appearances = " 339 "QPDFFormFieldObjectHelper::setV called with need_appearances = "
342 "true on an object that is not associated with an owning QPDF"); 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,7 +1180,7 @@ void
1180 QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf) 1180 QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf)
1181 { 1181 {
1182 JSON j_acroform = JSON::makeDictionary(); 1182 JSON j_acroform = JSON::makeDictionary();
1183 - QPDFAcroFormDocumentHelper afdh(pdf); 1183 + auto& afdh = pdf.acroform();
1184 j_acroform.addDictionaryMember("hasacroform", JSON::makeBool(afdh.hasAcroForm())); 1184 j_acroform.addDictionaryMember("hasacroform", JSON::makeBool(afdh.hasAcroForm()));
1185 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances())); 1185 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances()));
1186 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray()); 1186 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray());
@@ -1888,17 +1888,6 @@ QPDFJob::validateUnderOverlay(QPDF&amp; pdf, UnderOverlay* uo) @@ -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 std::string 1891 std::string
1903 QPDFJob::doUnderOverlayForPage( 1892 QPDFJob::doUnderOverlayForPage(
1904 QPDF& pdf, 1893 QPDF& pdf,
@@ -1914,13 +1903,7 @@ QPDFJob::doUnderOverlayForPage( @@ -1914,13 +1903,7 @@ QPDFJob::doUnderOverlayForPage(
1914 if (!(pagenos.contains(pageno) && pagenos[pageno].contains(uo_idx))) { 1903 if (!(pagenos.contains(pageno) && pagenos[pageno].contains(uo_idx))) {
1915 return ""; 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 std::string content; 1908 std::string content;
1926 int min_suffix = 1; 1909 int min_suffix = 1;
@@ -1940,7 +1923,7 @@ QPDFJob::doUnderOverlayForPage( @@ -1940,7 +1923,7 @@ QPDFJob::doUnderOverlayForPage(
1940 QPDFMatrix cm; 1923 QPDFMatrix cm;
1941 std::string new_content = dest_page.placeFormXObject( 1924 std::string new_content = dest_page.placeFormXObject(
1942 fo[from_pageno][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); 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 if (!new_content.empty()) { 1927 if (!new_content.empty()) {
1945 resources.mergeResources("<< /XObject << >> >>"_qpdf); 1928 resources.mergeResources("<< /XObject << >> >>"_qpdf);
1946 auto xobject = resources.getKey("/XObject"); 1929 auto xobject = resources.getKey("/XObject");
@@ -2182,15 +2165,15 @@ void @@ -2182,15 +2165,15 @@ void
2182 QPDFJob::handleTransformations(QPDF& pdf) 2165 QPDFJob::handleTransformations(QPDF& pdf)
2183 { 2166 {
2184 QPDFPageDocumentHelper dh(pdf); 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 if (m->remove_restrictions) { 2175 if (m->remove_restrictions) {
2192 - make_afdh();  
2193 - afdh->disableDigitalSignatures(); 2176 + afdh().disableDigitalSignatures();
2194 } 2177 }
2195 if (m->externalize_inline_images || (m->optimize_images && (!m->keep_inline_images))) { 2178 if (m->externalize_inline_images || (m->optimize_images && (!m->keep_inline_images))) {
2196 for (auto& ph: dh.getAllPages()) { 2179 for (auto& ph: dh.getAllPages()) {
@@ -2225,8 +2208,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf) @@ -2225,8 +2208,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf)
2225 } 2208 }
2226 } 2209 }
2227 if (m->generate_appearances) { 2210 if (m->generate_appearances) {
2228 - make_afdh();  
2229 - afdh->generateAppearancesIfNeeded(); 2211 + afdh().generateAppearancesIfNeeded();
2230 } 2212 }
2231 if (m->flatten_annotations) { 2213 if (m->flatten_annotations) {
2232 dh.flattenAnnotations(m->flatten_annotations_required, m->flatten_annotations_forbidden); 2214 dh.flattenAnnotations(m->flatten_annotations_required, m->flatten_annotations_forbidden);
@@ -2237,9 +2219,8 @@ QPDFJob::handleTransformations(QPDF&amp; pdf) @@ -2237,9 +2219,8 @@ QPDFJob::handleTransformations(QPDF&amp; pdf)
2237 } 2219 }
2238 } 2220 }
2239 if (m->flatten_rotation) { 2221 if (m->flatten_rotation) {
2240 - make_afdh();  
2241 for (auto& page: dh.getAllPages()) { 2222 for (auto& page: dh.getAllPages()) {
2242 - page.flattenRotation(afdh.get()); 2223 + page.flattenRotation(&afdh());
2243 } 2224 }
2244 } 2225 }
2245 if (m->remove_page_labels) { 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,8 +2540,7 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2559 std::vector<QPDFObjectHandle> new_labels; 2540 std::vector<QPDFObjectHandle> new_labels;
2560 bool any_page_labels = false; 2541 bool any_page_labels = false;
2561 int out_pageno = 0; 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 std::set<QPDFObjGen> referenced_fields; 2544 std::set<QPDFObjGen> referenced_fields;
2565 for (auto& page_data: parsed_specs) { 2545 for (auto& page_data: parsed_specs) {
2566 ClosedFileInputSource* cis = nullptr; 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,7 +2549,7 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2569 cis->stayOpen(true); 2549 cis->stayOpen(true);
2570 } 2550 }
2571 QPDFPageLabelDocumentHelper pldh(*page_data.qpdf); 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 if (pldh.hasPageLabels()) { 2553 if (pldh.hasPageLabels()) {
2574 any_page_labels = true; 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,15 +2591,15 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2611 // the original file until all copy operations are completed, any foreign pages that 2591 // the original file until all copy operations are completed, any foreign pages that
2612 // conflict with original pages will be adjusted. If we copy any page from the original 2592 // conflict with original pages will be adjusted. If we copy any page from the original
2613 // file more than once, that page would be in conflict with the previous copy of itself. 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 if (!this_file) { 2595 if (!this_file) {
2616 QTC::TC("qpdf", "QPDFJob copy fields not this file"); 2596 QTC::TC("qpdf", "QPDFJob copy fields not this file");
2617 } else if (!first_copy_from_orig) { 2597 } else if (!first_copy_from_orig) {
2618 QTC::TC("qpdf", "QPDFJob copy fields non-first from orig"); 2598 QTC::TC("qpdf", "QPDFJob copy fields non-first from orig");
2619 } 2599 }
2620 try { 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 } catch (std::exception& e) { 2603 } catch (std::exception& e) {
2624 pdf.warn( 2604 pdf.warn(
2625 qpdf_e_damaged_pdf, 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,7 +2627,7 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2647 for (size_t pageno = 0; pageno < orig_pages.size(); ++pageno) { 2627 for (size_t pageno = 0; pageno < orig_pages.size(); ++pageno) {
2648 auto page = orig_pages.at(pageno); 2628 auto page = orig_pages.at(pageno);
2649 if (selected_from_orig.contains(QIntC::to_int(pageno))) { 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 QTC::TC("qpdf", "QPDFJob pages keeping field from original"); 2631 QTC::TC("qpdf", "QPDFJob pages keeping field from original");
2652 referenced_fields.insert(field.getObjectHandle().getObjGen()); 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,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 // Remove unreferenced form fields 2638 // Remove unreferenced form fields
2659 - if (this_afdh->hasAcroForm()) { 2639 + if (this_afdh.hasAcroForm()) {
2660 auto acroform = pdf.getRoot().getKey("/AcroForm"); 2640 auto acroform = pdf.getRoot().getKey("/AcroForm");
2661 auto fields = acroform.getKey("/Fields"); 2641 auto fields = acroform.getKey("/Fields");
2662 if (fields.isArray()) { 2642 if (fields.isArray()) {
@@ -3013,7 +2993,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf) @@ -3013,7 +2993,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3013 dh.removeUnreferencedResources(); 2993 dh.removeUnreferencedResources();
3014 } 2994 }
3015 QPDFPageLabelDocumentHelper pldh(pdf); 2995 QPDFPageLabelDocumentHelper pldh(pdf);
3016 - QPDFAcroFormDocumentHelper afdh(pdf); 2996 + auto& afdh = pdf.acroform();
3017 std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages(); 2997 std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
3018 size_t pageno_len = std::to_string(pages.size()).length(); 2998 size_t pageno_len = std::to_string(pages.size()).length();
3019 size_t num_pages = pages.size(); 2999 size_t num_pages = pages.size();
@@ -3025,10 +3005,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf) @@ -3025,10 +3005,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3025 } 3005 }
3026 QPDF outpdf; 3006 QPDF outpdf;
3027 outpdf.emptyPDF(); 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 if (m->suppress_warnings) { 3009 if (m->suppress_warnings) {
3033 outpdf.setSuppressWarnings(true); 3010 outpdf.setSuppressWarnings(true);
3034 } 3011 }
@@ -3036,8 +3013,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf) @@ -3036,8 +3013,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3036 QPDFObjectHandle page = pages.at(pageno - 1); 3013 QPDFObjectHandle page = pages.at(pageno - 1);
3037 outpdf.addPage(page, false); 3014 outpdf.addPage(page, false);
3038 auto new_page = added_page(outpdf, page); 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 try { 3017 try {
3042 out_afdh->fixCopiedAnnotations(new_page, page, afdh); 3018 out_afdh->fixCopiedAnnotations(new_page, page, afdh);
3043 } catch (std::exception& e) { 3019 } catch (std::exception& e) {
libqpdf/QPDFPageDocumentHelper.cc
1 #include <qpdf/QPDFPageDocumentHelper.hh> 1 #include <qpdf/QPDFPageDocumentHelper.hh>
2 2
3 #include <qpdf/QPDFAcroFormDocumentHelper.hh> 3 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
  4 +#include <qpdf/QPDF_private.hh>
4 #include <qpdf/QTC.hh> 5 #include <qpdf/QTC.hh>
5 #include <qpdf/QUtil.hh> 6 #include <qpdf/QUtil.hh>
6 7
@@ -55,7 +56,7 @@ QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page) @@ -55,7 +56,7 @@ QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page)
55 void 56 void
56 QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags) 57 QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags)
57 { 58 {
58 - QPDFAcroFormDocumentHelper afdh(qpdf); 59 + auto& afdh = qpdf.acroform();
59 if (afdh.getNeedAppearances()) { 60 if (afdh.getNeedAppearances()) {
60 qpdf.getRoot() 61 qpdf.getRoot()
61 .getKey("/AcroForm") 62 .getKey("/AcroForm")
libqpdf/qpdf/QPDF_private.hh
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 3
4 #include <qpdf/QPDF.hh> 4 #include <qpdf/QPDF.hh>
5 5
  6 +#include <qpdf/QPDFAcroFormDocumentHelper.hh>
6 #include <qpdf/QPDFObject_private.hh> 7 #include <qpdf/QPDFObject_private.hh>
7 #include <qpdf/QPDFTokenizer_private.hh> 8 #include <qpdf/QPDFTokenizer_private.hh>
8 9
@@ -547,6 +548,9 @@ class QPDF::Members @@ -547,6 +548,9 @@ class QPDF::Members
547 // Optimization data 548 // Optimization data
548 std::map<ObjUser, std::set<QPDFObjGen>> obj_user_to_objects; 549 std::map<ObjUser, std::set<QPDFObjGen>> obj_user_to_objects;
549 std::map<QPDFObjGen, std::set<ObjUser>> object_to_obj_users; 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 // JobSetter class is restricted to QPDFJob. 556 // JobSetter class is restricted to QPDFJob.
@@ -569,4 +573,13 @@ QPDF::reconstructed_xref() const @@ -569,4 +573,13 @@ QPDF::reconstructed_xref() const
569 return m->reconstructed_xref; 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 #endif // QPDF_PRIVATE_HH 585 #endif // QPDF_PRIVATE_HH
qpdf/qpdf.testcov
@@ -519,7 +519,6 @@ QPDFPageObjectHelper flatten inherit rotate 0 @@ -519,7 +519,6 @@ QPDFPageObjectHelper flatten inherit rotate 0
519 QPDFAcroFormDocumentHelper copy annotation 3 519 QPDFAcroFormDocumentHelper copy annotation 3
520 QPDFAcroFormDocumentHelper field with parent 3 520 QPDFAcroFormDocumentHelper field with parent 3
521 QPDFAcroFormDocumentHelper modify ap matrix 0 521 QPDFAcroFormDocumentHelper modify ap matrix 0
522 -QPDFJob copy form fields in split_pages 0  
523 QPDFJob pages keeping field from original 0 522 QPDFJob pages keeping field from original 0
524 QPDFObjectHandle merge reuse 0 523 QPDFObjectHandle merge reuse 0
525 QPDFObjectHandle merge generate 0 524 QPDFObjectHandle merge generate 0