Commit 12ecd2019a3186d11ebb6083b3813ce722fb2329

Authored by Jay Berkenbilt
1 parent 3f9191a3

Add QPDFObjectHandle::setFilterOnWrite

ChangeLog
1 1 2020-12-26 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Add QPDFObjectHandle::setFilterOnWrite, which can be used to
  4 + tell QPDFWriter not to filter a stream on output even if it can.
  5 + You can use this to prevent QPDFWriter from touching a stream
  6 + (either uncompressing or compressing) that you have optimized or
  7 + otherwise ensured looks exactly the way you want it, even if
  8 + decode level or stream compression would otherwise cause
  9 + QPDFWriter to modify the stream.
  10 +
3 11 * Add ostream << for QPDFObjGen. (Don't ask why it took 7.5 years
4 12 for me to decide to do this.)
5 13  
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -786,6 +786,22 @@ class QPDFObjectHandle
786 786 QPDF_DLL
787 787 QPDFObjectHandle getDict();
788 788  
  789 + // By default, or if true passed, QPDFWriter will attempt to
  790 + // filter a stream based on decode level, whether compression is
  791 + // enabled, and its ability to filter. Passing false will prevent
  792 + // QPDFWriter from attempting to filter the stream even if it can.
  793 + // This includes both decoding and compressing. This makes it
  794 + // possible for you to prevent QPDFWriter from uncompressing and
  795 + // recompressing a stream that it knows how to operate on for any
  796 + // application-specific reason, such as that you have already
  797 + // optimized its filtering. Note that this doesn't affect any
  798 + // other ways to get the stream's data, such as pipeStreamData or
  799 + // getStreamData.
  800 + QPDF_DLL
  801 + void setFilterOnWrite(bool);
  802 + QPDF_DLL
  803 + bool getFilterOnWrite();
  804 +
789 805 // If addTokenFilter has been called for this stream, then the
790 806 // original data should be considered to be modified. This means we
791 807 // should avoid optimizations such as not filtering a stream that
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -1176,6 +1176,20 @@ QPDFObjectHandle::getDict()
1176 1176 return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getDict();
1177 1177 }
1178 1178  
  1179 +void
  1180 +QPDFObjectHandle::setFilterOnWrite(bool val)
  1181 +{
  1182 + assertStream();
  1183 + dynamic_cast<QPDF_Stream*>(obj.getPointer())->setFilterOnWrite(val);
  1184 +}
  1185 +
  1186 +bool
  1187 +QPDFObjectHandle::getFilterOnWrite()
  1188 +{
  1189 + assertStream();
  1190 + return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getFilterOnWrite();
  1191 +}
  1192 +
