Commit 12ecd2019a3186d11ebb6083b3813ce722fb2329
1 parent
3f9191a3
Add QPDFObjectHandle::setFilterOnWrite
Showing
11 changed files
with
131 additions
and
5 deletions
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
qpdf/qtest/qpdf.test
| ... | ... | @@ -1181,6 +1181,19 @@ $td->runtest("check output", |
| 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->runtest("object with zero offset", |
| 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 ") + | ... | ... |