Commit 4237a29c9492653e1be869d603e1b5bf87833682

Authored by Jay Berkenbilt
1 parent e57c2581

Refactor Dictionary writing code

Original code was written before we could shallow copy objects, so all
the filtering was done by suppressing the output of certain keys and
replacing them with other keys.  Now we can simplify the code greatly
by modifying shallow copies of dictionaries in place.
include/qpdf/QPDFWriter.hh
@@ -271,7 +271,6 @@ class QPDFWriter @@ -271,7 +271,6 @@ class QPDFWriter
271 static int const f_stream = 1 << 0; 271 static int const f_stream = 1 << 0;
272 static int const f_filtered = 1 << 1; 272 static int const f_filtered = 1 << 1;
273 static int const f_in_ostream = 1 << 2; 273 static int const f_in_ostream = 1 << 2;
274 - static int const f_in_extensions = 1 << 3;  
275 274
276 enum trailer_e { t_normal, t_lin_first, t_lin_second }; 275 enum trailer_e { t_normal, t_lin_first, t_lin_second };
277 276
libqpdf/QPDFWriter.cc
@@ -1135,7 +1135,8 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -1135,7 +1135,8 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1135 unsigned int flags, size_t stream_length, 1135 unsigned int flags, size_t stream_length,
1136 bool compress) 1136 bool compress)
1137 { 1137 {
1138 - unsigned int child_flags = flags & ~f_stream & ~f_in_extensions; 1138 + int old_id = object.getObjectID();
  1139 + unsigned int child_flags = flags & ~f_stream;
1139 1140
1140 std::string indent; 1141 std::string indent;
1141 for (int i = 0; i < level; ++i) 1142 for (int i = 0; i < level; ++i)
@@ -1167,10 +1168,19 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -1167,10 +1168,19 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1167 } 1168 }
1168 else if (object.isDictionary()) 1169 else if (object.isDictionary())
1169 { 1170 {
  1171 + // Make a shallow copy of this object so we can modify it
  1172 + // safely without affecting the original. This code makes
  1173 + // assumptions about things that are made true in
  1174 + // prepareFileForWrite, such as that certain things are direct
  1175 + // objects so that replacing them doesn't leave unreferenced
  1176 + // objects in the output.
  1177 + object = object.shallowCopy();
  1178 +
1170 // Handle special cases for specific dictionaries. 1179 // Handle special cases for specific dictionaries.
1171 1180
1172 - // Extensions dictionaries are complicated. We have one of  
1173 - // several cases: 1181 + // Extensions dictionaries.
  1182 +
  1183 + // We have one of several cases:
1174 // 1184 //
1175 // * We need ADBE 1185 // * We need ADBE
1176 // - We already have Extensions 1186 // - We already have Extensions
@@ -1183,16 +1193,16 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -1183,16 +1193,16 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1183 // - If it has other things, keep those and remove ADBE 1193 // - If it has other things, keep those and remove ADBE
1184 // - We have no extensions: no action required 1194 // - We have no extensions: no action required
1185 // 1195 //
1186 - // We may be in the root dictionary, or we may be inside the  
1187 - // extensions dictionary itself. The latter is determined by  
1188 - // the presence of the f_in_extensions flag. 1196 + // Before writing, we guarantee that /Extensions, if present,
  1197 + // is direct through the ADBE dictionary, so we can modify in
  1198 + // place.
1189 1199
1190 bool is_root = false; 1200 bool is_root = false;
1191 bool have_extensions_other = false; 1201 bool have_extensions_other = false;
1192 bool have_extensions_adbe = false; 1202 bool have_extensions_adbe = false;
1193 1203
1194 QPDFObjectHandle extensions; 1204 QPDFObjectHandle extensions;
1195 - if (object.getObjectID() == pdf.getRoot().getObjectID()) 1205 + if (old_id == pdf.getRoot().getObjectID())
1196 { 1206 {
1197 is_root = true; 1207 is_root = true;
1198 if (object.hasKey("/Extensions") && 1208 if (object.hasKey("/Extensions") &&
@@ -1201,10 +1211,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -1201,10 +1211,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1201 extensions = object.getKey("/Extensions"); 1211 extensions = object.getKey("/Extensions");
1202 } 1212 }
1203 } 1213 }
1204 - else if (flags & f_in_extensions)  
1205 - {  
1206 - extensions = object;  
1207 - } 1214 +
1208 if (extensions.isInitialized()) 1215 if (extensions.isInitialized())
1209 { 1216 {
1210 std::set<std::string> keys = extensions.getKeys(); 1217 std::set<std::string> keys = extensions.getKeys();
@@ -1221,10 +1228,6 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -1221,10 +1228,6 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1221 1228
1222 bool need_extensions_adbe = (this->final_extension_level > 0); 1229 bool need_extensions_adbe = (this->final_extension_level > 0);
1223 1230
1224 - bool write_new_extensions = false;  
1225 - bool write_new_adbe = false;  
1226 - bool suppress_existing_extensions = false;  
1227 - bool suppress_existing_adbe = false;  
1228 if (is_root) 1231 if (is_root)
1229 { 1232 {
1230 if (need_extensions_adbe) 1233 if (need_extensions_adbe)
@@ -1235,26 +1238,23 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -1235,26 +1238,23 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1235 // it here. 1238 // it here.
1236 QTC::TC("qpdf", "QPDFWriter create Extensions", 1239 QTC::TC("qpdf", "QPDFWriter create Extensions",
1237 this->qdf_mode ? 0 : 1); 1240 this->qdf_mode ? 0 : 1);
1238 - write_new_extensions = true;  
1239 - suppress_existing_extensions = true;  
1240 - }  
1241 - else  
1242 - {  
1243 - // Preserve existing Extensions and do the work  
1244 - // in the extensions dictionary. 1241 + extensions = QPDFObjectHandle::newDictionary();
  1242 + object.replaceKey("/Extensions", extensions);
1245 } 1243 }
1246 } 1244 }
1247 else if (! have_extensions_other) 1245 else if (! have_extensions_other)
1248 { 1246 {
1249 // We have Extensions dictionary and don't want one. 1247 // We have Extensions dictionary and don't want one.
1250 - suppress_existing_extensions = true;  
1251 if (have_extensions_adbe) 1248 if (have_extensions_adbe)
1252 { 1249 {
1253 QTC::TC("qpdf", "QPDFWriter remove existing Extensions"); 1250 QTC::TC("qpdf", "QPDFWriter remove existing Extensions");
  1251 + object.removeKey("/Extensions");
  1252 + extensions = QPDFObjectHandle(); // uninitialized
1254 } 1253 }
1255 } 1254 }
1256 } 1255 }
1257 - else if (flags & f_in_extensions) 1256 +
  1257 + if (extensions.isInitialized())
