aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog8
-rw-r--r--include/qpdf/QPDFObjectHandle.hh16
-rw-r--r--libqpdf/QPDFObjectHandle.cc14
-rw-r--r--libqpdf/QPDFWriter.cc15
-rw-r--r--libqpdf/QPDF_Stream.cc13
-rw-r--r--libqpdf/qpdf/QPDF_Stream.hh3
-rw-r--r--qpdf/qpdf.testcov1
-rw-r--r--qpdf/qtest/qpdf.test15
-rw-r--r--qpdf/qtest/qpdf/filter-on-write-out.pdfbin0 -> 732 bytes
-rw-r--r--qpdf/qtest/qpdf/filter-on-write.pdf41
-rw-r--r--qpdf/test_driver.cc10
11 files changed, 131 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index c94c76e6..32f0fb34 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
2020-12-26 Jay Berkenbilt <ejb@ql.org>
+ * Add QPDFObjectHandle::setFilterOnWrite, which can be used to
+ tell QPDFWriter not to filter a stream on output even if it can.
+ You can use this to prevent QPDFWriter from touching a stream
+ (either uncompressing or compressing) that you have optimized or
+ otherwise ensured looks exactly the way you want it, even if
+ decode level or stream compression would otherwise cause
+ QPDFWriter to modify the stream.
+
* Add ostream << for QPDFObjGen. (Don't ask why it took 7.5 years
for me to decide to do this.)
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index 0cd10569..060d21e1 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -786,6 +786,22 @@ class QPDFObjectHandle
QPDF_DLL
QPDFObjectHandle getDict();
+ // By default, or if true passed, QPDFWriter will attempt to
+ // filter a stream based on decode level, whether compression is
+ // enabled, and its ability to filter. Passing false will prevent
+ // QPDFWriter from attempting to filter the stream even if it can.
+ // This includes both decoding and compressing. This makes it
+ // possible for you to prevent QPDFWriter from uncompressing and
+ // recompressing a stream that it knows how to operate on for any
+ // application-specific reason, such as that you have already
+ // optimized its filtering. Note that this doesn't affect any
+ // other ways to get the stream's data, such as pipeStreamData or
+ // getStreamData.
+ QPDF_DLL
+ void setFilterOnWrite(bool);
+ QPDF_DLL
+ bool getFilterOnWrite();
+
// If addTokenFilter has been called for this stream, then the
// original data should be considered to be modified. This means we
// should avoid optimizations such as not filtering a stream that
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index e09146e4..7b36fffe 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -1176,6 +1176,20 @@ QPDFObjectHandle::getDict()
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getDict();
}
+void
+QPDFObjectHandle::setFilterOnWrite(bool val)
+{
+ assertStream();
+ dynamic_cast<QPDF_Stream*>(obj.getPointer())->setFilterOnWrite(val);
+}
+
+bool
+QPDFObjectHandle::getFilterOnWrite()
+{
+ assertStream();
+ return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getFilterOnWrite();
+}
+
bool
QPDFObjectHandle::isDataModified()
{
diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
index 7057930b..689fef74 100644
--- a/libqpdf/QPDFWriter.cc
+++ b/libqpdf/QPDFWriter.cc
@@ -1470,6 +1470,7 @@ QPDFWriter::willFilterStream(QPDFObjectHandle stream,
{
compress_stream = false;
is_metadata = false;
+
QPDFObjGen old_og = stream.getObjGen();
QPDFObjectHandle stream_dict = stream.getDict();
@@ -1481,7 +1482,13 @@ QPDFWriter::willFilterStream(QPDFObjectHandle stream,
bool filter = (stream.isDataModified() ||
this->m->compress_streams ||
this->m->stream_decode_level);
- if (this->m->compress_streams)
+ bool filter_on_write = stream.getFilterOnWrite();
+ if (! filter_on_write)
+ {
+ QTC::TC("qpdf", "QPDFWriter getFilterOnWrite false");
+ filter = false;
+ }
+ if (filter_on_write && this->m->compress_streams)
{
// Don't filter if the stream is already compressed with
// FlateDecode. This way we don't make it worse if the
@@ -1502,7 +1509,7 @@ QPDFWriter::willFilterStream(QPDFObjectHandle stream,
}
bool normalize = false;
bool uncompress = false;
- if (is_metadata &&
+ if (filter_on_write && is_metadata &&
((! this->m->encrypted) || (this->m->encrypt_metadata == false)))
{
QTC::TC("qpdf", "QPDFWriter not compressing metadata");
@@ -1510,13 +1517,13 @@ QPDFWriter::willFilterStream(QPDFObjectHandle stream,
compress_stream = false;
uncompress = true;
}
- else if (this->m->normalize_content &&
+ else if (filter_on_write && this->m->normalize_content &&
this->m->normalized_streams.count(old_og))
{
normalize = true;
filter = true;
}
- else if (filter && this->m->compress_streams)
+ else if (filter_on_write && filter && this->m->compress_streams)
{
compress_stream = true;
QTC::TC("qpdf", "QPDFWriter compressing uncompressed stream");
diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc
index 8f9b4b52..05cde8d8 100644
--- a/libqpdf/QPDF_Stream.cc
+++ b/libqpdf/QPDF_Stream.cc
@@ -90,6 +90,7 @@ QPDF_Stream::QPDF_Stream(QPDF* qpdf, int objid, int generation,
qpdf(qpdf),
objid(objid),
generation(generation),
+ filter_on_write(true),
stream_dict(stream_dict),
offset(offset),
length(length)
@@ -116,6 +117,18 @@ QPDF_Stream::registerStreamFilter(
}
void
+QPDF_Stream::setFilterOnWrite(bool val)
+{
+ this->filter_on_write = val;
+}
+
+bool
+QPDF_Stream::getFilterOnWrite() const
+{
+ return this->filter_on_write;
+}
+
+void
QPDF_Stream::releaseResolved()
{
this->stream_provider = 0;
diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh
index b11de6a2..c4fdd379 100644
--- a/libqpdf/qpdf/QPDF_Stream.hh
+++ b/libqpdf/qpdf/QPDF_Stream.hh
@@ -27,6 +27,8 @@ class QPDF_Stream: public QPDFObject
virtual void setDescription(QPDF*, std::string const&);
QPDFObjectHandle getDict() const;
bool isDataModified() const;
+ void setFilterOnWrite(bool);
+ bool getFilterOnWrite() const;
// Methods to help QPDF copy foreign streams
qpdf_offset_t getOffset() const;
@@ -83,6 +85,7 @@ class QPDF_Stream: public QPDFObject
QPDF* qpdf;
int objid;
int generation;
+ bool filter_on_write;
QPDFObjectHandle stream_dict;
qpdf_offset_t offset;
size_t length;
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index 908f06e5..04e3e044 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -520,3 +520,4 @@ qpdf-c called qpdf_oh_get_generation 0
qpdf-c called qpdf_oh_unparse 0
qpdf-c called qpdf_oh_unparse_resolved 0
qpdf-c called qpdf_oh_unparse_binary 0
+QPDFWriter getFilterOnWrite false 0
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 6ae330a4..b166db84 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -1181,6 +1181,19 @@ $td->runtest("check output",
show_ntests();
# ----------
+$td->notify("--- Disable filter on write ---");
+$n_tests += 2;
+
+$td->runtest("no filter on write",
+ {$td->COMMAND => "test_driver 70 filter-on-write.pdf"},
+ {$td->STRING => "test 70 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "filter-on-write-out.pdf"});
+
+show_ntests();
+# ----------
$td->notify("--- Invalid objects ---");
$n_tests += 2;
@@ -1197,7 +1210,7 @@ $td->runtest("object with zero offset",
show_ntests();
# ----------
-$td->notify("--- Error/output rediction ---");
+$td->notify("--- Error/output redirection ---");
$n_tests += 2;
$td->runtest("error/output redirection to null",
diff --git a/qpdf/qtest/qpdf/filter-on-write-out.pdf b/qpdf/qtest/qpdf/filter-on-write-out.pdf
new file mode 100644
index 00000000..46cdbbc9
--- /dev/null
+++ b/qpdf/qtest/qpdf/filter-on-write-out.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/filter-on-write.pdf b/qpdf/qtest/qpdf/filter-on-write.pdf
new file mode 100644
index 00000000..1e35a3e8
--- /dev/null
+++ b/qpdf/qtest/qpdf/filter-on-write.pdf
@@ -0,0 +1,41 @@
+%PDF-1.3
+%¿÷¢þ
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+2 0 obj
+<< /Count 0 /Kids [ ] /Type /Pages >>
+endobj
+3 0 obj
+<< /Filter /RunLengthDecode /Length 5 >>
+stream
+w¹w€endstream
+endobj
+4 0 obj
+<< /Length 6 >>
+stream
+potatoendstream
+endobj
+5 0 obj
+<< /Filter /RunLengthDecode /Length 5 >>
+stream
+w¹w€endstream
+endobj
+6 0 obj
+<< /Length 5 >>
+stream
+saladendstream
+endobj
+xref
+0 7
+0000000000 65535 f
+0000000015 00000 n
+0000000064 00000 n
+0000000117 00000 n
+0000000195 00000 n
+0000000249 00000 n
+0000000327 00000 n
+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 >>
+startxref
+380
+%%EOF
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index 68519e09..b373e5bc 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -2219,6 +2219,16 @@ void runtest(int n, char const* filename1, char const* arg2)
w.write();
}
}
+ else if (n == 70)
+ {
+ auto trailer = pdf.getTrailer();
+ trailer.getKey("/S1").setFilterOnWrite(false);
+ trailer.getKey("/S2").setFilterOnWrite(false);
+ QPDFWriter w(pdf, "a.pdf");
+ w.setStaticID(true);
+ w.setDecodeLevel(qpdf_dl_specialized);
+ w.write();
+ }
else
{
throw std::runtime_error(std::string("invalid test ") +