aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2022-05-20 17:10:12 +0200
committerJay Berkenbilt <ejb@ql.org>2022-05-20 17:10:12 +0200
commitd06509808957305ed01497568ddd81a2590362f8 (patch)
tree7431d6a0a03ed81e8f52ff563b42b1ebba78b30a
parentef955b04b5cfd1042dc1751bb4c82d79aff89f46 (diff)
downloadqpdf-d06509808957305ed01497568ddd81a2590362f8.tar.zst
Test --update-from-json
-rw-r--r--TODO35
-rw-r--r--include/qpdf/QPDF.hh1
-rw-r--r--libqpdf/QPDF_json.cc55
-rw-r--r--qpdf/qpdf.testcov3
-rw-r--r--qpdf/qtest/qpdf-json.test30
-rw-r--r--qpdf/qtest/qpdf/qjson-obj-key-errors.out4
-rw-r--r--qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out2
-rw-r--r--qpdf/qtest/qpdf/qpdf-json-update-errors.json20
-rw-r--r--qpdf/qtest/qpdf/replace-with-stream-updated.pdf182
-rw-r--r--qpdf/qtest/qpdf/replace-with-stream.json16
-rw-r--r--qpdf/qtest/qpdf/update-from-json-errors.out4
-rw-r--r--qpdf/qtest/qpdf/update-stream-data-updated.pdf171
-rw-r--r--qpdf/qtest/qpdf/update-stream-data.json20
-rw-r--r--qpdf/qtest/qpdf/update-stream-dict-only-updated.pdf172
-rw-r--r--qpdf/qtest/qpdf/update-stream-dict-only.json15
-rw-r--r--qpdf/qtest/qpdf/various-updates-updated.pdf128
-rw-r--r--qpdf/qtest/qpdf/various-updates.json39
17 files changed, 849 insertions, 48 deletions
diff --git a/TODO b/TODO
index 8d96d31e..ea6cf42f 100644
--- a/TODO
+++ b/TODO
@@ -58,15 +58,10 @@ Some of this documentation has drifted from the actual implementation.
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
which means we need some kind of semantic error that gets caught
after creation.
* 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
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
file must be complete, meaning all stream data, the trailer, and the
-PDF version must be present. In --update-from-json, an object
-explicitly set to null (not "value": null) is deleted. For streams
-with no stream data, the dictionary is updated but the data is left
-untouched. Other things that are omitted are left alone. Make sure
-document that, when writing a PDF file from QPDF, there is no
-expectation of object numbers being preserved. As such,
---update-from-json can only be used to update the exact file that the
-json was created from. You can put multiple objects in the update
-file, but you can't use a json from one file to update the output of a
-previous update since the object numbers will have changed. Note that,
-when creating from a JSON, object numbers are preserved in the
-resulting QPDF object but still modified by QPDFWriter for the output.
-This would be visible by combining --json-output and --json-input.
-Also using --qdf with --create-from-json would show original object
-IDs in comments. It will be important to capture this in the
-documentation.
+PDF version must be present. For streams with no stream data, the
+dictionary is updated but the data is left untouched. Other things
+that are omitted are left alone. Make sure document that, when writing
+a PDF file from QPDF, there is no expectation of object numbers being
+preserved. As such, --update-from-json can only be used to update the
+exact file that the json was created from. You can put multiple
+objects in the update file, but you can't use a json from one file to
+update the output of a previous update since the object numbers will
+have changed. Note that, when creating from a JSON, object numbers are
+preserved in the resulting QPDF object but still modified by
+QPDFWriter for the output. This would be visible by combining
+--json-output and --json-input. Also using --qdf with
+--create-from-json would show original object 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
or indirect object or start with "b:" or "u:" should be considered an
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index fc515898..7d7af225 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -1065,6 +1065,7 @@ class QPDF
bool saw_dict;
bool saw_data;
bool saw_datafile;
+ bool this_stream_needs_data;
std::vector<state_e> state_stack;
std::vector<QPDFObjectHandle> object_stack;
std::set<QPDFObjGen> reserved;
diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc
index 18631114..af12459a 100644
--- a/libqpdf/QPDF_json.cc
+++ b/libqpdf/QPDF_json.cc
@@ -102,7 +102,8 @@ QPDF::JSONReactor::JSONReactor(
saw_stream(false),
saw_dict(false),
saw_data(false),
- saw_datafile(false)
+ saw_datafile(false),
+ this_stream_needs_data(false)
{
state_stack.push_back(st_initial);
}
@@ -111,8 +112,11 @@ void
QPDF::JSONReactor::error(size_t offset, std::string const& msg)
{
this->errors = true;
- this->pdf.warn(
- qpdf_e_json, this->cur_object, QIntC::to_offset(offset), msg);
+ std::string object = this->cur_object;
+ if (is->getName() != pdf.getFilename()) {
+ object += " from " + is->getName();
+ }
+ this->pdf.warn(qpdf_e_json, object, QIntC::to_offset(offset), msg);
}
bool
@@ -196,20 +200,22 @@ QPDF::JSONReactor::containerEnd(JSON const& value)
QTC::TC("qpdf", "QPDF_json stream no 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");
error(
value.getStart(),
- "\"stream\" must have exactly one of \"data\" or "
+ "new \"stream\" must have exactly one of \"data\" or "
+ "\"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)) {
@@ -320,7 +326,6 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
nestedState(key, value, st_trailer);
this->cur_object = "trailer";
} 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()));
nestedState(key, value, st_object_top);
this->cur_object = key;
@@ -347,9 +352,11 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
} else if (key == "stream") {
this->saw_stream = true;
nestedState(key, value, st_stream);
+ this->this_stream_needs_data = false;
if (tos.isStream()) {
- // QXXXQ reusing -- need QTC
+ QTC::TC("qpdf", "QPDF_json updating existing stream");
} else {
+ this->this_stream_needs_data = true;
replacement =
pdf.reserveStream(tos.getObjectID(), tos.getGeneration());
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");
}
auto tos = object_stack.back();
- auto uninitialized = QPDFObjectHandle();
if (!tos.isStream()) {
- // QXXXQ QTC in update mode
- error(value.getStart(), "this object is not a stream");
- parse_error = true;
- } else if (key == "dict") {
+ throw std::logic_error("top of stack is not stream in st_stream");
+ }
+ auto uninitialized = QPDFObjectHandle();
+ if (key == "dict") {
this->saw_dict = true;
// Since a stream dictionary must be a dictionary, we can
// use nestedState to transition to st_value.
@@ -509,11 +515,12 @@ QPDF::JSONReactor::makeObject(JSON const& value)
"JSONReactor::makeObject didn't initialize the object");
}
- // QXXXQ include object number in description
- result.setObjectDescription(
- &this->pdf,
- this->is->getName() + " offset " +
- QUtil::uint_to_string(value.getStart()));
+ std::string description = this->is->getName();
+ if (!this->cur_object.empty()) {
+ description += " " + this->cur_object + ",";
+ }
+ description += " offset " + QUtil::uint_to_string(value.getStart());
+ result.setObjectDescription(&this->pdf, description);
return result;
}
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index 65aa421a..93c658bb 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -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 stream 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
diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test
index 9e92e27d..dda056a1 100644
--- a/qpdf/qtest/qpdf-json.test
+++ b/qpdf/qtest/qpdf-json.test
@@ -171,5 +171,35 @@ $td->runtest("check PDF",
{$td->FILE => "a.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();
$td->report($n_tests);
diff --git a/qpdf/qtest/qpdf/qjson-obj-key-errors.out b/qpdf/qtest/qpdf/qjson-obj-key-errors.out
index 752c5321..e739a2f8 100644
--- a/qpdf/qtest/qpdf/qjson-obj-key-errors.out
+++ b/qpdf/qtest/qpdf/qjson-obj-key-errors.out
@@ -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: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" 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: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): new "stream" must have exactly one of "data" or "datafile"
WARNING: qjson-obj-key-errors.json (trailer, offset 1152): "trailer" is missing "value"
qpdf: qjson-obj-key-errors.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out
index 2a2c8de9..39b5207d 100644
--- a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out
+++ b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out
@@ -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): 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
qpdf: qjson-stream-dict-not-dict.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qpdf-json-update-errors.json b/qpdf/qtest/qpdf/qpdf-json-update-errors.json
new file mode 100644
index 00000000..4f699342
--- /dev/null
+++ b/qpdf/qtest/qpdf/qpdf-json-update-errors.json
@@ -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"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/qpdf/qtest/qpdf/replace-with-stream-updated.pdf b/qpdf/qtest/qpdf/replace-with-stream-updated.pdf
new file mode 100644
index 00000000..d8dc76a3
--- /dev/null
+++ b/qpdf/qtest/qpdf/replace-with-stream-updated.pdf
@@ -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
diff --git a/qpdf/qtest/qpdf/replace-with-stream.json b/qpdf/qtest/qpdf/replace-with-stream.json
new file mode 100644
index 00000000..7004c2a1
--- /dev/null
+++ b/qpdf/qtest/qpdf/replace-with-stream.json
@@ -0,0 +1,16 @@
+{
+ "qpdf-v2": {
+ "pdfversion": "2.0",
+ "maxobjectid": 9,
+ "objects": {
+ "obj:8 0 R": {
+ "stream": {
+ "data": "bmV3LXN0cmVhbS1oZXJlCg==",
+ "dict": {
+ "/K": "/V"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/qpdf/qtest/qpdf/update-from-json-errors.out b/qpdf/qtest/qpdf/update-from-json-errors.out
new file mode 100644
index 00000000..763579c3
--- /dev/null
+++ b/qpdf/qtest/qpdf/update-from-json-errors.out
@@ -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
diff --git a/qpdf/qtest/qpdf/update-stream-data-updated.pdf b/qpdf/qtest/qpdf/update-stream-data-updated.pdf
new file mode 100644
index 00000000..3f9ae74d
--- /dev/null
+++ b/qpdf/qtest/qpdf/update-stream-data-updated.pdf
@@ -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
diff --git a/qpdf/qtest/qpdf/update-stream-data.json b/qpdf/qtest/qpdf/update-stream-data.json
new file mode 100644
index 00000000..a650e93f
--- /dev/null
+++ b/qpdf/qtest/qpdf/update-stream-data.json
@@ -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": {}
+ }
+ }
+ }
+ }
+}
diff --git a/qpdf/qtest/qpdf/update-stream-dict-only-updated.pdf b/qpdf/qtest/qpdf/update-stream-dict-only-updated.pdf
new file mode 100644
index 00000000..f2bd079f
--- /dev/null
+++ b/qpdf/qtest/qpdf/update-stream-dict-only-updated.pdf
@@ -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
diff --git a/qpdf/qtest/qpdf/update-stream-dict-only.json b/qpdf/qtest/qpdf/update-stream-dict-only.json
new file mode 100644
index 00000000..bc0a17ce
--- /dev/null
+++ b/qpdf/qtest/qpdf/update-stream-dict-only.json
@@ -0,0 +1,15 @@
+{
+ "qpdf-v2": {
+ "pdfversion": "2.0",
+ "maxobjectid": 9,
+ "objects": {
+ "obj:4 0 R": {
+ "stream": {
+ "dict": {
+ "/Potato": "u:salad"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/qpdf/qtest/qpdf/various-updates-updated.pdf b/qpdf/qtest/qpdf/various-updates-updated.pdf
new file mode 100644
index 00000000..93ecb403
--- /dev/null
+++ b/qpdf/qtest/qpdf/various-updates-updated.pdf
@@ -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
diff --git a/qpdf/qtest/qpdf/various-updates.json b/qpdf/qtest/qpdf/various-updates.json
new file mode 100644
index 00000000..80e36465
--- /dev/null
+++ b/qpdf/qtest/qpdf/various-updates.json
@@ -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
+ }
+ }
+ }
+ }
+}