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 12  
13 13 # ./Testing is created if you run ctest from the wrong place.
14 14 /Testing
  15 +
  16 +# macOS junk files
  17 +.DS_Store
  18 +._*
... ...
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-&gt;runtest(&quot;adjacent compressed objects&quot;,
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
... ...