Commit 38e9f9dec70e9b29dc44bb1cd6aeef563b0600b9

Authored by m-holger
Committed by GitHub
2 parents 2a4495dd 28e756f1

Merge pull request #1458 from m-holger/pr1428b

Refactor Stream::pipeStreamData filtering logic for empty streams
.gitignore
@@ -12,3 +12,7 @@ Doxyfile @@ -12,3 +12,7 @@ Doxyfile
12 12
13 # ./Testing is created if you run ctest from the wrong place. 13 # ./Testing is created if you run ctest from the wrong place.
14 /Testing 14 /Testing
  15 +
  16 +# macOS junk files
  17 +.DS_Store
  18 +._*
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-&gt;runtest(&quot;adjacent compressed objects&quot;, @@ -126,5 +126,23 @@ $td-&gt;runtest(&quot;adjacent compressed objects&quot;,
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