2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-12-22 19:08:59 +00:00

Add QPDFObjectHandle::makeDirect(bool allow_streams)

This commit is contained in:
Jay Berkenbilt 2020-12-22 09:31:26 -05:00
parent 573b6eb8b1
commit cc8895078a
9 changed files with 390 additions and 13 deletions

View File

@ -1,3 +1,10 @@
2020-12-22 Jay Berkenbilt <ejb@ql.org>
* Add QPDFObjectHandle::makeDirect(bool allow_streams) -- if
allow_streams is true, preserve indirect references to streams
rather than throwing an exception. This allows the object to be
made as direct as possible while preserving stream references.
2020-12-20 Jay Berkenbilt <ejb@ql.org> 2020-12-20 Jay Berkenbilt <ejb@ql.org>
* Add qpdf_register_progress_reporter method to C API, * Add qpdf_register_progress_reporter method to C API,

2
TODO
View File

@ -155,6 +155,8 @@ ABI Changes
This is a list of changes to make next time there is an ABI change. This is a list of changes to make next time there is an ABI change.
Comments appear in the code prefixed by "ABI" Comments appear in the code prefixed by "ABI"
* Merge two versions of QPDFObjectHandle::makeDirect per comment
Page splitting/merging Page splitting/merging
====================== ======================

View File

@ -721,8 +721,20 @@ class QPDFObjectHandle
// Mutator methods. Use with caution. // Mutator methods. Use with caution.
// Recursively copy this object, making it direct. Throws an // Recursively copy this object, making it direct. An exception is
// exception if a loop is detected or any sub-object is a stream. // thrown if a loop is detected. With allow_streams true, keep
// indirect object references to streams. Otherwise, throw an
// exception if any sub-object is a stream. Note that, when
// allow_streams is true and a stream is found, the resulting
// object is still associated with the containing qpdf. When
// allow_streams is false, the object will no longer be connected
// to the original QPDF object after this call completes
// successfully.
QPDF_DLL
void makeDirect(bool allow_streams);
// Zero-arg version is equivalent to makeDirect(false).
// ABI: delete zero-arg version of makeDirect, and make
// allow_streams default to false.
QPDF_DLL QPDF_DLL
void makeDirect(); void makeDirect();
@ -1121,7 +1133,7 @@ class QPDFObjectHandle
void assertType(char const* type_name, bool istype); void assertType(char const* type_name, bool istype);
void dereference(); void dereference();
void copyObject(std::set<QPDFObjGen>& visited, bool cross_indirect, void copyObject(std::set<QPDFObjGen>& visited, bool cross_indirect,
bool first_level_only); bool first_level_only, bool stop_at_streams);
void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only); void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only);
void releaseResolved(); void releaseResolved();
static void setObjectDescriptionFromInput( static void setObjectDescriptionFromInput(

View File

@ -2605,18 +2605,24 @@ QPDFObjectHandle::shallowCopyInternal(QPDFObjectHandle& new_obj,
} }
std::set<QPDFObjGen> visited; std::set<QPDFObjGen> visited;
new_obj.copyObject(visited, false, first_level_only); new_obj.copyObject(visited, false, first_level_only, false);
} }
void void
QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited, QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited,
bool cross_indirect, bool first_level_only) bool cross_indirect, bool first_level_only,
bool stop_at_streams)
{ {
assertInitialized(); assertInitialized();
if (isStream()) if (isStream())
{ {
QTC::TC("qpdf", "QPDFObjectHandle ERR clone stream"); QTC::TC("qpdf", "QPDFObjectHandle copy stream",
stop_at_streams ? 0 : 1);
if (stop_at_streams)
{
return;
}
throw std::runtime_error( throw std::runtime_error(
"attempt to make a stream into a direct object"); "attempt to make a stream into a direct object");
} }
@ -2690,7 +2696,8 @@ QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited,
(cross_indirect || (! items.back().isIndirect()))) (cross_indirect || (! items.back().isIndirect())))
{ {
items.back().copyObject( items.back().copyObject(
visited, cross_indirect, first_level_only); visited, cross_indirect,
first_level_only, stop_at_streams);
} }
} }
new_obj = new QPDF_Array(items); new_obj = new QPDF_Array(items);
@ -2708,7 +2715,8 @@ QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited,
(cross_indirect || (! items[*iter].isIndirect()))) (cross_indirect || (! items[*iter].isIndirect())))
{ {
items[*iter].copyObject( items[*iter].copyObject(
visited, cross_indirect, first_level_only); visited, cross_indirect,
first_level_only, stop_at_streams);
} }
} }
new_obj = new QPDF_Dictionary(items); new_obj = new QPDF_Dictionary(items);
@ -2729,9 +2737,15 @@ QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited,
void void
QPDFObjectHandle::makeDirect() QPDFObjectHandle::makeDirect()
{
makeDirect(false);
}
void
QPDFObjectHandle::makeDirect(bool allow_streams)
{ {
std::set<QPDFObjGen> visited; std::set<QPDFObjGen> visited;
copyObject(visited, true, false); copyObject(visited, true, false, allow_streams);
} }
void void

