2
1
mirror of https://github.com/qpdf/qpdf.git synced 2025-01-06 00:30:40 +00:00

Test --update-from-json

This commit is contained in:
Jay Berkenbilt 2022-05-20 11:10:12 -04:00
parent ef955b04b5
commit d065098089
17 changed files with 849 additions and 48 deletions

35
TODO
View File

@ -58,15 +58,10 @@ Some of this documentation has drifted from the actual implementation.
Make sure pages tree repair generates warnings. Make sure pages tree repair generates warnings.
* Reread from perspective of update
* Test all ignore cases with QTC
* Test case of correct file with dict before data/datafile
* Have a test case if possible that exercises the object description * Have a test case if possible that exercises the object description
which means we need some kind of semantic error that gets caught which means we need some kind of semantic error that gets caught
after creation. after creation.
* Test invalid data, invalid data file * Test invalid data, invalid data file
* Tests: round-trip through json, round-trip through qpdf --qdf
* Test to see if we get CR/NL on Windows, which is okay
Try to never flatten pages tree. Make sure we do something reasonable Try to never flatten pages tree. Make sure we do something reasonable
with pages tree repair. The problem is that if pages tree repair is with pages tree repair. The problem is that if pages tree repair is
@ -104,22 +99,20 @@ JSON to PDF:
Have --json-input and --update-from-json. With --json-input, the json Have --json-input and --update-from-json. With --json-input, the json
file must be complete, meaning all stream data, the trailer, and the file must be complete, meaning all stream data, the trailer, and the
PDF version must be present. In --update-from-json, an object PDF version must be present. For streams with no stream data, the
explicitly set to null (not "value": null) is deleted. For streams dictionary is updated but the data is left untouched. Other things
with no stream data, the dictionary is updated but the data is left that are omitted are left alone. Make sure document that, when writing
untouched. Other things that are omitted are left alone. Make sure a PDF file from QPDF, there is no expectation of object numbers being
document that, when writing a PDF file from QPDF, there is no preserved. As such, --update-from-json can only be used to update the
expectation of object numbers being preserved. As such, exact file that the json was created from. You can put multiple
--update-from-json can only be used to update the exact file that the objects in the update file, but you can't use a json from one file to
json was created from. You can put multiple objects in the update update the output of a previous update since the object numbers will
file, but you can't use a json from one file to update the output of a have changed. Note that, when creating from a JSON, object numbers are
previous update since the object numbers will have changed. Note that, preserved in the resulting QPDF object but still modified by
when creating from a JSON, object numbers are preserved in the QPDFWriter for the output. This would be visible by combining
resulting QPDF object but still modified by QPDFWriter for the output. --json-output and --json-input. Also using --qdf with
This would be visible by combining --json-output and --json-input. --create-from-json would show original object IDs in comments. It will
Also using --qdf with --create-from-json would show original object be important to capture this in the documentation.
IDs in comments. It will be important to capture this in the
documentation.
When reading a JSON string, any string that doesn't look like a name When reading a JSON string, any string that doesn't look like a name
or indirect object or start with "b:" or "u:" should be considered an or indirect object or start with "b:" or "u:" should be considered an

View File

@ -1065,6 +1065,7 @@ class QPDF
bool saw_dict; bool saw_dict;
bool saw_data; bool saw_data;
bool saw_datafile; bool saw_datafile;
bool this_stream_needs_data;
std::vector<state_e> state_stack; std::vector<state_e> state_stack;
std::vector<QPDFObjectHandle> object_stack; std::vector<QPDFObjectHandle> object_stack;
std::set<QPDFObjGen> reserved; std::set<QPDFObjGen> reserved;

View File