1179 1193 bool
1180 1194 QPDFObjectHandle::isDataModified()
1181 1195 {
... ...
libqpdf/QPDFWriter.cc
... ... @@ -1470,6 +1470,7 @@ QPDFWriter::willFilterStream(QPDFObjectHandle stream,
1470 1470 {
1471 1471 compress_stream = false;
1472 1472 is_metadata = false;
  1473 +
1473 1474 QPDFObjGen old_og = stream.getObjGen();
1474 1475 QPDFObjectHandle stream_dict = stream.getDict();
1475 1476  
... ... @@ -1481,7 +1482,13 @@ QPDFWriter::willFilterStream(QPDFObjectHandle stream,
1481 1482 bool filter = (stream.isDataModified() ||
1482 1483 this->m->compress_streams ||
1483 1484 this->m->stream_decode_level);
1484   - if (this->m->compress_streams)
  1485 + bool filter_on_write = stream.getFilterOnWrite();
  1486 + if (! filter_on_write)
  1487 + {
  1488 + QTC::TC("qpdf", "QPDFWriter getFilterOnWrite false");
  1489 + filter = false;
  1490 + }
  1491 + if (filter_on_write && this->m->compress_streams)
1485 1492 {
1486 1493 // Don't filter if the stream is already compressed with
1487 1494 // FlateDecode. This way we don't make it worse if the
... ... @@ -1502,7 +1509,7 @@ QPDFWriter::willFilterStream(QPDFObjectHandle stream,
1502 1509 }
1503 1510 bool normalize = false;
1504 1511 bool uncompress = false;
1505   - if (is_metadata &&
  1512 + if (filter_on_write && is_metadata &&
1506 1513 ((! this->m->encrypted) || (this->m->encrypt_metadata == false)))
1507 1514 {
1508 1515 QTC::TC("qpdf", "QPDFWriter not compressing metadata");
... ... @@ -1510,13 +1517,13 @@ QPDFWriter::willFilterStream(QPDFObjectHandle stream,
1510 1517 compress_stream = false;
1511 1518 uncompress = true;
1512 1519 }
1513   - else if (this->m->normalize_content &&
  1520 + else if (filter_on_write && this->m->normalize_content &&
1514 1521 this->m->normalized_streams.count(old_og))
1515 1522 {
1516 1523 normalize = true;
1517 1524 filter = true;
1518 1525 }
1519   - else if (filter && this->m->compress_streams)
  1526 + else if (filter_on_write && filter && this->m->compress_streams)
1520 1527 {
1521 1528 compress_stream = true;
1522 1529 QTC::TC("qpdf", "QPDFWriter compressing uncompressed stream");
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -90,6 +90,7 @@ QPDF_Stream::QPDF_Stream(QPDF* qpdf, int objid, int generation,
90 90 qpdf(qpdf),
91 91 objid(objid),
92 92 generation(generation),
  93 + filter_on_write(true),
93 94 stream_dict(stream_dict),
94 95 offset(offset),
95 96 length(length)
... ... @@ -116,6 +117,18 @@ QPDF_Stream::registerStreamFilter(
116 117 }
117 118  
118 119 void
  120 +QPDF_Stream::setFilterOnWrite(bool val)
  121 +{
  122 + this->filter_on_write = val;
  123 +}
  124 +
  125 +bool
  126 +QPDF_Stream::getFilterOnWrite() const
  127 +{
  128 + return this->filter_on_write;
  129 +}
  130 +
  131 +void
119 132 QPDF_Stream::releaseResolved()
120 133 {
121 134 this->stream_provider = 0;
... ...
libqpdf/qpdf/QPDF_Stream.hh
... ... @@ -27,6 +27,8 @@ class QPDF_Stream: public QPDFObject
27 27 virtual void setDescription(QPDF*, std::string const&);
28 28 QPDFObjectHandle getDict() const;
29 29 bool isDataModified() const;
  30 + void setFilterOnWrite(bool);
  31 + bool getFilterOnWrite() const;
30 32  
31 33 // Methods to help QPDF copy foreign streams
32 34 qpdf_offset_t getOffset() const;
... ... @@ -83,6 +85,7 @@ class QPDF_Stream: public QPDFObject
83 85 QPDF* qpdf;
84 86 int objid;
85 87 int generation;
  88 + bool filter_on_write;
86 89 QPDFObjectHandle stream_dict;
87 90 qpdf_offset_t offset;
88 91 size_t length;
... ...
qpdf/qpdf.testcov
... ... @@ -520,3 +520,4 @@ qpdf-c called qpdf_oh_get_generation 0
520 520 qpdf-c called qpdf_oh_unparse 0
521 521 qpdf-c called qpdf_oh_unparse_resolved 0
522 522 qpdf-c called qpdf_oh_unparse_binary 0
  523 +QPDFWriter getFilterOnWrite false 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -1181,6 +1181,19 @@ $td-&gt;runtest(&quot;check output&quot;,
1181 1181  
1182 1182 show_ntests();
1183 1183 # ----------
  1184 +$td->notify("--- Disable filter on write ---");
  1185 +$n_tests += 2;
  1186 +
  1187 +$td->runtest("no filter on write",
  1188 + {$td->COMMAND => "test_driver 70 filter-on-write.pdf"},
  1189 + {$td->STRING => "test 70 done\n", $td->EXIT_STATUS => 0},
  1190 + $td->NORMALIZE_NEWLINES);
  1191 +$td->runtest("check output",
  1192 + {$td->FILE => "a.pdf"},
  1193 + {$td->FILE => "filter-on-write-out.pdf"});
  1194 +
  1195 +show_ntests();
  1196 +# ----------
1184 1197 $td->notify("--- Invalid objects ---");
1185 1198 $n_tests += 2;
1186 1199  
... ... @@ -1197,7 +1210,7 @@ $td-&gt;runtest(&quot;object with zero offset&quot;,
1197 1210  
1198 1211 show_ntests();
1199 1212 # ----------
1200   -$td->notify("--- Error/output rediction ---");
  1213 +$td->notify("--- Error/output redirection ---");
1201 1214 $n_tests += 2;
1202 1215  
1203 1216 $td->runtest("error/output redirection to null",
... ...
qpdf/qtest/qpdf/filter-on-write-out.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/filter-on-write.pdf 0 → 100644
  1 +%PDF-1.3
  2 +%¿÷¢þ
  3 +1 0 obj
  4 +<< /Pages 2 0 R /Type /Catalog >>
  5 +endobj
  6 +2 0 obj
  7 +<< /Count 0 /Kids [ ] /Type /Pages >>
  8 +endobj
  9 +3 0 obj
  10 +<< /Filter /RunLengthDecode /Length 5 >>
  11 +stream
  12 +w¹w€endstream
  13 +endobj
  14 +4 0 obj
  15 +<< /Length 6 >>
  16 +stream
  17 +potatoendstream
  18 +endobj
  19 +5 0 obj
  20 +<< /Filter /RunLengthDecode /Length 5 >>
  21 +stream
  22 +w¹w€endstream
  23 +endobj
  24 +6 0 obj
  25 +<< /Length 5 >>
  26 +stream
  27 +saladendstream
  28 +endobj
  29 +xref
  30 +0 7
  31 +0000000000 65535 f
  32 +0000000015 00000 n
  33 +0000000064 00000 n
  34 +0000000117 00000 n
  35 +0000000195 00000 n
  36 +0000000249 00000 n
  37 +0000000327 00000 n
  38 +trailer << /Root 1 0 R /Size 7 /ID [<5ab7a0329a828e2f46377e16247bc367><5ab7a0329a828e2f46377e16247bc367>] /S1 3 0 R /S2 4 0 R /S3 5 0 R /S4 6 0 R >>
  39 +startxref
  40 +380
  41 +%%EOF
... ...
qpdf/test_driver.cc
... ... @@ -2219,6 +2219,16 @@ void runtest(int n, char const* filename1, char const* arg2)
2219 2219 w.write();
2220 2220 }
2221 2221 }
  2222 + else if (n == 70)
  2223 + {
  2224 + auto trailer = pdf.getTrailer();
  2225 + trailer.getKey("/S1").setFilterOnWrite(false);
  2226 + trailer.getKey("/S2").setFilterOnWrite(false);
  2227 + QPDFWriter w(pdf, "a.pdf");
  2228 + w.setStaticID(true);
  2229 + w.setDecodeLevel(qpdf_dl_specialized);
  2230 + w.write();
  2231 + }
2222 2232 else
2223 2233 {
2224 2234 throw std::runtime_error(std::string("invalid test ") +
... ...