Add functional versions of QPDFObjectHandle::replaceStreamData

Also fix a bug in checking consistency of length for stream data
providers. Length should not be checked or recorded if the provider
says it failed to generate the data.
This commit is contained in:
Jay Berkenbilt 2021-02-14 14:04:40 -05:00
parent e2593e2efe
commit efbb21673c
9 changed files with 298 additions and 3 deletions

View File

@ -1,3 +1,11 @@
2021-02-14 Jay Berkenbilt <ejb@ql.org>
* Add new versions of QPDFObjectHandle::replaceStreamData that
take std::function objects for cases when you need something
between a static string and a full-fledged StreamDataProvider.
Using this with QUtil::file_provider is a very easy way to create
a stream from the contents of a file.
2021-02-12 Jay Berkenbilt <ejb@ql.org>
* Move formerly internal QPDFMatrix class to the public API. This

View File

@ -30,6 +30,7 @@
#include <vector>
#include <set>
#include <map>
#include <functional>
#include <qpdf/QPDFObjGen.hh>
#include <qpdf/PointerHolder.hh>
@ -986,6 +987,26 @@ class QPDFObjectHandle
QPDFObjectHandle const& filter,
QPDFObjectHandle const& decode_parms);
// Starting in qpdf 10.2, you can use C++-11 function objects
// instead of StreamDataProvider.
// The provider should write the stream data to the pipeline. For
// a one-liner to replace stream data with the contents of a file,
// pass QUtil::file_provider(filename) as provider.
QPDF_DLL
void replaceStreamData(std::function<void(Pipeline*)> provider,
QPDFObjectHandle const& filter,
QPDFObjectHandle const& decode_parms);
// The provider should write the stream data to the pipeline,
// returning true if it succeeded without errors.
QPDF_DLL
void replaceStreamData(
std::function<bool(Pipeline*,
bool suppress_warnings,
bool will_retry)> provider,
QPDFObjectHandle const& filter,
QPDFObjectHandle const& decode_parms);
// Access object ID and generation. For direct objects, return
// object ID 0.

View File

