aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2021-02-14 20:04:40 +0100
committerJay Berkenbilt <ejb@ql.org>2021-02-14 20:42:24 +0100
commitefbb21673c59cfbf6a74de6866a59cb2dbb8e59f (patch)
tree37439ac43400705551107542c9e9939ee8504294
parente2593e2efe140d47870b0c511cbf5160db080edd (diff)
downloadqpdf-efbb21673c59cfbf6a74de6866a59cb2dbb8e59f.tar.zst
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.
-rw-r--r--ChangeLog8
-rw-r--r--include/qpdf/QPDFObjectHandle.hh21
-rw-r--r--libqpdf/QPDFObjectHandle.cc56
-rw-r--r--libqpdf/QPDF_Stream.cc4
-rw-r--r--manual/qpdf-manual.xml11
-rw-r--r--qpdf/qtest/qpdf.test9
-rw-r--r--qpdf/qtest/qpdf/test78.out10
-rw-r--r--qpdf/qtest/qpdf/test78.pdf139
-rw-r--r--qpdf/test_driver.cc43
9 files changed, 298 insertions, 3 deletions
diff --git a/ChangeLog b/ChangeLog
index b9f44077..7b1db1f2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index 41646b79..cb4c5be6 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -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.
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index 94875e13..d4796498 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -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
{
diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc
index b05137df..bc3b1b56 100644
--- a/libqpdf/QPDF_Stream.cc
+++ b/libqpdf/QPDF_Stream.cc
@@ -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(
diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml
index 58012e30..ae085d6e 100644
--- a/manual/qpdf-manual.xml
+++ b/manual/qpdf-manual.xml
@@ -5213,6 +5213,17 @@ print "\n";
</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>
to trim trailing zeroes, which is on by default. Within the
qpdf library, this causes changes to output the from code
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index dba10181..38a2d1b8 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -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();
# ----------
diff --git a/qpdf/qtest/qpdf/test78.out b/qpdf/qtest/qpdf/test78.out
new file mode 100644
index 00000000..d726dd37
--- /dev/null
+++ b/qpdf/qtest/qpdf/test78.out
@@ -0,0 +1,10 @@
+piping with warning suppression
+f2
+f2 done
+writing
+f2
+failing
+f2
+warning
+f2 done
+test 78 done
diff --git a/qpdf/qtest/qpdf/test78.pdf b/qpdf/qtest/qpdf/test78.pdf
new file mode 100644
index 00000000..d5d4ce4b
--- /dev/null
+++ b/qpdf/qtest/qpdf/test78.pdf
@@ -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
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index 28e07c5d..d7c9a352 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -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 ") +