QPDFObjectHandle::copyStream

This commit is contained in:
Jay Berkenbilt 2021-02-21 06:35:53 -05:00
parent 60afe4142e
commit 92fbc6fdf5
9 changed files with 349 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5200,6 +5200,13 @@ print "\n";
details.
</para>
</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>,

View File

@ -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();
# ----------

214
qpdf/qtest/qpdf/test79.pdf Normal file
View File

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

View File

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