1258 { 1258 {
1259 QTC::TC("qpdf", "QPDFWriter preserve Extensions"); 1259 QTC::TC("qpdf", "QPDFWriter preserve Extensions");
1260 QPDFObjectHandle adbe = extensions.getKey("/ADBE"); 1260 QPDFObjectHandle adbe = extensions.getKey("/ADBE");
@@ -1272,77 +1272,54 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -1272,77 +1272,54 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1272 } 1272 }
1273 else 1273 else
1274 { 1274 {
1275 - suppress_existing_adbe = true;  
1276 if (need_extensions_adbe) 1275 if (need_extensions_adbe)
1277 { 1276 {
1278 - write_new_adbe = true; 1277 + extensions.replaceKey(
  1278 + "/ADBE",
  1279 + QPDFObjectHandle::parse(
  1280 + "<< /BaseVersion /" + this->final_pdf_version +
  1281 + " /ExtensionLevel " +
  1282 + QUtil::int_to_string(this->final_extension_level) +
  1283 + " >>"));
  1284 + }
  1285 + else
  1286 + {
  1287 + QTC::TC("qpdf", "QPDFWriter remove ADBE");
  1288 + extensions.removeKey("/ADBE");
1279 } 1289 }
1280 } 1290 }
1281 } 1291 }
1282 1292
1283 - writeString("<<");  
1284 - writeStringQDF("\n"); 1293 + // Stream dictionaries.
1285 1294
1286 - if (write_new_extensions || write_new_adbe) 1295 + if (flags & f_stream)
1287 { 1296 {
1288 - writeStringQDF(indent);  
1289 - writeStringQDF(" ");  
1290 - writeStringNoQDF(" ");  
1291 - if (write_new_extensions)  
1292 - {  
1293 - writeString("/Extensions << ");  
1294 - }  
1295 - writeString("/ADBE << /BaseVersion /");  
1296 - writeString(this->final_pdf_version);  
1297 - writeString(" /ExtensionLevel ");  
1298 - writeString(QUtil::int_to_string(this->final_extension_level));  
1299 - writeString(" >>");  
1300 - if (write_new_extensions) 1297 + // Suppress /Length since we will write it manually
  1298 + object.removeKey("/Length");
  1299 +
  1300 + // XXX BUG: /Crypt filters should always be removed.
  1301 + if (flags & f_filtered)
1301 { 1302 {
1302 - writeString(" >>"); 1303 + object.removeKey("/Filter");
  1304 + object.removeKey("/DecodeParms");
1303 } 1305 }
1304 - writeStringQDF("\n");  
1305 } 1306 }
1306 1307
  1308 + writeString("<<");
  1309 + writeStringQDF("\n");
  1310 +