View File

@ -79,7 +79,7 @@ QPDFObjectHandle clone string 0
QPDFObjectHandle clone array 0 QPDFObjectHandle clone array 0
QPDFObjectHandle clone dictionary 0 QPDFObjectHandle clone dictionary 0
QPDFObjectHandle makeDirect loop 0 QPDFObjectHandle makeDirect loop 0
QPDFObjectHandle ERR clone stream 0 QPDFObjectHandle copy stream 1
QPDF default for xref stream field 0 0 QPDF default for xref stream field 0 0
QPDF prev key in xref stream dictionary 0 QPDF prev key in xref stream dictionary 0
QPDF prev key in trailer dictionary 0 QPDF prev key in trailer dictionary 0

View File

@ -2963,7 +2963,7 @@ $td->runtest("check output",
show_ntests(); show_ntests();
# ---------- # ----------
$td->notify("--- Mutability Tests ---"); $td->notify("--- Mutability Tests ---");
$n_tests += 4; $n_tests += 5;
$td->runtest("no normalization", $td->runtest("no normalization",
{$td->COMMAND => "test_driver 4 test4-1.pdf"}, {$td->COMMAND => "test_driver 4 test4-1.pdf"},
@ -2975,13 +2975,18 @@ $td->runtest("object ordering",
{$td->FILE => "test4-4.qdf", {$td->FILE => "test4-4.qdf",
$td->EXIT_STATUS => 0}); $td->EXIT_STATUS => 0});
$td->runtest("loop detected", $td->runtest("make direct with allow_streams",
{$td->COMMAND => "test_driver 4 test4-5.pdf"},
{$td->FILE => "test4-5.qdf",
$td->EXIT_STATUS => 0});
$td->runtest("stream detected",
{$td->COMMAND => "test_driver 4 test4-2.pdf"}, {$td->COMMAND => "test_driver 4 test4-2.pdf"},
{$td->FILE => "test4-2.out", {$td->FILE => "test4-2.out",
$td->EXIT_STATUS => 2}, $td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES); $td->NORMALIZE_NEWLINES);
$td->runtest("stream detected", $td->runtest("loop detected",
{$td->COMMAND => "test_driver 4 test4-3.pdf"}, {$td->COMMAND => "test_driver 4 test4-3.pdf"},
{$td->FILE => "test4-3.out", {$td->FILE => "test4-3.out",
$td->EXIT_STATUS => 2}, $td->EXIT_STATUS => 2},

152
qpdf/qtest/qpdf/test4-5.pdf Normal file
View File

@ -0,0 +1,152 @@
%PDF-1.3
%¿÷¢þ
%QDF-1.0
1 0 obj
<<
/Pages 3 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/A 4 0 R
/B 5 0 R
/Subject (Subject)
/Title (Some Title Is Here)
>>
endobj
3 0 obj
<<
/Count 1
/Kids [
6 0 R
]
/Type /Pages
>>
endobj
4 0 obj
[
100
2
3
]
endobj
5 0 obj
<<
/A 4 0 R
/B (B)
>>
endobj
%% Page 1
6 0 obj
<<
/Contents 7 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 9 0 R
>>
/ProcSet 10 0 R
>>
/Type /Page
>>
endobj
%% Contents for page 1
7 0 obj
<<
/Length 8 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
8 0 obj
44
endobj
9 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
10 0 obj
[
/PDF
/Text
]
endobj
11 0 obj
<<
/A 12 0 R
/C (potato)
>>
endobj
12 0 obj
<< /B 13 0 R >>
endobj
13 0 obj
<<
/Length 14 0 R
>>
stream
salad
endstream
endobj
14 0 obj
6
endobj
xref
0 15
0000000000 65535 f
0000000025 00000 n
0000000079 00000 n
0000000174 00000 n
0000000246 00000 n
0000000280 00000 n
0000000332 00000 n
0000000548 00000 n
0000000647 00000 n
0000000666 00000 n
0000000784 00000 n
0000000820 00000 n
0000000869 00000 n
0000000902 00000 n
0000000965 00000 n
trailer <<
/QTest 2 0 R
/QTest2 11 0 R
/Root 1 0 R
/Size 15
/ID [<c61bd35bada064f61e0a56aa9588064e><c893e7330be149468080ad6518819868>]
>>
startxref
984
%%EOF

177
qpdf/qtest/qpdf/test4-5.qdf Normal file
View File

@ -0,0 +1,177 @@
%PDF-1.3
%¿÷¢þ
%QDF-1.0
%% Original object ID: 1 0
1 0 obj
<<
/Pages 6 0 R
/Type /Catalog
>>
endobj
%% Original object ID: 15 0
2 0 obj
<<
/A [
14
15
9
]
/Author (Mr. Potato Head)
/B <<
/A [
100
2
3
]
/B (B)
>>
/Title (Some Title Is Here)
>>
endobj
%% Original object ID: 2 0
3 0 obj
<<
/A 7 0 R
/B 8 0 R
/Subject (Subject)
/Title (Some Title Is Here)
>>
endobj
%% Original object ID: 13 0
4 0 obj
<<
/Length 5 0 R
>>
stream
salad
endstream
endobj
5 0 obj
6
endobj
%% Original object ID: 3 0
6 0 obj
<<
/Count 1
/Kids [
9 0 R
]
/Type /Pages
>>
endobj
%% Original object ID: 4 0
7 0 obj
[
100
2
3
]
endobj
%% Original object ID: 5 0
8 0 obj
<<
/A 7 0 R
/B (B)
>>
endobj
%% Page 1
%% Original object ID: 6 0
9 0 obj
<<
/Contents 10 0 R
/MediaBox [
0
0
612
792
]
/Parent 6 0 R
/Resources <<
/Font <<
/F1 12 0 R
>>
/ProcSet 13 0 R
>>
/Type /Page
>>
endobj
%% Contents for page 1
%% Original object ID: 7 0
10 0 obj
<<
/Length 11 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
11 0 obj
44
endobj
%% Original object ID: 9 0
12 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
%% Original object ID: 10 0
13 0 obj
[
/PDF
/Text
]
endobj
xref
0 14
0000000000 65535 f
0000000052 00000 n
0000000134 00000 n
0000000337 00000 n
0000000460 00000 n
0000000521 00000 n
0000000566 00000 n
0000000665 00000 n
0000000726 00000 n
0000000805 00000 n
0000001050 00000 n
0000001151 00000 n
0000001198 00000 n
0000001345 00000 n
trailer <<
/Info 2 0 R
/QTest 3 0 R
/QTest2 <<
/A <<
/B 4 0 R
>>
/C (potato)
>>
/Root 1 0 R
/Size 14
/ID [<c61bd35bada064f61e0a56aa9588064e><31415926535897932384626433832795>]
>>
startxref
1381
%%EOF

View File

@ -485,6 +485,14 @@ void runtest(int n, char const* filename1, char const* arg2)
A.setArrayFromVector(items); A.setArrayFromVector(items);
} }
QPDFObjectHandle qtest2 = trailer.getKey("/QTest2");
if (! qtest2.isNull())
{
// Test allow_streams=true
qtest2.makeDirect(true);
trailer.replaceKey("/QTest2", qtest2);
}
trailer.replaceKey("/Info", pdf.makeIndirectObject(qtest)); trailer.replaceKey("/Info", pdf.makeIndirectObject(qtest));
QPDFWriter w(pdf, 0); QPDFWriter w(pdf, 0);
w.setQDFMode(true); w.setQDFMode(true);