@ -102,7 +102,8 @@ QPDF::JSONReactor::JSONReactor(
saw_stream(false), saw_stream(false),
saw_dict(false), saw_dict(false),
saw_data(false), saw_data(false),
saw_datafile(false) saw_datafile(false),
this_stream_needs_data(false)
{ {
state_stack.push_back(st_initial); state_stack.push_back(st_initial);
} }
@ -111,8 +112,11 @@ void
QPDF::JSONReactor::error(size_t offset, std::string const& msg) QPDF::JSONReactor::error(size_t offset, std::string const& msg)
{ {
this->errors = true; this->errors = true;
this->pdf.warn( std::string object = this->cur_object;
qpdf_e_json, this->cur_object, QIntC::to_offset(offset), msg); if (is->getName() != pdf.getFilename()) {
object += " from " + is->getName();
}
this->pdf.warn(qpdf_e_json, object, QIntC::to_offset(offset), msg);
} }
bool bool
@ -196,20 +200,22 @@ QPDF::JSONReactor::containerEnd(JSON const& value)
QTC::TC("qpdf", "QPDF_json stream no dict"); QTC::TC("qpdf", "QPDF_json stream no dict");
error(value.getStart(), "\"stream\" is missing \"dict\""); error(value.getStart(), "\"stream\" is missing \"dict\"");
} }
if (must_be_complete) { if (saw_data == saw_datafile) {
if (saw_data == saw_datafile) { if (this_stream_needs_data) {
QTC::TC("qpdf", "QPDF_json data datafile both or neither"); QTC::TC("qpdf", "QPDF_json data datafile both or neither");
error( error(
value.getStart(), value.getStart(),
"\"stream\" must have exactly one of \"data\" or " "new \"stream\" must have exactly one of \"data\" or "
"\"datafile\""); "\"datafile\"");
} else if (saw_datafile) {
QTC::TC("qpdf", "QPDF_json data and datafile");
error(
value.getStart(),
"existing \"stream\" may at most one of \"data\" or "
"\"datafile\"");
} else {
QTC::TC("qpdf", "QPDF_json no stream data in update mode");
} }
} else if (saw_data && saw_datafile) {
// QXXXQ
/// QTC::TC("qpdf", "QPDF_json data and datafile");
error(
value.getStart(),
"\"stream\" may at most one of \"data\" or \"datafile\"");
} }
} }
} else if ((state == st_stream) || (state == st_object)) { } else if ((state == st_stream) || (state == st_object)) {
@ -320,7 +326,6 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
nestedState(key, value, st_trailer); nestedState(key, value, st_trailer);
this->cur_object = "trailer"; this->cur_object = "trailer";
} else if (std::regex_match(key, m, OBJ_KEY_RE)) { } else if (std::regex_match(key, m, OBJ_KEY_RE)) {
// QXXXQ remember to handle null for delete
object_stack.push_back(reserveObject(m[1].str(), m[2].str())); object_stack.push_back(reserveObject(m[1].str(), m[2].str()));
nestedState(key, value, st_object_top); nestedState(key, value, st_object_top);
this->cur_object = key; this->cur_object = key;
@ -347,9 +352,11 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
} else if (key == "stream") { } else if (key == "stream") {
this->saw_stream = true; this->saw_stream = true;
nestedState(key, value, st_stream); nestedState(key, value, st_stream);
this->this_stream_needs_data = false;
if (tos.isStream()) { if (tos.isStream()) {
// QXXXQ reusing -- need QTC QTC::TC("qpdf", "QPDF_json updating existing stream");
} else { } else {
this->this_stream_needs_data = true;
replacement = replacement =
pdf.reserveStream(tos.getObjectID(), tos.getGeneration()); pdf.reserveStream(tos.getObjectID(), tos.getGeneration());
replaceObject(tos, replacement); replaceObject(tos, replacement);
@ -386,12 +393,11 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
throw std::logic_error("no object on stack in st_stream"); throw std::logic_error("no object on stack in st_stream");
} }
auto tos = object_stack.back(); auto tos = object_stack.back();
auto uninitialized = QPDFObjectHandle();
if (!tos.isStream()) { if (!tos.isStream()) {
// QXXXQ QTC in update mode throw std::logic_error("top of stack is not stream in st_stream");
error(value.getStart(), "this object is not a stream"); }
parse_error = true; auto uninitialized = QPDFObjectHandle();
} else if (key == "dict") { if (key == "dict") {
this->saw_dict = true; this->saw_dict = true;
// Since a stream dictionary must be a dictionary, we can // Since a stream dictionary must be a dictionary, we can
// use nestedState to transition to st_value. // use nestedState to transition to st_value.
@ -509,11 +515,12 @@ QPDF::JSONReactor::makeObject(JSON const& value)
"JSONReactor::makeObject didn't initialize the object"); "JSONReactor::makeObject didn't initialize the object");
} }
// QXXXQ include object number in description std::string description = this->is->getName();
result.setObjectDescription( if (!this->cur_object.empty()) {
&this->pdf, description += " " + this->cur_object + ",";
this->is->getName() + " offset " + }
QUtil::uint_to_string(value.getStart())); description += " offset " + QUtil::uint_to_string(value.getStart());
result.setObjectDescription(&this->pdf, description);
return result; return result;
} }

