Add QPDFObjectHandle::setFilterOnWrite

This commit is contained in:
Jay Berkenbilt 2020-12-26 19:45:01 -05:00
parent 3f9191a344
commit 12ecd2019a
11 changed files with 131 additions and 5 deletions

View File

@ -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.)

View File

@ -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

View File

@ -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()
{

View File

@ -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");

View File

@ -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)
@ -115,6 +116,18 @@ QPDF_Stream::registerStreamFilter(
filter_factories[filter_name] = factory;
}
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()
{

View File

@ -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;

View File

@ -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

View File

@ -1179,6 +1179,19 @@ $td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "filter-abbreviation.out"});
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 ---");
@ -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",

Binary file not shown.

View File

@ -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
<EFBFBD>w¹w€endstream
endobj
4 0 obj
<< /Length 6 >>
stream
potatoendstream
endobj
5 0 obj
<< /Filter /RunLengthDecode /Length 5 >>
stream
<EFBFBD>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

View File

@ -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 ") +