Commit 5e7b37e15cb366f75dea16a2fc8c1db4d71da0a3
Committed by
GitHub
Merge pull request #1530 from m-holger/afdh
Cache QPDFAcroFormDocumentHelpers
Showing
8 changed files
with
118 additions
and
102 deletions
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& name) | @@ -88,23 +89,21 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& 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& pdf, UnderOverlay* uo) | @@ -1888,17 +1888,6 @@ QPDFJob::validateUnderOverlay(QPDF& 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& pdf) | @@ -2225,8 +2208,7 @@ QPDFJob::handleTransformations(QPDF& 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& pdf) | @@ -2237,9 +2219,8 @@ QPDFJob::handleTransformations(QPDF& 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& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea | @@ -2559,8 +2540,7 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& 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& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea | @@ -2569,7 +2549,7 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& 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& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea | @@ -2611,15 +2591,15 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& 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& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea | @@ -2647,7 +2627,7 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& 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& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea | @@ -2656,7 +2636,7 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& 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& pdf) | @@ -3013,7 +2993,7 @@ QPDFJob::doSplitPages(QPDF& 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& pdf) | @@ -3025,10 +3005,7 @@ QPDFJob::doSplitPages(QPDF& 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& pdf) | @@ -3036,8 +3013,7 @@ QPDFJob::doSplitPages(QPDF& 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 |