View File

@ -676,3 +676,6 @@ QPDF_json ignore unknown key in object_top 0
QPDF_json ignore unknown key in trailer 0 QPDF_json ignore unknown key in trailer 0
QPDF_json ignore unknown key in stream 0 QPDF_json ignore unknown key in stream 0
QPDF_json non-trivial null reserved 0 QPDF_json non-trivial null reserved 0
QPDF_json data and datafile 0
QPDF_json no stream data in update mode 0
QPDF_json updating existing stream 0

View File

@ -171,5 +171,35 @@ $td->runtest("check PDF",
{$td->FILE => "a.pdf"}, {$td->FILE => "a.pdf"},
{$td->FILE => "b.pdf"}); {$td->FILE => "b.pdf"});
# Replace mode tests
$n_tests += 1;
$td->runtest("create PDF for replace",
{$td->COMMAND => "qpdf good13.pdf a.pdf" .
" --update-from-json=qpdf-json-update-errors.json"},
{$td->FILE => "update-from-json-errors.out",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
my @update_files = (
"update-stream-dict-only",
"update-stream-data",
"replace-with-stream",
"various-updates",
);
$n_tests += 2 * scalar(@update_files);
foreach my $f (@update_files) {
$td->runtest("update: $f",
{$td->COMMAND =>
"qpdf good13.pdf a.pdf --qdf --static-id" .
" --update-from-json=$f.json"},
{$td->STRING => "", $td->EXIT_STATUS => 0});
$td->runtest("$f: check updated",
{$td->FILE => "a.pdf"},
{$td->FILE => "$f-updated.pdf"});
}
cleanup(); cleanup();
$td->report($n_tests); $td->report($n_tests);

View File

@ -1,7 +1,7 @@
WARNING: qjson-obj-key-errors.json (obj:2 0 R, offset 218): object must have exactly one of "value" or "stream" WARNING: qjson-obj-key-errors.json (obj:2 0 R, offset 218): object must have exactly one of "value" or "stream"
WARNING: qjson-obj-key-errors.json (obj:3 0 R, offset 516): object must have exactly one of "value" or "stream" WARNING: qjson-obj-key-errors.json (obj:3 0 R, offset 516): object must have exactly one of "value" or "stream"
WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 684): "stream" is missing "dict" WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 684): "stream" is missing "dict"
WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 684): "stream" must have exactly one of "data" or "datafile" WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 684): new "stream" must have exactly one of "data" or "datafile"
WARNING: qjson-obj-key-errors.json (obj:5 0 R, offset 774): "stream" must have exactly one of "data" or "datafile" WARNING: qjson-obj-key-errors.json (obj:5 0 R, offset 774): new "stream" must have exactly one of "data" or "datafile"
WARNING: qjson-obj-key-errors.json (trailer, offset 1152): "trailer" is missing "value" WARNING: qjson-obj-key-errors.json (trailer, offset 1152): "trailer" is missing "value"
qpdf: qjson-obj-key-errors.json: errors found in JSON qpdf: qjson-obj-key-errors.json: errors found in JSON

View File

@ -1,5 +1,5 @@
WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 118): "stream.dict" must be a dictionary WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 118): "stream.dict" must be a dictionary
WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 118): unrecognized string value WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 118): unrecognized string value
WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 98): "stream" must have exactly one of "data" or "datafile" WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 98): new "stream" must have exactly one of "data" or "datafile"
WARNING: qjson-stream-dict-not-dict.json: "qpdf-v2.objects.trailer" was not seen WARNING: qjson-stream-dict-not-dict.json: "qpdf-v2.objects.trailer" was not seen
qpdf: qjson-stream-dict-not-dict.json: errors found in JSON qpdf: qjson-stream-dict-not-dict.json: errors found in JSON

View File

@ -0,0 +1,20 @@
{
"qpdf-v2": {
"objects": {
"obj:4 0 R": {
"stream": {
"data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
"datafile": "file-too",
"dict": {}
}
},
"obj:20 0 R": {
"stream": {
"dict": {
"/Bad": "string-value"
}
}
}
}
}
}

View File

