Commit 36e1c1425f124170d6af907312bdc9c744805654

Authored by m-holger
1 parent 847f0246

Integrate `QPDFPageDocumentHelper` with `QPDF` for improved page management. Add…

… shared helper retrieval, validation methods, and update usages across the codebase. Remove unused test coverage entries.
examples/examples.testcov
1 -pdf-bookmarks lines 0  
2 -pdf-bookmarks numbers 0  
3 -pdf-bookmarks none 0  
4 -pdf-bookmarks has count 0  
5 -pdf-bookmarks no count 0  
6 -pdf-bookmarks open 0  
7 -pdf-bookmarks closed 0  
8 -pdf-bookmarks dest 0  
9 -pdf-bookmarks targets 0  
10 pdf-mod-info --dump 0 1 pdf-mod-info --dump 0
11 pdf-mod-info no in file 0 2 pdf-mod-info no in file 0
12 pdf-mod-info in-place 0 3 pdf-mod-info in-place 0
examples/pdf-bookmarks.cc
@@ -47,7 +47,7 @@ print_lines(std::vector<int>& numbers) @@ -47,7 +47,7 @@ print_lines(std::vector<int>& numbers)
47 void 47 void
48 generate_page_map(QPDF& qpdf) 48 generate_page_map(QPDF& qpdf)
49 { 49 {
50 - QPDFPageDocumentHelper dh(qpdf); 50 + auto& dh = QPDFPageDocumentHelper::get(qpdf);
51 int n = 0; 51 int n = 0;
52 for (auto const& page: dh.getAllPages()) { 52 for (auto const& page: dh.getAllPages()) {
53 page_map[page] = ++n; 53 page_map[page] = ++n;
@@ -60,11 +60,9 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) @@ -60,11 +60,9 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers)
60 // No default so gcc will warn on missing tag 60 // No default so gcc will warn on missing tag
61 switch (style) { 61 switch (style) {
62 case st_none: 62 case st_none:
63 - QTC::TC("examples", "pdf-bookmarks none");  
64 break; 63 break;
65 64
66 case st_numbers: 65 case st_numbers:
67 - QTC::TC("examples", "pdf-bookmarks numbers");  
68 for (auto const& number: numbers) { 66 for (auto const& number: numbers) {
69 std::cout << number << "."; 67 std::cout << number << ".";
70 } 68 }
@@ -72,7 +70,6 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector&lt;int&gt; numbers) @@ -72,7 +70,6 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector&lt;int&gt; numbers)
72 break; 70 break;
73 71
74 case st_lines: 72 case st_lines:
75 - QTC::TC("examples", "pdf-bookmarks lines");  
76 print_lines(numbers); 73 print_lines(numbers);
77 std::cout << "|\n"; 74 std::cout << "|\n";
78 print_lines(numbers); 75 print_lines(numbers);
@@ -83,27 +80,21 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector&lt;int&gt; numbers) @@ -83,27 +80,21 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector&lt;int&gt; numbers)
83 if (show_open) { 80 if (show_open) {
84 int count = outline.getCount(); 81 int count = outline.getCount();
85 if (count) { 82 if (count) {
86 - QTC::TC("examples", "pdf-bookmarks has count");  
87 if (count > 0) { 83 if (count > 0) {
88 // hierarchy is open at this point 84 // hierarchy is open at this point
89 - QTC::TC("examples", "pdf-bookmarks open");  
90 std::cout << "(v) "; 85 std::cout << "(v) ";
91 } else { 86 } else {
92 - QTC::TC("examples", "pdf-bookmarks closed");  
93 std::cout << "(>) "; 87 std::cout << "(>) ";
94 } 88 }
95 } else { 89 } else {
96 - QTC::TC("examples", "pdf-bookmarks no count");  
97 std::cout << "( ) "; 90 std::cout << "( ) ";
98 } 91 }
99 } 92 }
100 93
101 if (show_targets) { 94 if (show_targets) {
102 - QTC::TC("examples", "pdf-bookmarks targets");  
103 std::string target = "unknown"; 95 std::string target = "unknown";
104 QPDFObjectHandle dest_page = outline.getDestPage(); 96 QPDFObjectHandle dest_page = outline.getDestPage();
105 if (!dest_page.isNull()) { 97 if (!dest_page.isNull()) {
106 - QTC::TC("examples", "pdf-bookmarks dest");  
107 if (page_map.contains(dest_page)) { 98 if (page_map.contains(dest_page)) {
108 target = std::to_string(page_map[dest_page]); 99 target = std::to_string(page_map[dest_page]);
109 } 100 }
examples/pdf-count-strings.cc
@@ -76,7 +76,7 @@ main(int argc, char* argv[]) @@ -76,7 +76,7 @@ main(int argc, char* argv[])
76 QPDF pdf; 76 QPDF pdf;
77 pdf.processFile(infilename); 77 pdf.processFile(infilename);
78 int pageno = 0; 78 int pageno = 0;
79 - for (auto& page: QPDFPageDocumentHelper(pdf).getAllPages()) { 79 + for (auto& page: QPDFPageDocumentHelper::get(pdf).getAllPages()) {
80 ++pageno; 80 ++pageno;
81 // Pass the contents of a page through our string counter. If it's an even page, capture 81 // Pass the contents of a page through our string counter. If it's an even page, capture
82 // the output. This illustrates that you may capture any output generated by the filter, 82 // the output. This illustrates that you may capture any output generated by the filter,
examples/pdf-create.cc
@@ -229,7 +229,7 @@ check( @@ -229,7 +229,7 @@ check(
229 229
230 QPDF pdf; 230 QPDF pdf;
231 pdf.processFile(filename); 231 pdf.processFile(filename);
232 - auto pages = QPDFPageDocumentHelper(pdf).getAllPages(); 232 + auto pages = QPDFPageDocumentHelper::get(pdf).getAllPages();
233 if (n_color_spaces * n_filters != pages.size()) { 233 if (n_color_spaces * n_filters != pages.size()) {
234 throw std::logic_error("incorrect number of pages"); 234 throw std::logic_error("incorrect number of pages");
235 } 235 }
examples/pdf-overlay-page.cc
@@ -29,14 +29,14 @@ stamp_page(char const* infile, char const* stampfile, char const* outfile) @@ -29,14 +29,14 @@ stamp_page(char const* infile, char const* stampfile, char const* outfile)
29 stamppdf.processFile(stampfile); 29 stamppdf.processFile(stampfile);
30 30
31 // Get first page from other file 31 // Get first page from other file
32 - QPDFPageObjectHelper stamp_page_1 = QPDFPageDocumentHelper(stamppdf).getAllPages().at(0); 32 + QPDFPageObjectHelper stamp_page_1 = QPDFPageDocumentHelper::get(stamppdf).getAllPages().at(0);
33 // Convert page to a form XObject 33 // Convert page to a form XObject
34 QPDFObjectHandle foreign_fo = stamp_page_1.getFormXObjectForPage(); 34 QPDFObjectHandle foreign_fo = stamp_page_1.getFormXObjectForPage();
35 // Copy form XObject to the input file 35 // Copy form XObject to the input file
36 QPDFObjectHandle stamp_fo = inpdf.copyForeignObject(foreign_fo); 36 QPDFObjectHandle stamp_fo = inpdf.copyForeignObject(foreign_fo);
37 37
38 // For each page... 38 // For each page...
39 - for (auto& ph: QPDFPageDocumentHelper(inpdf).getAllPages()) { 39 + for (auto& ph: QPDFPageDocumentHelper::get(inpdf).getAllPages()) {
40 // Find a unique resource name for the new form XObject 40 // Find a unique resource name for the new form XObject
41 QPDFObjectHandle resources = ph.getAttribute("/Resources", true); 41 QPDFObjectHandle resources = ph.getAttribute("/Resources", true);
42 int min_suffix = 1; 42 int min_suffix = 1;
include/qpdf/QPDF.hh
@@ -65,6 +65,7 @@ class QPDFParser; @@ -65,6 +65,7 @@ class QPDFParser;
65 class QPDFAcroFormDocumentHelper; 65 class QPDFAcroFormDocumentHelper;
66 class QPDFEmbeddedFileDocumentHelper; 66 class QPDFEmbeddedFileDocumentHelper;
67 class QPDFOutlineDocumentHelper; 67 class QPDFOutlineDocumentHelper;
  68 +class QPDFPageDocumentHelper;
68 class QPDFPageLabelDocumentHelper; 69 class QPDFPageLabelDocumentHelper;
69 70
70 class QPDF 71 class QPDF
@@ -804,6 +805,7 @@ class QPDF @@ -804,6 +805,7 @@ class QPDF
804 inline QPDFAcroFormDocumentHelper& acroform(); 805 inline QPDFAcroFormDocumentHelper& acroform();
805 inline QPDFEmbeddedFileDocumentHelper& embedded_files(); 806 inline QPDFEmbeddedFileDocumentHelper& embedded_files();
806 inline QPDFOutlineDocumentHelper& outlines(); 807 inline QPDFOutlineDocumentHelper& outlines();
  808 + inline QPDFPageDocumentHelper& pages();
807 inline QPDFPageLabelDocumentHelper& page_labels(); 809 inline QPDFPageLabelDocumentHelper& page_labels();
808 810
809 // For testing only -- do not add to DLL 811 // For testing only -- do not add to DLL
include/qpdf/QPDFPageDocumentHelper.hh
@@ -35,6 +35,21 @@ class QPDFAcroFormDocumentHelper; @@ -35,6 +35,21 @@ class QPDFAcroFormDocumentHelper;
35 class QPDFPageDocumentHelper: public QPDFDocumentHelper 35 class QPDFPageDocumentHelper: public QPDFDocumentHelper
36 { 36 {
37 public: 37 public:
  38 + // Get a shared document helper for a given QPDF object.
  39 + //
  40 + // Retrieving a document helper for a QPDF object rather than creating a new one avoids repeated
  41 + // validation of the Acroform structure, which can be expensive.
  42 + QPDF_DLL
  43 + static QPDFPageDocumentHelper& get(QPDF& qpdf);
  44 +
  45 + // Re-validate the Pages structure. This is useful if you have modified the Pages structure in
  46 + // a way that would invalidate the cache.
  47 + //
  48 + // If repair is true, the document will be repaired if possible if the validation encounters
  49 + // errors.
  50 + QPDF_DLL
  51 + void validate(bool repair = true);
  52 +
38 QPDF_DLL 53 QPDF_DLL
39 QPDFPageDocumentHelper(QPDF&); 54 QPDFPageDocumentHelper(QPDF&);
40 55
@@ -112,17 +127,7 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper @@ -112,17 +127,7 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper
112 int required_flags, 127 int required_flags,
113 int forbidden_flags); 128 int forbidden_flags);
114 129
115 - class Members  
116 - {  
117 - friend class QPDFPageDocumentHelper;  
118 -  
119 - public:  
120 - ~Members() = default;  
121 -  
122 - private:  
123 - Members() = default;  
124 - Members(Members const&) = delete;  
125 - }; 130 + class Members;
126 131
127 std::shared_ptr<Members> m; 132 std::shared_ptr<Members> m;
128 }; 133 };
libqpdf/QPDFJob.cc
@@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
17 #include <qpdf/QPDFExc.hh> 17 #include <qpdf/QPDFExc.hh>
18 #include <qpdf/QPDFLogger.hh> 18 #include <qpdf/QPDFLogger.hh>
19 #include <qpdf/QPDFObjectHandle_private.hh> 19 #include <qpdf/QPDFObjectHandle_private.hh>
20 -#include <qpdf/QPDFPageDocumentHelper.hh>  
21 #include <qpdf/QPDFPageObjectHelper.hh> 20 #include <qpdf/QPDFPageObjectHelper.hh>
22 #include <qpdf/QPDFSystemError.hh> 21 #include <qpdf/QPDFSystemError.hh>
23 #include <qpdf/QPDFUsage.hh> 22 #include <qpdf/QPDFUsage.hh>
@@ -790,7 +789,7 @@ QPDFJob::doCheck(QPDF&amp; pdf) @@ -790,7 +789,7 @@ QPDFJob::doCheck(QPDF&amp; pdf)
790 789
791 // Parse all content streams 790 // Parse all content streams
792 int pageno = 0; 791 int pageno = 0;
793 - for (auto& page: QPDFPageDocumentHelper(pdf).getAllPages()) { 792 + for (auto& page: pdf.pages().getAllPages()) {
794 ++pageno; 793 ++pageno;
795 try { 794 try {
796 page.parseContents(nullptr); 795 page.parseContents(nullptr);
@@ -858,7 +857,7 @@ QPDFJob::doShowPages(QPDF&amp; pdf) @@ -858,7 +857,7 @@ QPDFJob::doShowPages(QPDF&amp; pdf)
858 { 857 {
859 int pageno = 0; 858 int pageno = 0;
860 auto& cout = *m->log->getInfo(); 859 auto& cout = *m->log->getInfo();
861 - for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { 860 + for (auto& ph: pdf.pages().getAllPages()) {
862 QPDFObjectHandle page = ph.getObjectHandle(); 861 QPDFObjectHandle page = ph.getObjectHandle();
863 ++pageno; 862 ++pageno;
864 863
@@ -1052,7 +1051,7 @@ QPDFJob::doJSONPages(Pipeline* p, bool&amp; first, QPDF&amp; pdf) @@ -1052,7 +1051,7 @@ QPDFJob::doJSONPages(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1052 auto& pldh = pdf.page_labels(); 1051 auto& pldh = pdf.page_labels();
1053 auto& odh = pdf.outlines(); 1052 auto& odh = pdf.outlines();
1054 int pageno = -1; 1053 int pageno = -1;
1055 - for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { 1054 + for (auto& ph: pdf.pages().getAllPages()) {
1056 ++pageno; 1055 ++pageno;
1057 JSON j_page = JSON::makeDictionary(); 1056 JSON j_page = JSON::makeDictionary();
1058 QPDFObjectHandle page = ph.getObjectHandle(); 1057 QPDFObjectHandle page = ph.getObjectHandle();
@@ -1113,7 +1112,7 @@ QPDFJob::doJSONPageLabels(Pipeline* p, bool&amp; first, QPDF&amp; pdf) @@ -1113,7 +1112,7 @@ QPDFJob::doJSONPageLabels(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1113 { 1112 {
1114 JSON j_labels = JSON::makeArray(); 1113 JSON j_labels = JSON::makeArray();
1115 auto& pldh = pdf.page_labels(); 1114 auto& pldh = pdf.page_labels();
1116 - long long npages = QIntC::to_longlong(QPDFPageDocumentHelper(pdf).getAllPages().size()); 1115 + long long npages = QIntC::to_longlong(pdf.pages().getAllPages().size());
1117 if (pldh.hasPageLabels()) { 1116 if (pldh.hasPageLabels()) {
1118 std::vector<QPDFObjectHandle> labels; 1117 std::vector<QPDFObjectHandle> labels;
1119 pldh.getLabelsForPageRange(0, npages - 1, 0, labels); 1118 pldh.getLabelsForPageRange(0, npages - 1, 0, labels);
@@ -1161,7 +1160,7 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool&amp; first, QPDF&amp; pdf) @@ -1161,7 +1160,7 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1161 { 1160 {
1162 std::map<QPDFObjGen, int> page_numbers; 1161 std::map<QPDFObjGen, int> page_numbers;
1163 int n = 0; 1162 int n = 0;
1164 - for (auto const& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { 1163 + for (auto const& ph: pdf.pages().getAllPages()) {
1165 QPDFObjectHandle oh = ph.getObjectHandle(); 1164 QPDFObjectHandle oh = ph.getObjectHandle();
1166 page_numbers[oh.getObjGen()] = ++n; 1165 page_numbers[oh.getObjGen()] = ++n;
1167 } 1166 }
@@ -1180,7 +1179,7 @@ QPDFJob::doJSONAcroform(Pipeline* p, bool&amp; first, QPDF&amp; pdf) @@ -1180,7 +1179,7 @@ QPDFJob::doJSONAcroform(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1180 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances())); 1179 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances()));
1181 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray()); 1180 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray());
1182 int pagepos1 = 0; 1181 int pagepos1 = 0;
1183 - for (auto const& page: QPDFPageDocumentHelper(pdf).getAllPages()) { 1182 + for (auto const& page: pdf.pages().getAllPages()) {
1184 ++pagepos1; 1183 ++pagepos1;
1185 for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) { 1184 for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) {
1186 QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh); 1185 QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh);
@@ -1857,7 +1856,7 @@ QPDFJob::processInputSource( @@ -1857,7 +1856,7 @@ QPDFJob::processInputSource(
1857 void 1856 void
1858 QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) 1857 QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo)
1859 { 1858 {
1860 - QPDFPageDocumentHelper main_pdh(pdf); 1859 + auto& main_pdh = pdf.pages();
1861 int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); 1860 int main_npages = QIntC::to_int(main_pdh.getAllPages().size());
1862 processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false); 1861 processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false);
1863 QPDFPageDocumentHelper uo_pdh(*(uo->pdf)); 1862 QPDFPageDocumentHelper uo_pdh(*(uo->pdf));
libqpdf/QPDFPageDocumentHelper.cc
@@ -6,11 +6,26 @@ @@ -6,11 +6,26 @@
6 #include <qpdf/QTC.hh> 6 #include <qpdf/QTC.hh>
7 #include <qpdf/QUtil.hh> 7 #include <qpdf/QUtil.hh>
8 8
  9 +class QPDFPageDocumentHelper::Members
  10 +{
  11 +};
  12 +
9 QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) : 13 QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) :
10 QPDFDocumentHelper(qpdf) 14 QPDFDocumentHelper(qpdf)
11 { 15 {
12 } 16 }
13 17
  18 +QPDFPageDocumentHelper&
  19 +QPDFPageDocumentHelper::get(QPDF& qpdf)
  20 +{
  21 + return qpdf.pages();
  22 +}
  23 +
  24 +void
  25 +QPDFPageDocumentHelper::validate(bool repair)
  26 +{
  27 +}
  28 +
14 std::vector<QPDFPageObjectHelper> 29 std::vector<QPDFPageObjectHelper>
15 QPDFPageDocumentHelper::getAllPages() 30 QPDFPageDocumentHelper::getAllPages()
16 { 31 {
libqpdf/qpdf/QPDF_private.hh
@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> 7 #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
8 #include <qpdf/QPDFObject_private.hh> 8 #include <qpdf/QPDFObject_private.hh>
9 #include <qpdf/QPDFOutlineDocumentHelper.hh> 9 #include <qpdf/QPDFOutlineDocumentHelper.hh>
  10 +#include <qpdf/QPDFPageDocumentHelper.hh>
10 #include <qpdf/QPDFPageLabelDocumentHelper.hh> 11 #include <qpdf/QPDFPageLabelDocumentHelper.hh>
11 #include <qpdf/QPDFTokenizer_private.hh> 12 #include <qpdf/QPDFTokenizer_private.hh>
12 13
@@ -558,6 +559,7 @@ class QPDF::Members @@ -558,6 +559,7 @@ class QPDF::Members
558 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform; 559 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform;
559 std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files; 560 std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files;
560 std::unique_ptr<QPDFOutlineDocumentHelper> outlines; 561 std::unique_ptr<QPDFOutlineDocumentHelper> outlines;
  562 + std::unique_ptr<QPDFPageDocumentHelper> pages;
561 std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels; 563 std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels;
562 }; 564 };
563 565
@@ -608,6 +610,15 @@ QPDF::outlines() @@ -608,6 +610,15 @@ QPDF::outlines()
608 return *m->outlines; 610 return *m->outlines;
609 } 611 }
610 612
  613 +inline QPDFPageDocumentHelper&
  614 +QPDF::pages()
  615 +{
  616 + if (!m->pages) {
  617 + m->pages = std::make_unique<QPDFPageDocumentHelper>(*this);
  618 + }
  619 + return *m->pages;
  620 +}
  621 +
611 inline QPDFPageLabelDocumentHelper& 622 inline QPDFPageLabelDocumentHelper&
612 QPDF::page_labels() 623 QPDF::page_labels()
613 { 624 {
manual/release-notes.rst
@@ -26,20 +26,27 @@ more detail. @@ -26,20 +26,27 @@ more detail.
26 - Library Enhancements 26 - Library Enhancements
27 27
28 - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` 28 - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper``
29 - constructor overloads that allow a function to ne passed to 29 + constructor overloads that allow a function to be passed to
30 validate the values in the tree. 30 validate the values in the tree.
31 31
32 - Add new ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` 32 - Add new ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper``
33 ``validate`` method to validate and optionally repair the name/number 33 ``validate`` method to validate and optionally repair the name/number
34 tree. 34 tree.
35 35
  36 + - Add new ``get`` and ``validate`` methods to all DocumentHelper classes.
  37 + The ``get`` method retrieves a shared DocumentHelper, avoiding the the
  38 + overhead of repeatedly validating the underlying document structure
  39 + and/or building internal caches. If the underlying document structure
  40 + is directly modified (without the use of DocumentHelpers), the
  41 + ``validate`` methods revalidates the structure and resynchronizes any
  42 + internal caches.
  43 +
36 - CLI Enhancements 44 - CLI Enhancements
37 45
38 - Disallow option :qpdf:ref:`--deterministic-id` to be used together 46 - Disallow option :qpdf:ref:`--deterministic-id` to be used together
39 with the incompatible options :qpdf:ref:`--encrypt` or 47 with the incompatible options :qpdf:ref:`--encrypt` or
40 :qpdf:ref:`--copy-encryption`. 48 :qpdf:ref:`--copy-encryption`.
41 49
42 -  
43 - Other enhancements 50 - Other enhancements
44 51
45 - ``QPDFWriter`` will no longer add filters when writing empty streams. 52 - ``QPDFWriter`` will no longer add filters when writing empty streams.