Commit 38e9f9dec70e9b29dc44bb1cd6aeef563b0600b9
Committed by
GitHub
Merge pull request #1458 from m-holger/pr1428b
Refactor Stream::pipeStreamData filtering logic for empty streams
Showing
7 changed files
with
108 additions
and
2 deletions
.gitignore
libqpdf/QPDFWriter.cc
| @@ -1293,6 +1293,12 @@ QPDFWriter::willFilterStream( | @@ -1293,6 +1293,12 @@ QPDFWriter::willFilterStream( | ||
| 1293 | QTC::TC("qpdf", "QPDFWriter compressing uncompressed stream"); | 1293 | QTC::TC("qpdf", "QPDFWriter compressing uncompressed stream"); |
| 1294 | } | 1294 | } |
| 1295 | 1295 | ||
| 1296 | + // Disable compression for empty streams to improve compatibility | ||
| 1297 | + if (stream_dict.getKey("/Length").isInteger() && stream_dict.getKey("/Length").getIntValue() == 0) { | ||
| 1298 | + filter = true; | ||
| 1299 | + compress_stream = false; | ||
| 1300 | + } | ||
| 1301 | + | ||
| 1296 | bool filtered = false; | 1302 | bool filtered = false; |
| 1297 | for (bool first_attempt: {true, false}) { | 1303 | for (bool first_attempt: {true, false}) { |
| 1298 | PipelinePopper pp_stream_data(this); | 1304 | PipelinePopper pp_stream_data(this); |
libqpdf/QPDF_Stream.cc
| @@ -432,7 +432,16 @@ Stream::pipeStreamData( | @@ -432,7 +432,16 @@ Stream::pipeStreamData( | ||
| 432 | filterp = &ignored; | 432 | filterp = &ignored; |
| 433 | } | 433 | } |
| 434 | bool& filter = *filterp; | 434 | bool& filter = *filterp; |
| 435 | - filter = encode_flags || decode_level != qpdf_dl_none; | 435 | + |
| 436 | + const bool empty_stream = !s->stream_provider && !s->stream_data && s->length == 0; | ||
| 437 | + const bool empty_stream_data = s->stream_data && s->stream_data->getSize() == 0; | ||
| 438 | + const bool empty = empty_stream || empty_stream_data; | ||
| 439 | + | ||
| 440 | + if(empty_stream || empty_stream_data) { | ||
| 441 | + filter = true; | ||
| 442 | + } | ||
| 443 | + | ||
| 444 | + filter = empty || encode_flags || decode_level != qpdf_dl_none; | ||
| 436 | if (filter) { | 445 | if (filter) { |
| 437 | filter = filterable(decode_level, filters); | 446 | filter = filterable(decode_level, filters); |
| 438 | } | 447 | } |
manual/release-notes.rst
| @@ -23,6 +23,13 @@ more detail. | @@ -23,6 +23,13 @@ more detail. | ||
| 23 | not work on some older Linux distributions. If you need support | 23 | not work on some older Linux distributions. If you need support |
| 24 | for an older distribution, please use version 12.2.0 or below. | 24 | for an older distribution, please use version 12.2.0 or below. |
| 25 | 25 | ||
| 26 | + - Other enhancements | ||
| 27 | + | ||
| 28 | + - ``QPDFWriter`` will no longer add filters when writing empty streams. | ||
| 29 | + | ||
| 30 | + - More sanity checks have been added when files with damaged xref tables | ||
| 31 | + are recovered. | ||
| 32 | + | ||
| 26 | 12.2.0: May 4, 2025 | 33 | 12.2.0: May 4, 2025 |
| 27 | - Upcoming C++ Version Change | 34 | - Upcoming C++ Version Change |
| 28 | 35 |
qpdf/qtest/object-stream.test
| @@ -16,7 +16,7 @@ cleanup(); | @@ -16,7 +16,7 @@ cleanup(); | ||
| 16 | 16 | ||
| 17 | my $td = new TestDriver('object-stream'); | 17 | my $td = new TestDriver('object-stream'); |
| 18 | 18 | ||
| 19 | -my $n_tests = 10 + (36 * 4) + (12 * 2); | 19 | +my $n_tests = 10 + (36 * 4) + (12 * 2) + 4; |
| 20 | my $n_compare_pdfs = 36; | 20 | my $n_compare_pdfs = 36; |
| 21 | 21 | ||
| 22 | for (my $n = 16; $n <= 19; ++$n) | 22 | for (my $n = 16; $n <= 19; ++$n) |
| @@ -126,5 +126,23 @@ $td->runtest("adjacent compressed objects", | @@ -126,5 +126,23 @@ $td->runtest("adjacent compressed objects", | ||
| 126 | $td->EXIT_STATUS => 0}, | 126 | $td->EXIT_STATUS => 0}, |
| 127 | $td->NORMALIZE_NEWLINES); | 127 | $td->NORMALIZE_NEWLINES); |
| 128 | 128 | ||
| 129 | +# Never compress empty streams | ||
| 130 | +$td->runtest("never compress empty streams", | ||
| 131 | + {$td->COMMAND => "qpdf --compress-streams=y --static-id" . | ||
| 132 | + " empty-stream-uncompressed.pdf a.pdf"}, | ||
| 133 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | ||
| 134 | +$td->runtest("check file", | ||
| 135 | + {$td->FILE => "a.pdf"}, | ||
| 136 | + {$td->FILE => "empty-stream-uncompressed.pdf"}); | ||
| 137 | + | ||
| 138 | +# Always remove filters from compressed empty streams | ||
| 139 | +$td->runtest("always remove filters from empty streams", | ||
| 140 | + {$td->COMMAND => "qpdf --compress-streams=y --static-id" . | ||
| 141 | + " empty-stream-compressed.pdf a.pdf"}, | ||
| 142 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | ||
| 143 | +$td->runtest("check file", | ||
| 144 | + {$td->FILE => "a.pdf"}, | ||
| 145 | + {$td->FILE => "empty-stream-uncompressed.pdf"}); | ||
| 146 | + | ||
| 129 | cleanup(); | 147 | cleanup(); |
| 130 | $td->report(calc_ntests($n_tests, $n_compare_pdfs)); | 148 | $td->report(calc_ntests($n_tests, $n_compare_pdfs)); |
qpdf/qtest/qpdf/empty-stream-compressed.pdf
0 → 100644
| 1 | +%PDF-1.4 | ||
| 2 | +%¿÷¢þ | ||
| 3 | +1 0 obj | ||
| 4 | +<< /Pages 3 0 R /Type /Catalog /ViewerPreferences << /DisplayDocTitle true /Type /ViewerPreferences >> >> | ||
| 5 | +endobj | ||
| 6 | +2 0 obj | ||
| 7 | +<< /CreationDate (D:20250409064524+00'00') /Creator (Mozilla/5.0 \(X11; Linux x86_64\) AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/135.0.0.0 Safari/537.36) /ModDate (D:20250409064524+00'00') /Producer (Skia/PDF m135) /Title (about:blank) >> | ||
| 8 | +endobj | ||
| 9 | +3 0 obj | ||
| 10 | +<< /Count 1 /Kids [ 4 0 R ] /Type /Pages >> | ||
| 11 | +endobj | ||
| 12 | +4 0 obj | ||
| 13 | +<< /Contents 5 0 R /MediaBox [ 0 0 594.95996 841.91998 ] /Parent 3 0 R /Resources << /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /StructParents 0 /Tabs /S /Type /Page >> | ||
| 14 | +endobj | ||
| 15 | +5 0 obj | ||
| 16 | +<< /Length 0 /Filter /FlateDecode >> | ||
| 17 | +stream | ||
| 18 | +endstream | ||
| 19 | +endobj | ||
| 20 | +xref | ||
| 21 | +0 6 | ||
| 22 | +0000000000 65535 f | ||
| 23 | +0000000015 00000 n | ||
| 24 | +0000000136 00000 n | ||
| 25 | +0000000396 00000 n | ||
| 26 | +0000000455 00000 n | ||
| 27 | +0000000647 00000 n | ||
| 28 | +trailer << /Info 2 0 R /Root 1 0 R /Size 6 /ID [<c1d8baf5144c3a51530c879d5c908f31><31415926535897932384626433832795>] >> | ||
| 29 | +startxref | ||
| 30 | +716 | ||
| 31 | +%%EOF |
qpdf/qtest/qpdf/empty-stream-uncompressed.pdf
0 → 100644
| 1 | +%PDF-1.4 | ||
| 2 | +%¿÷¢þ | ||
| 3 | +1 0 obj | ||
| 4 | +<< /Pages 3 0 R /Type /Catalog /ViewerPreferences << /DisplayDocTitle true /Type /ViewerPreferences >> >> | ||
| 5 | +endobj | ||
| 6 | +2 0 obj | ||
| 7 | +<< /CreationDate (D:20250409064524+00'00') /Creator (Mozilla/5.0 \(X11; Linux x86_64\) AppleWebKit/537.36 \(KHTML, like Gecko\) Chrome/135.0.0.0 Safari/537.36) /ModDate (D:20250409064524+00'00') /Producer (Skia/PDF m135) /Title (about:blank) >> | ||
| 8 | +endobj | ||
| 9 | +3 0 obj | ||
| 10 | +<< /Count 1 /Kids [ 4 0 R ] /Type /Pages >> | ||
| 11 | +endobj | ||
| 12 | +4 0 obj | ||
| 13 | +<< /Contents 5 0 R /MediaBox [ 0 0 594.95996 841.91998 ] /Parent 3 0 R /Resources << /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /StructParents 0 /Tabs /S /Type /Page >> | ||
| 14 | +endobj | ||
| 15 | +5 0 obj | ||
| 16 | +<< /Length 0 >> | ||
| 17 | +stream | ||
| 18 | +endstream | ||
| 19 | +endobj | ||
| 20 | +xref | ||
| 21 | +0 6 | ||
| 22 | +0000000000 65535 f | ||
| 23 | +0000000015 00000 n | ||
| 24 | +0000000136 00000 n | ||
| 25 | +0000000396 00000 n | ||
| 26 | +0000000455 00000 n | ||
| 27 | +0000000647 00000 n | ||
| 28 | +trailer << /Info 2 0 R /Root 1 0 R /Size 6 /ID [<c1d8baf5144c3a51530c879d5c908f31><31415926535897932384626433832795>] >> | ||
| 29 | +startxref | ||
| 30 | +695 | ||
| 31 | +%%EOF |