1307 std::set<std::string> keys = object.getKeys(); 1311 std::set<std::string> keys = object.getKeys();
1308 for (std::set<std::string>::iterator iter = keys.begin(); 1312 for (std::set<std::string>::iterator iter = keys.begin();
1309 iter != keys.end(); ++iter) 1313 iter != keys.end(); ++iter)
1310 { 1314 {
1311 - // I'm not fully clear on /Crypt keys in /DecodeParms. If  
1312 - // one is found, we refuse to filter, so we should be  
1313 - // safe.  
1314 std::string const& key = *iter; 1315 std::string const& key = *iter;
1315 - if ((flags & f_filtered) &&  
1316 - ((key == "/Filter") ||  
1317 - (key == "/DecodeParms")))  
1318 - {  
1319 - continue;  
1320 - }  
1321 - if ((flags & f_stream) && (key == "/Length"))  
1322 - {  
1323 - continue;  
1324 - }  
1325 -  
1326 - bool is_extensions = (is_root && (key == "/Extensions"));  
1327 - if (suppress_existing_extensions && is_extensions)  
1328 - {  
1329 - QTC::TC("qpdf", "QPDFWriter skip Extensions");  
1330 - continue;  
1331 - }  
1332 - if (suppress_existing_adbe && (key == "/ADBE"))  
1333 - {  
1334 - QTC::TC("qpdf", "QPDFWriter skip ADBE");  
1335 - continue;  
1336 - }  
1337 -  
1338 1316
1339 writeStringQDF(indent); 1317 writeStringQDF(indent);
1340 writeStringQDF(" "); 1318 writeStringQDF(" ");
1341 writeStringNoQDF(" "); 1319 writeStringNoQDF(" ");
1342 writeString(QPDF_Name::normalizeName(key)); 1320 writeString(QPDF_Name::normalizeName(key));
1343 writeString(" "); 1321 writeString(" ");
1344 - unparseChild(object.getKey(key), level + 1,  
1345 - child_flags | (is_extensions ? f_in_extensions : 0)); 1322 + unparseChild(object.getKey(key), level + 1, child_flags);
1346 writeStringQDF("\n"); 1323 writeStringQDF("\n");
1347 } 1324 }
1348 1325
@@ -1379,7 +1356,6 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, @@ -1379,7 +1356,6 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
1379 else if (object.isStream()) 1356 else if (object.isStream())
1380 { 1357 {
1381 // Write stream data to a buffer. 1358 // Write stream data to a buffer.
1382 - int old_id = object.getObjectID();  
1383 int new_id = obj_renumber[old_id]; 1359 int new_id = obj_renumber[old_id];
1384 if (! this->direct_stream_lengths) 1360 if (! this->direct_stream_lengths)
1385 { 1361 {
qpdf/qpdf.testcov
@@ -249,9 +249,8 @@ QPDFWriter make Extensions direct 0 @@ -249,9 +249,8 @@ QPDFWriter make Extensions direct 0
249 QPDFWriter make ADBE direct 1 249 QPDFWriter make ADBE direct 1
250 QPDFWriter preserve Extensions 0 250 QPDFWriter preserve Extensions 0
251 QPDFWriter create Extensions 1 251 QPDFWriter create Extensions 1
252 -QPDFWriter skip ADBE 0 252 +QPDFWriter remove ADBE 0
253 QPDFWriter remove existing Extensions 0 253 QPDFWriter remove existing Extensions 0
254 -QPDFWriter skip Extensions 0  
255 QPDFWriter preserve ADBE 0 254 QPDFWriter preserve ADBE 0
256 QPDF_encryption skip 0x28 0 255 QPDF_encryption skip 0x28 0
257 QPDF_encrypt crypt array 0 256 QPDF_encrypt crypt array 0
qpdf/qtest/qpdf.test
@@ -134,7 +134,7 @@ foreach my $input (@ext_inputs) @@ -134,7 +134,7 @@ foreach my $input (@ext_inputs)
134 "qpdf --static-id" . 134 "qpdf --static-id" .
135 " --$op-version=$version $input a.pdf"}, 135 " --$op-version=$version $input a.pdf"},
136 {$td->STRING => "", $td->EXIT_STATUS => 0}); 136 {$td->STRING => "", $td->EXIT_STATUS => 0});
137 - $td->runtest("check version information", 137 + $td->runtest("check version information ($op $version)",
138 {$td->COMMAND => "test_driver 34 a.pdf"}, 138 {$td->COMMAND => "test_driver 34 a.pdf"},
139 {$td->FILE => "$base-$op-$version.out", 139 {$td->FILE => "$base-$op-$version.out",
140 $td->EXIT_STATUS => 0}, 140 $td->EXIT_STATUS => 0},
qpdf/qtest/qpdf/extensions-adbe-force-1.8.5.qdf
@@ -6,7 +6,10 @@ @@ -6,7 +6,10 @@
6 1 0 obj 6 1 0 obj
7 << 7 <<
8 /Extensions << 8 /Extensions <<
9 - /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> 9 + /ADBE <<
  10 + /BaseVersion /1.8
  11 + /ExtensionLevel 5
  12 + >>
10 >> 13 >>
11 /Pages 2 0 R 14 /Pages 2 0 R
12 /Type /Catalog 15 /Type /Catalog
@@ -88,17 +91,17 @@ xref @@ -88,17 +91,17 @@ xref
88 0 8 91 0 8
89 0000000000 65535 f 92 0000000000 65535 f
90 0000000052 00000 n 93 0000000052 00000 n
91 -0000000207 00000 n  
92 -0000000316 00000 n  
93 -0000000558 00000 n  
94 -0000000657 00000 n  
95 -0000000703 00000 n  
96 -0000000848 00000 n 94 +0000000223 00000 n
  95 +0000000332 00000 n
  96 +0000000574 00000 n
  97 +0000000673 00000 n
  98 +0000000719 00000 n
  99 +0000000864 00000 n
97 trailer << 100 trailer <<
98 /Root 1 0 R 101 /Root 1 0 R
99 /Size 8 102 /Size 8
100 /ID [<e42c124696c09bd2cacaf7196e9c88a0><31415926535897932384626433832795>] 103 /ID [<e42c124696c09bd2cacaf7196e9c88a0><31415926535897932384626433832795>]
101 >> 104 >>
102 startxref 105 startxref
103 -883 106 +899
104 %%EOF 107 %%EOF
qpdf/qtest/qpdf/extensions-adbe-other-force-1.8.5.qdf
@@ -6,7 +6,10 @@ @@ -6,7 +6,10 @@
6 1 0 obj 6 1 0 obj
7 << 7 <<
8 /Extensions << 8 /Extensions <<
9 - /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> 9 + /ADBE <<
  10 + /BaseVersion /1.8
  11 + /ExtensionLevel 5
  12 + >>
10 /Potato << 13 /Potato <<
11 /BaseVersion /3.14159 14 /BaseVersion /3.14159
12 /ExtensionLevel 16059 15 /ExtensionLevel 16059
@@ -92,17 +95,17 @@ xref @@ -92,17 +95,17 @@ xref
92 0 8 95 0 8
93 0000000000 65535 f 96 0000000000 65535 f
94 0000000052 00000 n 97 0000000052 00000 n
95 -0000000285 00000 n  
96 -0000000394 00000 n  
97 -0000000636 00000 n  
98 -0000000735 00000 n  
99 -0000000781 00000 n  
100 -0000000926 00000 n 98 +0000000301 00000 n
  99 +0000000410 00000 n
  100 +0000000652 00000 n
  101 +0000000751 00000 n
  102 +0000000797 00000 n
  103 +0000000942 00000 n
101 trailer << 104 trailer <<
102 /Root 1 0 R 105 /Root 1 0 R
103 /Size 8 106 /Size 8
104 /ID [<484577389048fa45fc00a1f5b434efa5><31415926535897932384626433832795>] 107 /ID [<484577389048fa45fc00a1f5b434efa5><31415926535897932384626433832795>]
105 >> 108 >>
106 startxref 109 startxref
107 -961 110 +977
108 %%EOF 111 %%EOF
qpdf/qtest/qpdf/extensions-none-force-1.8.5.qdf
@@ -5,7 +5,12 @@ @@ -5,7 +5,12 @@
5 %% Original object ID: 1 0 5 %% Original object ID: 1 0
6 1 0 obj 6 1 0 obj
7 << 7 <<
8 - /Extensions << /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> >> 8 + /Extensions <<
  9 + /ADBE <<
  10 + /BaseVersion /1.8
  11 + /ExtensionLevel 5
  12 + >>
  13 + >>
9 /Pages 2 0 R 14 /Pages 2 0 R
10 /Type /Catalog 15 /Type /Catalog
11 >> 16 >>
@@ -86,17 +91,17 @@ xref @@ -86,17 +91,17 @@ xref
86 0 8 91 0 8
87 0000000000 65535 f 92 0000000000 65535 f
88 0000000052 00000 n 93 0000000052 00000 n
89 -0000000201 00000 n  
90 -0000000310 00000 n  
91 -0000000552 00000 n  
92 -0000000651 00000 n  
93 -0000000697 00000 n  
94 -0000000842 00000 n 94 +0000000223 00000 n
  95 +0000000332 00000 n
  96 +0000000574 00000 n
  97 +0000000673 00000 n
  98 +0000000719 00000 n
  99 +0000000864 00000 n
95 trailer << 100 trailer <<
96 /Root 1 0 R 101 /Root 1 0 R
97 /Size 8 102 /Size 8
98 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] 103 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
99 >> 104 >>
100 startxref 105 startxref
101 -877 106 +899
102 %%EOF 107 %%EOF
qpdf/qtest/qpdf/extensions-other-force-1.8.5.qdf
@@ -6,7 +6,10 @@ @@ -6,7 +6,10 @@
6 1 0 obj 6 1 0 obj
7 << 7 <<
8 /Extensions << 8 /Extensions <<
9 - /ADBE << /BaseVersion /1.8 /ExtensionLevel 5 >> 9 + /ADBE <<
  10 + /BaseVersion /1.8
  11 + /ExtensionLevel 5
  12 + >>
10 /Potato << 13 /Potato <<
11 /BaseVersion /3.14159 14 /BaseVersion /3.14159
12 /ExtensionLevel 16059 15 /ExtensionLevel 16059
@@ -92,17 +95,17 @@ xref @@ -92,17 +95,17 @@ xref
92 0 8 95 0 8
93 0000000000 65535 f 96 0000000000 65535 f
94 0000000052 00000 n 97 0000000052 00000 n
95 -0000000285 00000 n  
96 -0000000394 00000 n  
97 -0000000636 00000 n  
98 -0000000735 00000 n  
99 -0000000781 00000 n  
100 -0000000926 00000 n 98 +0000000301 00000 n
  99 +0000000410 00000 n
  100 +0000000652 00000 n
  101 +0000000751 00000 n
  102 +0000000797 00000 n
  103 +0000000942 00000 n
101 trailer << 104 trailer <<
102 /Root 1 0 R 105 /Root 1 0 R
103 /Size 8 106 /Size 8
104 /ID [<369e89600ee1a6c4c7e73533610180c2><31415926535897932384626433832795>] 107 /ID [<369e89600ee1a6c4c7e73533610180c2><31415926535897932384626433832795>]
105 >> 108 >>
106 startxref 109 startxref
107 -961 110 +977
108 %%EOF 111 %%EOF