You need to sign in before continuing.

Commit 8f3f5099b8e00da04374c9f1f1d416eec07b3935

Authored by m-holger
1 parent 38e9f9de

Fix stream filter handling.

qpdf permits replacing standard stream filters with user provided filters.
#1457 incorrectly removed that option.
libqpdf/QPDFWriter.cc
... ... @@ -1294,7 +1294,8 @@ QPDFWriter::willFilterStream(
1294 1294 }
1295 1295  
1296 1296 // Disable compression for empty streams to improve compatibility
1297   - if (stream_dict.getKey("/Length").isInteger() && stream_dict.getKey("/Length").getIntValue() == 0) {
  1297 + if (stream_dict.getKey("/Length").isInteger() &&
  1298 + stream_dict.getKey("/Length").getIntValue() == 0) {
1298 1299 filter = true;
1299 1300 compress_stream = false;
1300 1301 }
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -85,11 +85,51 @@ namespace
85 85  
86 86 /// User defined streamfilter factories
87 87 std::map<std::string, std::function<std::shared_ptr<QPDFStreamFilter>()>> filter_factories;
  88 + bool filter_factories_registered = false;
88 89 } // namespace
89 90  
  91 +std::string
  92 +QPDF_Stream::Members::expand_filter_name(std::string const& name) const
  93 +{
  94 + // The PDF specification provides these filter abbreviations for use in inline images, but
  95 + // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader also
  96 + // accepts them for stream filters.
  97 + if (name == "/AHx") {
  98 + return "/ASCIIHexDecode";
  99 + }
  100 + if (name == "/A85") {
  101 + return "/ASCII85Decode";
  102 + }
  103 + if (name == "/LZW") {
  104 + return "/LZWDecode";
  105 + }
  106 + if (name == "/Fl") {
  107 + return "/FlateDecode";
  108 + }
  109 + if (name == "/RL") {
  110 + return "/RunLengthDecode";
  111 + }
  112 + if (name == "/CCF") {
  113 + return "/CCITTFaxDecode";
  114 + }
  115 + if (name == "/DCT") {
  116 + return "/DCTDecode";
  117 + }
  118 + return name;
  119 +};
  120 +
90 121 std::function<std::shared_ptr<QPDFStreamFilter>()>
91 122 QPDF_Stream::Members::filter_factory(std::string const& name) const
92 123 {
  124 + if (filter_factories_registered) [[unlikely]] {
  125 + // We need to check user provided filters first as we allow users to replace qpdf provided
  126 + // default filters. This will have a performance impact if the facility to register stream
  127 + // filters is actually used. We can optimize this away if necessary.
  128 + auto ff = filter_factories.find(expand_filter_name(name));
  129 + if (ff != filter_factories.end()) {
  130 + return ff->second;
  131 + }
  132 + }
93 133 if (name == "/FlateDecode") {
94 134 return SF_FlateLzwDecode::flate_factory;
95 135 }
... ... @@ -112,8 +152,8 @@ QPDF_Stream::Members::filter_factory(std::string const&amp; name) const
112 152 return SF_ASCIIHexDecode::factory;
113 153 }
114 154 // The PDF specification provides these filter abbreviations for use in inline images, but
115   - // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader also
116   - // accepts them for stream filters.
  155 + // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader
  156 + // also accepts them for stream filters.
117 157  
118 158 if (name == "/Fl") {
119 159 return SF_FlateLzwDecode::flate_factory;
... ... @@ -133,15 +173,7 @@ QPDF_Stream::Members::filter_factory(std::string const&amp; name) const
133 173 if (name == "/DCT") {
134 174 return SF_DCTDecode::factory;
135 175 }
136   - if (filter_factories.empty()) {
137   - return nullptr;
138   - }
139   - auto ff =
140   - name == "/CCF" ? filter_factories.find("/CCITTFaxDecode") : filter_factories.find(name);
141   - if (ff == filter_factories.end()) {
142   - return nullptr;
143   - }
144   - return ff->second;
  176 + return nullptr;
145 177 }
146 178  
147 179 Stream::Stream(
... ... @@ -159,6 +191,7 @@ Stream::registerStreamFilter(
159 191 std::string const& filter_name, std::function<std::shared_ptr<QPDFStreamFilter>()> factory)
160 192 {
161 193 filter_factories[filter_name] = factory;
  194 + filter_factories_registered = true;
162 195 }
163 196  
164 197 JSON
... ... @@ -437,7 +470,7 @@ Stream::pipeStreamData(
437 470 const bool empty_stream_data = s->stream_data && s->stream_data->getSize() == 0;
438 471 const bool empty = empty_stream || empty_stream_data;
439 472  
440   - if(empty_stream || empty_stream_data) {
  473 + if (empty_stream || empty_stream_data) {
441 474 filter = true;
442 475 }
443 476  
... ...
libqpdf/qpdf/QPDFObject_private.hh
... ... @@ -226,6 +226,7 @@ class QPDF_Stream final
226 226 std::shared_ptr<Buffer> stream_data;
227 227 std::shared_ptr<QPDFObjectHandle::StreamDataProvider> stream_provider;
228 228 std::vector<std::shared_ptr<QPDFObjectHandle::TokenFilter>> token_filters;
  229 + std::string expand_filter_name(std::string const& name) const;
229 230 std::function<std::shared_ptr<QPDFStreamFilter>()>
230 231 filter_factory(std::string const& name) const;
231 232 };
... ...
manual/release-notes.rst
... ... @@ -30,6 +30,13 @@ more detail.
30 30 - More sanity checks have been added when files with damaged xref tables
31 31 are recovered.
32 32  
  33 + - Other changes
  34 +
  35 + - There has been some refactoring of stream filtering. These are optimized
  36 + for the common case where no user provided stream filters are
  37 + registered by calling ``QPDF::registerStreamFilter``. If you are
  38 + providing your own stream filters please open a ticket_.
  39 +
33 40 12.2.0: May 4, 2025
34 41 - Upcoming C++ Version Change
35 42  
... ...