@ -0,0 +1,182 @@
%PDF-2.0
%¿÷¢þ
%QDF-1.0
%% Original object ID: 1 0
1 0 obj
<<
/Pages 3 0 R
/Type /Catalog
>>
endobj
%% Original object ID: 7 0
2 0 obj
<<
/dangling-ref-for-json-test [
4 0 R
]
/hex#20strings [
(Potato)
<01020300040560>
(AB)
]
/indirect 5 0 R
/names [
/nesting
/hex#20strings
/text#2fplain
]
/nesting <<
/a [
1
2
<<
/x (y)
>>
[
(z)
]
]
/b <<
/ (legal)
/a [
1
2
]
>>
>>
/strings [
(one)
<24a2>
()
(\(\))
(\()
(\))
(a\f\b\t\r\nb)
(")
("")
("\("\)")
<410042>
(a\nb)
(a b)
<efbbbfcf80>
<efbbbff09fa594>
]
>>
endobj
%% Original object ID: 2 0
3 0 obj
<<
/Count 1
/Kids [
7 0 R
]
/Type /Pages
>>
endobj
%% Original object ID: 9 0
4 0 obj
null
endobj
%% Original object ID: 8 0
5 0 obj
<<
/K /V
/Length 6 0 R
>>
stream
new-stream-here
endstream
endobj
6 0 obj
16
endobj
%% Page 1
%% Original object ID: 3 0
7 0 obj
<<
/Contents 8 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 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
0000000756 00000 n
0000000855 00000 n
0000000903 00000 n
0000000982 00000 n
0000001038 00000 n
0000001282 00000 n
0000001381 00000 n
0000001427 00000 n
0000001573 00000 n
trailer <<
/QTest 2 0 R
/Root 1 0 R
/Size 12
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
1609
%%EOF

View File

@ -0,0 +1,16 @@
{
"qpdf-v2": {
"pdfversion": "2.0",
"maxobjectid": 9,
"objects": {
"obj:8 0 R": {
"stream": {
"data": "bmV3LXN0cmVhbS1oZXJlCg==",
"dict": {
"/K": "/V"
}
}
}
}
}
}

View File

@ -0,0 +1,4 @@
WARNING: good13.pdf (obj:4 0 R from qpdf-json-update-errors.json, offset 73): existing "stream" may at most one of "data" or "datafile"
WARNING: good13.pdf (obj:20 0 R from qpdf-json-update-errors.json, offset 313): unrecognized string value
WARNING: good13.pdf (obj:20 0 R from qpdf-json-update-errors.json, offset 271): new "stream" must have exactly one of "data" or "datafile"
qpdf: qpdf-json-update-errors.json: errors found in JSON

View File

@ -0,0 +1,171 @@
%PDF-2.0
%¿÷¢þ
%QDF-1.0
%% Original object ID: 1 0
1 0 obj
<<
/Pages 3 0 R
/Type /Catalog
>>
endobj
%% Original object ID: 7 0
2 0 obj
<<
/dangling-ref-for-json-test [
4 0 R
]
/hex#20strings [
(Potato)
<01020300040560>
(AB)
]
/indirect 5 0 R
/names [
/nesting
/hex#20strings
/text#2fplain
]
/nesting <<
/a [
1
2
<<
/x (y)
>>
[
(z)
]
]
/b <<
/ (legal)
/a [
1
2
]
>>
>>
/strings [
(one)
<24a2>
()
(\(\))
(\()
(\))
(a\f\b\t\r\nb)
(")
("")
("\("\)")
<410042>
(a\nb)
(a b)
<efbbbfcf80>
<efbbbff09fa594>
]
>>
endobj
%% Original object ID: 2 0
3 0 obj
<<
/Count 1
/Kids [
6 0 R
]
/Type /Pages
>>
endobj
%% Original object ID: 9 0
4 0 obj
null
endobj
%% Original object ID: 8 0
5 0 obj
(hello)
endobj
%% Page 1
%% Original object ID: 3 0
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
%% Original object ID: 4 0
7 0 obj
<<
/Length 8 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Salad) Tj
ET
endstream
endobj
8 0 obj
43
endobj
%% Original object ID: 6 0
9 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
%% Original object ID: 5 0
10 0 obj
[
/PDF
/Text
]
endobj
xref
0 11
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
0000000756 00000 n
0000000855 00000 n
0000000903 00000 n
0000000964 00000 n
0000001207 00000 n
0000001305 00000 n
0000001351 00000 n
0000001496 00000 n
trailer <<
/QTest 2 0 R
/Root 1 0 R
/Size 11
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
1532
%%EOF

View File

