aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2021-02-21 12:35:53 +0100
committerJay Berkenbilt <ejb@ql.org>2021-02-21 12:36:30 +0100
commit92fbc6fdf56bad6aff8c9f3f1a3032d5ad36ec51 (patch)
tree583522ecd2468ef78421fb91a99d866098c12625
parent60afe4142e4399b12f21aced476df7ef719008b9 (diff)
downloadqpdf-92fbc6fdf56bad6aff8c9f3f1a3032d5ad36ec51.tar.zst
QPDFObjectHandle::copyStream
-rw-r--r--ChangeLog3
-rw-r--r--include/qpdf/QPDF.hh15
-rw-r--r--include/qpdf/QPDFObjectHandle.hh16
-rw-r--r--libqpdf/QPDF.cc4
-rw-r--r--libqpdf/QPDFObjectHandle.cc22
-rw-r--r--manual/qpdf-manual.xml7
-rw-r--r--qpdf/qtest/qpdf.test9
-rw-r--r--qpdf/qtest/qpdf/test79.pdf214
-rw-r--r--qpdf/test_driver.cc61
9 files changed, 349 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog
index 9621b3ff..2adee2ba 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
2021-02-21 Jay Berkenbilt <ejb@ql.org>
+ * Add QPDFObjectHandle::copyStream() for making a copy of a stream
+ within the same QPDF instance.
+
* Allow QPDFObjectHandle::newArray and
QPDFObjectHandle::newFromMatrix take QPDFMatrix as well as
QPDFObjectHandle::Matrix
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index ee00b23f..d4b7e775 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -700,6 +700,21 @@ class QPDF
};
friend class Resolver;
+ // StreamCopier class is restricted to QPDFObjectHandle so it can
+ // copy stream data.
+ class StreamCopier
+ {
+ friend class QPDFObjectHandle;
+ private:
+ static void copyStreamData(QPDF* qpdf,
+ QPDFObjectHandle const& dest,
+ QPDFObjectHandle const& src)
+ {
+ qpdf->copyStreamData(dest, src);
+ }
+ };
+ friend class Resolver;
+
// ParseGuard class allows QPDFObjectHandle to detect re-entrant
// resolution
class ParseGuard
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index bb877622..07c5b427 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -761,7 +761,8 @@ class QPDFObjectHandle
// the same place. In the strictest sense, this is not a shallow
// copy because it recursively descends arrays and dictionaries;
// it just doesn't cross over indirect objects. See also
- // unsafeShallowCopy().
+ // unsafeShallowCopy(). You can't copy a stream this way. See
+ // copyStream() instead.
QPDF_DLL
QPDFObjectHandle shallowCopy();
@@ -776,6 +777,19 @@ class QPDFObjectHandle
QPDF_DLL
QPDFObjectHandle unsafeShallowCopy();
+ // Create a copy of this stream. The new stream and the old stream
+ // are independent: after the copy, either the original or the
+ // copy's dictionary or data can be modified without affecting the
+ // other. This uses StreamDataProvider internally, so no
+ // unnecessary copies of the stream's data are made. If the source
+ // stream's data is already being provided by a
+ // StreamDataProvider, the new stream will use the same one, so
+ // you have to make sure your StreamDataProvider can handle that
+ // case. But if you're already using a StreamDataProvider, you
+ // probably don't need to call this method.
+ QPDF_DLL
+ QPDFObjectHandle copyStream();
+
// Mutator methods. Use with caution.
// Recursively copy this object, making it direct. An exception is
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index 5dfc9224..023e0469 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -2596,6 +2596,10 @@ QPDF::replaceForeignIndirectObjects(
void
QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign)
{
+ // This method was originally written for copying foreign streams,
+ // but it is used by QPDFObjectHandle to copy streams from the
+ // same QPDF object as well.
+
QPDFObjectHandle dict = result.getDict();
QPDFObjectHandle old_dict = foreign.getDict();
if (this->m->copied_stream_data_provider == 0)
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index ceb91630..f6ba3093 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -2877,6 +2877,28 @@ QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited,
}
}
+QPDFObjectHandle
+QPDFObjectHandle::copyStream()
+{
+ assertStream();
+ QPDFObjectHandle result = newStream(this->getOwningQPDF());
+ QPDFObjectHandle dict = result.getDict();
+ QPDFObjectHandle old_dict = getDict();
+ for (auto& iter: QPDFDictItems(old_dict))
+ {
+ if (iter.second.isIndirect())
+ {
+ dict.replaceKey(iter.first, iter.second);
+ }
+ else
+ {
+ dict.replaceKey(iter.first, iter.second.shallowCopy());
+ }
+ }
+ QPDF::StreamCopier::copyStreamData(getOwningQPDF(), result, *this);
+ return result;
+}
+
void
QPDFObjectHandle::makeDirect()
{
diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml
index bffd52cb..f7146221 100644
--- a/manual/qpdf-manual.xml
+++ b/manual/qpdf-manual.xml
@@ -5202,6 +5202,13 @@ print "\n";
</listitem>
<listitem>
<para>
+ Add <function>QPDFObjectHandle::copyStream</function> for
+ making a copy of a stream within the same
+ <classname>QPDF</classname> instance.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
Add <function>QUtil::get_current_qpdf_time</function>,
<function>QUtil::pdf_time_to_qpdf_time</function>, and
<function>QUtil::qpdf_time_to_pdf_time</function> for
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 90e7730f..100520f3 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -1549,7 +1549,7 @@ unlink "a.pdf" or die;
show_ntests();
# ----------
$td->notify("--- Object copying ---");
-$n_tests += 7;
+$n_tests += 9;
$td->runtest("shallow copy an array",
{$td->COMMAND => "test_driver 20 shallow_array.pdf"},
@@ -1578,6 +1578,13 @@ $td->runtest("detect foreign object in write",
" copy-foreign-objects-in.pdf minimal.pdf"},
{$td->FILE => "foreign-in-write.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+$td->runtest("copy a stream",
+ {$td->COMMAND => "test_driver 79 minimal.pdf"},
+ {$td->STRING => "test 79 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "test79.pdf"});
show_ntests();
# ----------
diff --git a/qpdf/qtest/qpdf/test79.pdf b/qpdf/qtest/qpdf/test79.pdf
new file mode 100644
index 00000000..2dbbc903
--- /dev/null
+++ b/qpdf/qtest/qpdf/test79.pdf
@@ -0,0 +1,214 @@
+%PDF-1.3
+%¿÷¢þ
+%QDF-1.0
+
+%% Original object ID: 1 0
+1 0 obj
+<<
+ /Pages 14 0 R
+ /Type /Catalog
+>>
+endobj
+
+%% Original object ID: 10 0
+2 0 obj
+<<
+ /Other (other: 1)
+ /Length 3 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+3 0 obj
+44
+endobj
+
+%% Original object ID: 11 0
+4 0 obj
+<<
+ /Other (other: 2)
+ /Stuff <<
+ /Direct 3
+ /Indirect 15 0 R
+ >>
+ /Length 5 0 R
+>>
+stream
+from string
+endstream
+endobj
+
+%QDF: ignore_newline
+5 0 obj
+11
+endobj
+
+%% Original object ID: 12 0
+6 0 obj
+<<
+ /Other (other: 3)
+ /Length 7 0 R
+>>
+stream
+from buffer
+endstream
+endobj
+
+%QDF: ignore_newline
+7 0 obj
+11
+endobj
+
+%% Contents for page 1
+%% Original object ID: 4 0
+8 0 obj
+<<
+ /Length 9 0 R
+>>
+stream
+something new 1
+endstream
+endobj
+
+%QDF: ignore_newline
+9 0 obj
+15
+endobj
+
+%% Original object ID: 7 0
+10 0 obj
+<<
+ /Other (other stuff)
+ /Stuff <<
+ /Direct 3
+ /Indirect 15 0 R
+ >>
+ /Length 11 0 R
+>>
+stream
+something new 2
+endstream
+endobj
+
+%QDF: ignore_newline
+11 0 obj
+15
+endobj
+
+%% Original object ID: 9 0
+12 0 obj
+<<
+ /Length 13 0 R
+>>
+stream
+something new 3
+endstream
+endobj
+
+%QDF: ignore_newline
+13 0 obj
+15
+endobj
+
+%% Original object ID: 2 0
+14 0 obj
+<<
+ /Count 1
+ /Kids [
+ 16 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Original object ID: 8 0
+15 0 obj
+16059
+endobj
+
+%% Page 1
+%% Original object ID: 3 0
+16 0 obj
+<<
+ /Contents 8 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 14 0 R
+ /Resources <<
+ /Font <<
+ /F1 17 0 R
+ >>
+ /ProcSet 18 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Original object ID: 6 0
+17 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+%% Original object ID: 5 0
+18 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 19
+0000000000 65535 f
+0000000052 00000 n
+0000000135 00000 n
+0000000254 00000 n
+0000000301 00000 n
+0000000461 00000 n
+0000000508 00000 n
+0000000616 00000 n
+0000000685 00000 n
+0000000777 00000 n
+0000000823 00000 n
+0000000992 00000 n
+0000001039 00000 n
+0000001133 00000 n
+0000001180 00000 n
+0000001281 00000 n
+0000001341 00000 n
+0000001564 00000 n
+0000001710 00000 n
+trailer <<
+ /Copies [
+ 2 0 R
+ 4 0 R
+ 6 0 R
+ ]
+ /Originals [
+ 8 0 R
+ 10 0 R
+ 12 0 R
+ ]
+ /Root 1 0 R
+ /Size 19
+ /ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
+>>
+startxref
+1746
+%%EOF
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index 2ad5bd62..356d9312 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -2844,6 +2844,67 @@ void runtest(int n, char const* filename1, char const* arg2)
w.setQDFMode(true);
w.write();
}
+ else if (n == 79)
+ {
+ // Exercise stream copier
+
+ // Copy streams. Modify the original and make sure the copy is
+ // unaffected.
+ auto copies = QPDFObjectHandle::newArray();
+ pdf.getTrailer().replaceKey("/Copies", copies);
+ auto null = QPDFObjectHandle::newNull();
+
+ // Get a regular stream from the file
+ auto p1 = pdf.getAllPages().at(0);
+ auto s1 = p1.getKey("/Contents");
+
+ // Create a stream from a string
+ auto s2 = QPDFObjectHandle::newStream(&pdf, "from string");
+ // Add direct and indirect objects to the dictionary
+ s2.getDict().replaceKey(
+ "/Stuff",
+ QPDFObjectHandle::parse(
+ &pdf,
+ "<< /Direct 3 /Indirect " +
+ pdf.makeIndirectObject(
+ QPDFObjectHandle::newInteger(16059)).unparse() + ">>"));
+ s2.getDict().replaceKey(
+ "/Other", QPDFObjectHandle::newString("other stuff"));
+
+ // Use a provider
+ Pl_Buffer b("buffer");
+ b.write(QUtil::unsigned_char_pointer("from buffer"), 11);
+ b.finish();
+ PointerHolder<Buffer> bp = b.getBuffer();
+ auto s3 = QPDFObjectHandle::newStream(&pdf, bp);
+
+ std::vector<QPDFObjectHandle> streams = {s1, s2, s3};
+ pdf.getTrailer().replaceKey(
+ "/Originals", QPDFObjectHandle::newArray(streams));
+
+ int i = 0;
+ for (auto orig: streams)
+ {
+ ++i;
+ auto istr = QUtil::int_to_string(i);
+ auto orig_data = orig.getStreamData();
+ auto copy = orig.copyStream();
+ copy.getDict().replaceKey(
+ "/Other", QPDFObjectHandle::newString("other: " + istr));
+ orig.replaceStreamData("something new " + istr, null, null);
+ auto copy_data = copy.getStreamData();
+ assert(orig_data->getSize() == copy_data->getSize());
+ assert(memcmp(orig_data->getBuffer(),
+ copy_data->getBuffer(),
+ orig_data->getSize()) == 0);
+ copies.appendItem(copy);
+ }
+
+ QPDFWriter w(pdf, "a.pdf");
+ w.setStaticID(true);
+ w.setQDFMode(true);
+ w.write();
+ }
else
{
throw std::runtime_error(std::string("invalid test ") +