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 | 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 | 1302 | bool filtered = false; |
| 1297 | 1303 | for (bool first_attempt: {true, false}) { |
| 1298 | 1304 | PipelinePopper pp_stream_data(this); | ... | ... |
libqpdf/QPDF_Stream.cc
| ... | ... | @@ -432,7 +432,16 @@ Stream::pipeStreamData( |
| 432 | 432 | filterp = &ignored; |
| 433 | 433 | } |
| 434 | 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 | 445 | if (filter) { |
| 437 | 446 | filter = filterable(decode_level, filters); |
| 438 | 447 | } | ... | ... |
manual/release-notes.rst
| ... | ... | @@ -23,6 +23,13 @@ more detail. |
| 23 | 23 | not work on some older Linux distributions. If you need support |
| 24 | 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 | 33 | 12.2.0: May 4, 2025 |
| 27 | 34 | - Upcoming C++ Version Change |
| 28 | 35 | ... | ... |
qpdf/qtest/object-stream.test
| ... | ... | @@ -16,7 +16,7 @@ cleanup(); |
| 16 | 16 | |
| 17 | 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 | 20 | my $n_compare_pdfs = 36; |
| 21 | 21 | |
| 22 | 22 | for (my $n = 16; $n <= 19; ++$n) |
| ... | ... | @@ -126,5 +126,23 @@ $td->runtest("adjacent compressed objects", |
| 126 | 126 | $td->EXIT_STATUS => 0}, |
| 127 | 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 | 147 | cleanup(); |
| 130 | 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 | ... | ... |