@ -0,0 +1,20 @@
{
"qpdf-v2": {
"pdfversion": "2.0",
"maxobjectid": 9,
"objects": {
"obj:1 0 R": {
"value": {
"/Pages": "2 0 R",
"/Type": "/Catalog"
}
},
"obj:4 0 R": {
"stream": {
"data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoU2FsYWQpIFRqCkVUCg==",
"dict": {}
}
}
}
}
}

View File

@ -0,0 +1,172 @@
%PDF-2.0
%¿÷¢þ
%QDF-1.0
%% Original object ID: 1 0
1 0 obj
<<
/Pages 3 0 R
/Type /Catalog
>>
endobj
%% Original object ID: 7 0
2 0 obj
<<
/dangling-ref-for-json-test [
4 0 R
]
/hex#20strings [
(Potato)
<01020300040560>
(AB)
]
/indirect 5 0 R
/names [
/nesting
/hex#20strings
/text#2fplain
]
/nesting <<
/a [
1
2
<<
/x (y)
>>
[
(z)
]
]
/b <<
/ (legal)
/a [
1
2
]
>>
>>
/strings [
(one)
<24a2>
()
(\(\))
(\()
(\))
(a\f\b\t\r\nb)
(")
("")
("\("\)")
<410042>
(a\nb)
(a b)
<efbbbfcf80>
<efbbbff09fa594>
]
>>
endobj
%% Original object ID: 2 0
3 0 obj
<<
/Count 1
/Kids [
6 0 R
]
/Type /Pages
>>
endobj
%% Original object ID: 9 0
4 0 obj
null
endobj
%% Original object ID: 8 0
5 0 obj
(hello)
endobj
%% Page 1
%% Original object ID: 3 0
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
%% Original object ID: 4 0
7 0 obj
<<
/Potato (salad)
/Length 8 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
8 0 obj
44
endobj
%% Original object ID: 6 0
9 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
%% Original object ID: 5 0
10 0 obj
[
/PDF
/Text
]
endobj
xref
0 11
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
0000000756 00000 n
0000000855 00000 n
0000000903 00000 n
0000000964 00000 n
0000001207 00000 n
0000001324 00000 n
0000001370 00000 n
0000001515 00000 n
trailer <<
/QTest 2 0 R
/Root 1 0 R
/Size 11
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
1551
%%EOF

View File

@ -0,0 +1,15 @@
{
"qpdf-v2": {
"pdfversion": "2.0",
"maxobjectid": 9,
"objects": {
"obj:4 0 R": {
"stream": {
"dict": {
"/Potato": "u:salad"
}
}
}
}
}
}

View File

@ -0,0 +1,128 @@
%PDF-2.0
%¿÷¢þ
%QDF-1.0
%% Original object ID: 1 0
1 0 obj
<<
/Pages 3 0 R
/Type /Catalog
>>
endobj
%% Original object ID: 7 0
2 0 obj
<<
/indirect 4 0 R
/now-#cf#80 [
5 0 R
]
/strings [
(one)
<feff03c0>
]
>>
endobj
%% Original object ID: 2 0
3 0 obj
<<
/Count 1
/Kids [
6 0 R
]
/Type /Pages
>>
endobj
%% Original object ID: 8 0
4 0 obj
[
(hello)
7 0 R
]
endobj
%% Original object ID: 9 0
5 0 obj
3.14159
endobj
%% Page 1
%% Original object ID: 3 0
6 0 obj
<<
/Contents 8 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 10 0 R
>>
>>
/Type /Page
>>
endobj
%% Original object ID: 10 0
7 0 obj
(this is new)
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
xref
0 11
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
0000000272 00000 n
0000000371 00000 n
0000000436 00000 n
0000000497 00000 n
0000000699 00000 n
0000000779 00000 n
0000000878 00000 n
0000000924 00000 n
trailer <<
/QTest 2 0 R
/Root 1 0 R
/Size 11
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
1043
%%EOF

View File

@ -0,0 +1,39 @@
{
"qpdf-v2": {
"pdfversion": "2.0",
"maxobjectid": 9,
"objects": {
"obj:5 0 R": {
"value": null
},
"obj:7 0 R": {
"value": {
"/now-π": [
"9 0 R"
],
"/indirect": "8 0 R",
"/strings": [
"u:one",
"u:π"
]
}
},
"obj:8 0 R": {
"value": ["u:hello", "10 0 R"]
},
"obj:9 0 R": {
"value": 3.14159
},
"obj:10 0 R": {
"value": "u:this is new"
},
"trailer": {
"value": {
"/QTest": "7 0 R",
"/Root": "1 0 R",
"/Size": 9
}
}
}
}
}