@ -1347,6 +1347,62 @@ QPDFObjectHandle::replaceStreamData(PointerHolder<StreamDataProvider> provider,
provider, filter, decode_parms);
}
class FunctionProvider: public QPDFObjectHandle::StreamDataProvider
{
public:
FunctionProvider(std::function<void(Pipeline*)> provider) :
StreamDataProvider(false),
p1(provider),
p2(nullptr)
{
}
FunctionProvider(std::function<bool(Pipeline*, bool, bool)> provider) :
StreamDataProvider(true),
p1(nullptr),
p2(provider)
{
}
virtual void provideStreamData(int, int, Pipeline* pipeline) override
{
p1(pipeline);
}
virtual bool provideStreamData(int, int, Pipeline* pipeline,
bool suppress_warnings,
bool will_retry) override
{
return p2(pipeline, suppress_warnings, will_retry);
}
private:
std::function<void(Pipeline*)> p1;
std::function<bool(Pipeline*, bool, bool)> p2;
};
void
QPDFObjectHandle::replaceStreamData(std::function<void(Pipeline*)> provider,
QPDFObjectHandle const& filter,
QPDFObjectHandle const& decode_parms)
{
assertStream();
PointerHolder<StreamDataProvider> sdp = new FunctionProvider(provider);
dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
sdp, filter, decode_parms);
}
void
QPDFObjectHandle::replaceStreamData(
std::function<bool(Pipeline*, bool, bool)> provider,
QPDFObjectHandle const& filter,
QPDFObjectHandle const& decode_parms)
{
assertStream();
PointerHolder<StreamDataProvider> sdp = new FunctionProvider(provider);
dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
sdp, filter, decode_parms);
}
QPDFObjGen
QPDFObjectHandle::getObjGen() const
{

View File

@ -533,7 +533,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
}
qpdf_offset_t actual_length = count.getCount();
qpdf_offset_t desired_length = 0;
if (this->stream_dict.hasKey("/Length"))
if (success && this->stream_dict.hasKey("/Length"))
{
desired_length = this->stream_dict.getKey("/Length").getIntValue();
if (actual_length == desired_length)
@ -555,7 +555,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
QUtil::int_to_string(desired_length) + " bytes");
}
}
else
else if (success)
{
QTC::TC("qpdf", "QPDF_Stream provider length not provided");
this->stream_dict.replaceKey(

View File

@ -5211,6 +5211,17 @@ print "\n";
contents of a file through a pipeline as binary data.
</para>
</listitem>
<listitem>
<para>
Add new versions of
<function>QPDFObjectHandle::replaceStreamData</function>
that take <classname>std::function</classname> objects for
cases when you need something between a static string and a
full-fledged StreamDataProvider. Using this with
QUtil::file_provider is a very easy way to create a stream
from the contents of a file.
</para>
</listitem>
<listitem>
<para>
Add option to <function>QUtil::double_to_string</function>

View File

@ -717,7 +717,7 @@ $td->runtest("check dates",
show_ntests();
# ----------
$td->notify("--- Stream Replacement Tests ---");
$n_tests += 8;
$n_tests += 10;
$td->runtest("replace stream data",
{$td->COMMAND => "test_driver 7 qstream.pdf"},
@ -747,6 +747,13 @@ $td->runtest("add page contents",
$td->runtest("new stream",
{$td->FILE => "a.pdf"},
{$td->FILE => "add-contents.pdf"});
$td->runtest("functional replace stream data",
{$td->COMMAND => "test_driver 78 minimal.pdf"},
{$td->FILE => "test78.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "test78.pdf"});
show_ntests();
# ----------

View File

@ -0,0 +1,10 @@
piping with warning suppression
f2
f2 done
writing
f2
failing
f2
warning
f2 done
test 78 done

139
qpdf/qtest/qpdf/test78.pdf Normal file
View File

@ -0,0 +1,139 @@
%PDF-1.3
%¿÷¢þ
%QDF-1.0
%% Original object ID: 1 0
1 0 obj
<<
/Pages 6 0 R
/Type /Catalog
>>
endobj
%% Original object ID: 7 0
2 0 obj
<<
/Length 3 0 R
>>
stream
potato
endstream
endobj
%QDF: ignore_newline
3 0 obj
6
endobj
%% Original object ID: 8 0
4 0 obj
<<
/Length 5 0 R
>>
stream
salad
endstream
endobj
%QDF: ignore_newline
5 0 obj
5
endobj
%% Original object ID: 2 0
6 0 obj
<<
/Count 1
/Kids [
7 0 R
]
/Type /Pages
>>
endobj
%% Page 1
%% Original object ID: 3 0
7 0 obj
<<
/Contents 8 0 R
/MediaBox [
0
0
612
792
]
/Parent 6 0 R
/Resources <<
/Font <<
/F1 10 0 R
>>
/ProcSet 11 0 R
>>
/Type /Page
>>
endobj
%% Contents for page 1
%% Original object ID: 4 0
8 0 obj
<<
/Length 9 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
9 0 obj
44
endobj
%% Original object ID: 6 0
10 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
%% Original object ID: 5 0
11 0 obj
[
/PDF
/Text
]
endobj
xref
0 12
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
0000000216 00000 n
0000000261 00000 n
0000000343 00000 n
0000000388 00000 n
0000000497 00000 n
0000000741 00000 n
0000000840 00000 n
0000000886 00000 n
0000001032 00000 n
trailer <<
/Root 1 0 R
/Size 12
/Streams [
2 0 R
4 0 R
]
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
1068
%%EOF

View File

@ -16,6 +16,7 @@
#include <qpdf/Pl_StdioFile.hh>
#include <qpdf/Pl_Buffer.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_Discard.hh>
#include <qpdf/QPDFWriter.hh>
#include <qpdf/QPDFSystemError.hh>
#include <qpdf/QIntC.hh>
@ -2798,6 +2799,48 @@ void runtest(int n, char const* filename1, char const* arg2)
w.setQDFMode(true);
w.write();
}
else if (n == 78)
{
// Test functional versions of replaceStreamData()
auto f1 = [](Pipeline* p) {
p->write(QUtil::unsigned_char_pointer("potato"), 6);
p->finish();
};
auto f2 = [](Pipeline* p, bool suppress_warnings, bool will_retry) {
std::cerr << "f2" << std::endl;
if (will_retry)
{
std::cerr << "failing" << std::endl;
return false;
}
if (! suppress_warnings)
{
std::cerr << "warning" << std::endl;
}
p->write(QUtil::unsigned_char_pointer("salad"), 5);
p->finish();
std::cerr << "f2 done" << std::endl;
return true;
};
auto null = QPDFObjectHandle::newNull();
auto s1 = QPDFObjectHandle::newStream(&pdf);
s1.replaceStreamData(f1, null, null);
auto s2 = QPDFObjectHandle::newStream(&pdf);
s2.replaceStreamData(f2, null, null);
pdf.getTrailer().replaceKey(
"/Streams", QPDFObjectHandle::newArray({s1, s2}));
std::cout << "piping with warning suppression" << std::endl;
Pl_Discard d;
s2.pipeStreamData(&d, nullptr, 0, qpdf_dl_all, true, false);
std::cout << "writing" << std::endl;
QPDFWriter w(pdf, "a.pdf");
w.setStaticID(true);
w.setQDFMode(true);
w.write();
}
else
{
throw std::runtime_error(std::string("invalid test ") +