Commit cf7b2b5700de735f6db6904d001db598bfb947af

Authored by Jay Berkenbilt
1 parent c1e2b64a

test_driver: split runtest into separate functions

Too bad about git annotate but it was pretty crazy to have all those
test cases together like that.
... ... @@ -3,8 +3,6 @@ Next
3 3  
4 4 * High-level API/doc overhaul: #593
5 5  
6   -* Refactor test_driver.cc so that runtest is not one huge function.
7   -
8 6 Documentation
9 7 =============
10 8  
... ...
qpdf/qtest/qpdf/form-minimal.out
1 1 no forms
  2 +test 43 done
... ...
qpdf/test_driver.cc
Changes suppressed. Click to show
... ... @@ -193,1697 +193,1649 @@ static void compare_numbers(
193 193 }
194 194 }
195 195  
196   -void runtest(int n, char const* filename1, char const* arg2)
  196 +static void test_0_1(QPDF& pdf, char const* arg2)
197 197 {
198   - // Most tests here are crafted to work on specific files. Look at
199   - // the test suite to see how the test is invoked to find the file
200   - // that the test is supposed to operate on.
  198 + QPDFObjectHandle trailer = pdf.getTrailer();
  199 + QPDFObjectHandle qtest = trailer.getKey("/QTest");
201 200  
202   - if (n == 0)
  201 + if (! trailer.hasKey("/QTest"))
203 202 {
204   - // Throw in some random test cases that don't fit anywhere
205   - // else. This is in addition to whatever else is going on in
206   - // test 0.
  203 + // This will always happen when /QTest is null because
  204 + // hasKey returns false for null keys regardless of
  205 + // whether the key exists or not. That way there's never
  206 + // any difference between a key that is present and null
  207 + // and a key that is absent.
  208 + QTC::TC("qpdf", "main QTest implicit");
  209 + std::cout << "/QTest is implicit" << std::endl;
  210 + }
207 211  
208   - // The code to trim user passwords looks for 0x28 (which is
209   - // "(") since it marks the beginning of the padding. Exercise
210   - // the code to make sure it skips over 0x28 characters that
211   - // aren't part of padding.
212   - std::string password(
213   - "1234567890123456789012(45678\x28\xbf\x4e\x5e");
214   - assert(password.length() == 32);
215   - QPDF::trim_user_password(password);
216   - assert(password == "1234567890123456789012(45678");
  212 + QTC::TC("qpdf", "main QTest indirect",
  213 + qtest.isIndirect() ? 1 : 0);
  214 + std::cout << "/QTest is "
  215 + << (qtest.isIndirect() ? "in" : "")
  216 + << "direct and has type "
  217 + << qtest.getTypeName()
  218 + << " (" << qtest.getTypeCode() << ")" << std::endl;
217 219  
218   - QPDFObjectHandle uninitialized;
219   - assert(uninitialized.getTypeCode() == QPDFObject::ot_uninitialized);
220   - assert(strcmp(uninitialized.getTypeName(), "uninitialized") == 0);
  220 + if (qtest.isNull())
  221 + {
  222 + QTC::TC("qpdf", "main QTest null");
  223 + std::cout << "/QTest is null" << std::endl;
221 224 }
222   -
223   - QPDF pdf;
224   - PointerHolder<char> file_buf;
225   - FILE* filep = 0;
226   - if (n == 0)
  225 + else if (qtest.isBool())
227 226 {
228   - pdf.setAttemptRecovery(false);
  227 + QTC::TC("qpdf", "main QTest bool",
  228 + qtest.getBoolValue() ? 1 : 0);
  229 + std::cout << "/QTest is Boolean with value "
  230 + << (qtest.getBoolValue() ? "true" : "false")
  231 + << std::endl;
229 232 }
230   - if (((n == 35) || (n == 36)) && (arg2 != 0))
  233 + else if (qtest.isInteger())
231 234 {
232   - // arg2 is password
233   - pdf.processFile(filename1, arg2);
  235 + QTC::TC("qpdf", "main QTest int");
  236 + std::cout << "/QTest is an integer with value "
  237 + << qtest.getIntValue() << std::endl;
234 238 }
235   - else if (n == 45)
  239 + else if (qtest.isReal())
236 240 {
237   - // Decode obfuscated files. To obfuscated, run the input file
238   - // through this perl script, and save the result to
239   - // filename.obfuscated. This pretends that the input was
240   - // called filename.pdf and that that file contained the
241   - // deobfuscated version.
242   -
243   - // undef $/;
244   - // my @str = split('', <STDIN>);
245   - // for (my $i = 0; $i < scalar(@str); ++$i)
246   - // {
247   - // $str[$i] = chr(ord($str[$i]) ^ 0xcc);
248   - // }
249   - // print(join('', @str));
250   -
251   - std::string filename(std::string(filename1) + ".obfuscated");
252   - size_t size = 0;
253   - QUtil::read_file_into_memory(filename.c_str(), file_buf, size);
254   - char* p = file_buf.getPointer();
255   - for (size_t i = 0; i < size; ++i)
  241 + QTC::TC("qpdf", "main QTest real");
  242 + std::cout << "/QTest is a real number with value "
  243 + << qtest.getRealValue() << std::endl;
  244 + }
  245 + else if (qtest.isName())
  246 + {
  247 + QTC::TC("qpdf", "main QTest name");
  248 + std::cout << "/QTest is a name with value "
  249 + << qtest.getName() << std::endl;
  250 + }
  251 + else if (qtest.isString())
  252 + {
  253 + QTC::TC("qpdf", "main QTest string");
  254 + std::cout << "/QTest is a string with value "
  255 + << qtest.getStringValue() << std::endl;
  256 + }
  257 + else if (qtest.isArray())
  258 + {
  259 + QTC::TC("qpdf", "main QTest array");
  260 + std::cout << "/QTest is an array with "
  261 + << qtest.getArrayNItems() << " items" << std::endl;
  262 + int i = 0;
  263 + for (auto& iter: qtest.aitems())
256 264 {
257   - p[i] = static_cast<char>(p[i] ^ 0xcc);
  265 + QTC::TC("qpdf", "main QTest array indirect",
  266 + iter.isIndirect() ? 1 : 0);
  267 + std::cout << " item " << i << " is "
  268 + << (iter.isIndirect() ? "in" : "")
  269 + << "direct" << std::endl;
  270 + ++i;
258 271 }
259   - pdf.processMemoryFile((std::string(filename1) + ".pdf").c_str(),
260   - p, size);
261 272 }
262   - else if ((n == 61) || (n == 81))
  273 + else if (qtest.isDictionary())
263 274 {
264   - // Ignore filename argument entirely
  275 + QTC::TC("qpdf", "main QTest dictionary");
  276 + std::cout << "/QTest is a dictionary" << std::endl;
  277 + for (auto& iter: qtest.ditems())
  278 + {
  279 + QTC::TC("qpdf", "main QTest dictionary indirect",
  280 + iter.second.isIndirect() ? 1 : 0);
  281 + std::cout << " " << iter.first << " is "
  282 + << (iter.second.isIndirect() ? "in" : "")
  283 + << "direct" << std::endl;
  284 + }
265 285 }
266   - else if (n % 2 == 0)
  286 + else if (qtest.isStream())
267 287 {
268   - if (n % 4 == 0)
  288 + QTC::TC("qpdf", "main QTest stream");
  289 + std::cout << "/QTest is a stream. Dictionary: "
  290 + << qtest.getDict().unparse() << std::endl;
  291 +
  292 + std::cout << "Raw stream data:" << std::endl;
  293 + std::cout.flush();
  294 + QUtil::binary_stdout();
  295 + PointerHolder<Pl_StdioFile> out = new Pl_StdioFile("raw", stdout);
  296 + qtest.pipeStreamData(out.getPointer(), 0, qpdf_dl_none);
  297 +
  298 + std::cout << std::endl << "Uncompressed stream data:" << std::endl;
  299 + if (qtest.pipeStreamData(0, 0, qpdf_dl_all))
269 300 {
270   - QTC::TC("qpdf", "exercise processFile(name)");
271   - pdf.processFile(filename1);
  301 + std::cout.flush();
  302 + QUtil::binary_stdout();
  303 + out = new Pl_StdioFile("filtered", stdout);
  304 + qtest.pipeStreamData(out.getPointer(), 0, qpdf_dl_all);
  305 + std::cout << std::endl << "End of stream data" << std::endl;
272 306 }
273 307 else
274 308 {
275   - QTC::TC("qpdf", "exercise processFile(FILE*)");
276   - filep = QUtil::safe_fopen(filename1, "rb");
277   - pdf.processFile(filename1, filep, false);
  309 + std::cout << "Stream data is not filterable." << std::endl;
278 310 }
279 311 }
280 312 else
281 313 {
282   - QTC::TC("qpdf", "exercise processMemoryFile");
283   - size_t size = 0;
284   - QUtil::read_file_into_memory(filename1, file_buf, size);
285   - pdf.processMemoryFile(filename1, file_buf.getPointer(), size);
  314 + // Should not happen!
  315 + std::cout << "/QTest is an unknown object" << std::endl;
286 316 }
287 317  
288   - if ((n == 0) || (n == 1))
289   - {
290   - QPDFObjectHandle trailer = pdf.getTrailer();
291   - QPDFObjectHandle qtest = trailer.getKey("/QTest");
292   -
293   - if (! trailer.hasKey("/QTest"))
294   - {
295   - // This will always happen when /QTest is null because
296   - // hasKey returns false for null keys regardless of
297   - // whether the key exists or not. That way there's never
298   - // any difference between a key that is present and null
299   - // and a key that is absent.
300   - QTC::TC("qpdf", "main QTest implicit");
301   - std::cout << "/QTest is implicit" << std::endl;
302   - }
  318 + std::cout << "unparse: " << qtest.unparse() << std::endl
  319 + << "unparseResolved: " << qtest.unparseResolved()
  320 + << std::endl;
  321 +}
303 322  
304   - QTC::TC("qpdf", "main QTest indirect",
305   - qtest.isIndirect() ? 1 : 0);
306   - std::cout << "/QTest is "
307   - << (qtest.isIndirect() ? "in" : "")
308   - << "direct and has type "
309   - << qtest.getTypeName()
310   - << " (" << qtest.getTypeCode() << ")" << std::endl;
  323 +static void test_2(QPDF& pdf, char const* arg2)
  324 +{
  325 + // Encrypted file. This test case is designed for a specific
  326 + // PDF file.
  327 +
  328 + QPDFObjectHandle trailer = pdf.getTrailer();
  329 + std::cout << trailer.getKey("/Info").
  330 + getKey("/CreationDate").getStringValue() << std::endl;
  331 + std::cout << trailer.getKey("/Info").
  332 + getKey("/Producer").getStringValue() << std::endl;
  333 +
  334 + QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
  335 + std::cout << encrypt.getKey("/O").unparse() << std::endl;
  336 + std::cout << encrypt.getKey("/U").unparse() << std::endl;
  337 +
  338 + QPDFObjectHandle root = pdf.getRoot();
  339 + QPDFObjectHandle pages = root.getKey("/Pages");
  340 + QPDFObjectHandle kids = pages.getKey("/Kids");
  341 + QPDFObjectHandle page = kids.getArrayItem(1); // second page
  342 + QPDFObjectHandle contents = page.getKey("/Contents");
  343 + QUtil::binary_stdout();
  344 + PointerHolder<Pl_StdioFile> out = new Pl_StdioFile("filtered", stdout);
  345 + contents.pipeStreamData(out.getPointer(), 0, qpdf_dl_generalized);
  346 +}
311 347  
312   - if (qtest.isNull())
313   - {
314   - QTC::TC("qpdf", "main QTest null");
315   - std::cout << "/QTest is null" << std::endl;
316   - }
317   - else if (qtest.isBool())
318   - {
319   - QTC::TC("qpdf", "main QTest bool",
320   - qtest.getBoolValue() ? 1 : 0);
321   - std::cout << "/QTest is Boolean with value "
322   - << (qtest.getBoolValue() ? "true" : "false")
323   - << std::endl;
324   - }
325   - else if (qtest.isInteger())
326   - {
327   - QTC::TC("qpdf", "main QTest int");
328   - std::cout << "/QTest is an integer with value "
329   - << qtest.getIntValue() << std::endl;
330   - }
331   - else if (qtest.isReal())
332   - {
333   - QTC::TC("qpdf", "main QTest real");
334   - std::cout << "/QTest is a real number with value "
335   - << qtest.getRealValue() << std::endl;
336   - }
337   - else if (qtest.isName())
338   - {
339   - QTC::TC("qpdf", "main QTest name");
340   - std::cout << "/QTest is a name with value "
341   - << qtest.getName() << std::endl;
342   - }
343   - else if (qtest.isString())
344   - {
345   - QTC::TC("qpdf", "main QTest string");
346   - std::cout << "/QTest is a string with value "
347   - << qtest.getStringValue() << std::endl;
348   - }
349   - else if (qtest.isArray())
350   - {
351   - QTC::TC("qpdf", "main QTest array");
352   - std::cout << "/QTest is an array with "
353   - << qtest.getArrayNItems() << " items" << std::endl;
354   - int i = 0;
355   - for (auto& iter: qtest.aitems())
356   - {
357   - QTC::TC("qpdf", "main QTest array indirect",
358   - iter.isIndirect() ? 1 : 0);
359   - std::cout << " item " << i << " is "
360   - << (iter.isIndirect() ? "in" : "")
361   - << "direct" << std::endl;
362   - ++i;
363   - }
364   - }
365   - else if (qtest.isDictionary())
366   - {
367   - QTC::TC("qpdf", "main QTest dictionary");
368   - std::cout << "/QTest is a dictionary" << std::endl;
369   - for (auto& iter: qtest.ditems())
370   - {
371   - QTC::TC("qpdf", "main QTest dictionary indirect",
372   - iter.second.isIndirect() ? 1 : 0);
373   - std::cout << " " << iter.first << " is "
374   - << (iter.second.isIndirect() ? "in" : "")
375   - << "direct" << std::endl;
376   - }
377   - }
378   - else if (qtest.isStream())
379   - {
380   - QTC::TC("qpdf", "main QTest stream");
381   - std::cout << "/QTest is a stream. Dictionary: "
382   - << qtest.getDict().unparse() << std::endl;
383   -
384   - std::cout << "Raw stream data:" << std::endl;
385   - std::cout.flush();
386   - QUtil::binary_stdout();
387   - PointerHolder<Pl_StdioFile> out = new Pl_StdioFile("raw", stdout);
388   - qtest.pipeStreamData(out.getPointer(), 0, qpdf_dl_none);
389   -
390   - std::cout << std::endl << "Uncompressed stream data:" << std::endl;
391   - if (qtest.pipeStreamData(0, 0, qpdf_dl_all))
392   - {
393   - std::cout.flush();
394   - QUtil::binary_stdout();
395   - out = new Pl_StdioFile("filtered", stdout);
396   - qtest.pipeStreamData(out.getPointer(), 0, qpdf_dl_all);
397   - std::cout << std::endl << "End of stream data" << std::endl;
398   - }
399   - else
400   - {
401   - std::cout << "Stream data is not filterable." << std::endl;
402   - }
403   - }
404   - else
405   - {
406   - // Should not happen!
407   - std::cout << "/QTest is an unknown object" << std::endl;
408   - }
  348 +static void test_3(QPDF& pdf, char const* arg2)
  349 +{
  350 + QPDFObjectHandle streams = pdf.getTrailer().getKey("/QStreams");
  351 + for (int i = 0; i < streams.getArrayNItems(); ++i)
  352 + {
  353 + QPDFObjectHandle stream = streams.getArrayItem(i);
  354 + std::cout << "-- stream " << i << " --" << std::endl;
  355 + std::cout.flush();
  356 + QUtil::binary_stdout();
  357 + PointerHolder<Pl_StdioFile> out =
  358 + new Pl_StdioFile("tokenized stream", stdout);
  359 + stream.pipeStreamData(out.getPointer(),
  360 + qpdf_ef_normalize, qpdf_dl_generalized);
  361 + }
  362 +}
409 363  
410   - std::cout << "unparse: " << qtest.unparse() << std::endl
411   - << "unparseResolved: " << qtest.unparseResolved()
412   - << std::endl;
  364 +static void test_4(QPDF& pdf, char const* arg2)
  365 +{
  366 + // Mutability testing: Make /QTest direct recursively, then
  367 + // copy to /Info. Also make some other mutations so we can
  368 + // tell the difference and ensure that the original /QTest
  369 + // isn't effected.
  370 + QPDFObjectHandle trailer = pdf.getTrailer();
  371 + QPDFObjectHandle qtest = trailer.getKey("/QTest");
  372 + qtest.makeDirect();
  373 + qtest.removeKey("/Subject");
  374 + qtest.replaceKey("/Author",
  375 + QPDFObjectHandle::newString("Mr. Potato Head"));
  376 + // qtest.A and qtest.B.A were originally the same object.
  377 + // They no longer are after makeDirect(). Mutate one of them
  378 + // and ensure the other is not changed. These test cases are
  379 + // crafted around a specific set of input files.
  380 + QPDFObjectHandle A = qtest.getKey("/A");
  381 + if (A.getArrayItem(0).getIntValue() == 1)
  382 + {
  383 + // Test mutators
  384 + A.setArrayItem(1, QPDFObjectHandle::newInteger(5)); // 1 5 3
  385 + A.insertItem(2, QPDFObjectHandle::newInteger(10)); // 1 5 10 3
  386 + A.appendItem(QPDFObjectHandle::newInteger(12)); // 1 5 10 3 12
  387 + A.eraseItem(3); // 1 5 10 12
  388 + A.insertItem(4, QPDFObjectHandle::newInteger(6)); // 1 5 10 12 6
  389 + A.insertItem(0, QPDFObjectHandle::newInteger(9)); // 9 1 5 10 12 6
413 390 }
414   - else if (n == 2)
  391 + else
415 392 {
416   - // Encrypted file. This test case is designed for a specific
417   - // PDF file.
418   -
419   - QPDFObjectHandle trailer = pdf.getTrailer();
420   - std::cout << trailer.getKey("/Info").
421   - getKey("/CreationDate").getStringValue() << std::endl;
422   - std::cout << trailer.getKey("/Info").
423   - getKey("/Producer").getStringValue() << std::endl;
424   -
425   - QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
426   - std::cout << encrypt.getKey("/O").unparse() << std::endl;
427   - std::cout << encrypt.getKey("/U").unparse() << std::endl;
428   -
429   - QPDFObjectHandle root = pdf.getRoot();
430   - QPDFObjectHandle pages = root.getKey("/Pages");
431   - QPDFObjectHandle kids = pages.getKey("/Kids");
432   - QPDFObjectHandle page = kids.getArrayItem(1); // second page
433   - QPDFObjectHandle contents = page.getKey("/Contents");
434   - QUtil::binary_stdout();
435   - PointerHolder<Pl_StdioFile> out = new Pl_StdioFile("filtered", stdout);
436   - contents.pipeStreamData(out.getPointer(), 0, qpdf_dl_generalized);
  393 + std::vector<QPDFObjectHandle> items;
  394 + items.push_back(QPDFObjectHandle::newInteger(14));
  395 + items.push_back(QPDFObjectHandle::newInteger(15));
  396 + items.push_back(QPDFObjectHandle::newInteger(9));
  397 + A.setArrayFromVector(items);
437 398 }
438   - else if (n == 3)
  399 +
  400 + QPDFObjectHandle qtest2 = trailer.getKey("/QTest2");
  401 + if (! qtest2.isNull())
439 402 {
440   - QPDFObjectHandle streams = pdf.getTrailer().getKey("/QStreams");
441   - for (int i = 0; i < streams.getArrayNItems(); ++i)
442   - {
443   - QPDFObjectHandle stream = streams.getArrayItem(i);
444   - std::cout << "-- stream " << i << " --" << std::endl;
445   - std::cout.flush();
446   - QUtil::binary_stdout();
447   - PointerHolder<Pl_StdioFile> out =
448   - new Pl_StdioFile("tokenized stream", stdout);
449   - stream.pipeStreamData(out.getPointer(),
450   - qpdf_ef_normalize, qpdf_dl_generalized);
451   - }
  403 + // Test allow_streams=true
  404 + qtest2.makeDirect(true);
  405 + trailer.replaceKey("/QTest2", qtest2);
452 406 }
453   - else if (n == 4)
454   - {
455   - // Mutability testing: Make /QTest direct recursively, then
456   - // copy to /Info. Also make some other mutations so we can
457   - // tell the difference and ensure that the original /QTest
458   - // isn't effected.
459   - QPDFObjectHandle trailer = pdf.getTrailer();
460   - QPDFObjectHandle qtest = trailer.getKey("/QTest");
461   - qtest.makeDirect();
462   - qtest.removeKey("/Subject");
463   - qtest.replaceKey("/Author",
464   - QPDFObjectHandle::newString("Mr. Potato Head"));
465   - // qtest.A and qtest.B.A were originally the same object.
466   - // They no longer are after makeDirect(). Mutate one of them
467   - // and ensure the other is not changed. These test cases are
468   - // crafted around a specific set of input files.
469   - QPDFObjectHandle A = qtest.getKey("/A");
470   - if (A.getArrayItem(0).getIntValue() == 1)
  407 +
  408 + trailer.replaceKey("/Info", pdf.makeIndirectObject(qtest));
  409 + QPDFWriter w(pdf, 0);
  410 + w.setQDFMode(true);
  411 + w.setStaticID(true);
  412 + w.write();
  413 +
  414 + // Prevent "done" message from getting appended
  415 + exit(0);
  416 +}
  417 +
  418 +static void test_5(QPDF& pdf, char const* arg2)
  419 +{
  420 + QPDFPageDocumentHelper dh(pdf);
  421 + std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
  422 + int pageno = 0;
  423 + for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
  424 + iter != pages.end(); ++iter)
  425 + {
  426 + QPDFPageObjectHelper& page(*iter);
  427 + ++pageno;
  428 +
  429 + std::cout << "page " << pageno << ":" << std::endl;
  430 +
  431 + std::cout << " images:" << std::endl;
  432 + std::map<std::string, QPDFObjectHandle> images = page.getImages();
  433 + for (auto const& iter2: images)
  434 + {
  435 + std::string const& name = iter2.first;
  436 + QPDFObjectHandle image = iter2.second;
  437 + QPDFObjectHandle dict = image.getDict();
  438 + long long width = dict.getKey("/Width").getIntValue();
  439 + long long height = dict.getKey("/Height").getIntValue();
  440 + std::cout << " " << name
  441 + << ": " << width << " x " << height
  442 + << std::endl;
  443 + }
  444 +
  445 + std::cout << " content:" << std::endl;
  446 + std::vector<QPDFObjectHandle> content = page.getPageContents();
  447 + for (auto& iter2: content)
471 448 {
472   - // Test mutators
473   - A.setArrayItem(1, QPDFObjectHandle::newInteger(5)); // 1 5 3
474   - A.insertItem(2, QPDFObjectHandle::newInteger(10)); // 1 5 10 3
475   - A.appendItem(QPDFObjectHandle::newInteger(12)); // 1 5 10 3 12
476   - A.eraseItem(3); // 1 5 10 12
477   - A.insertItem(4, QPDFObjectHandle::newInteger(6)); // 1 5 10 12 6
478   - A.insertItem(0, QPDFObjectHandle::newInteger(9)); // 9 1 5 10 12 6
  449 + std::cout << " " << iter2.unparse() << std::endl;
479 450 }
480   - else
  451 +
  452 + std::cout << "end page " << pageno << std::endl;
  453 + }
  454 +
  455 + QPDFObjectHandle root = pdf.getRoot();
  456 + QPDFObjectHandle qstrings = root.getKey("/QStrings");
  457 + if (qstrings.isArray())
  458 + {
  459 + std::cout << "QStrings:" << std::endl;
  460 + int nitems = qstrings.getArrayNItems();
  461 + for (int i = 0; i < nitems; ++i)
481 462 {
482   - std::vector<QPDFObjectHandle> items;
483   - items.push_back(QPDFObjectHandle::newInteger(14));
484   - items.push_back(QPDFObjectHandle::newInteger(15));
485   - items.push_back(QPDFObjectHandle::newInteger(9));
486   - A.setArrayFromVector(items);
  463 + std::cout << qstrings.getArrayItem(i).getUTF8Value()
  464 + << std::endl;
487 465 }
  466 + }
488 467  
489   - QPDFObjectHandle qtest2 = trailer.getKey("/QTest2");
490   - if (! qtest2.isNull())
  468 + QPDFObjectHandle qnumbers = root.getKey("/QNumbers");
  469 + if (qnumbers.isArray())
  470 + {
  471 + std::cout << "QNumbers:" << std::endl;
  472 + int nitems = qnumbers.getArrayNItems();
  473 + for (int i = 0; i < nitems; ++i)
491 474 {
492   - // Test allow_streams=true
493   - qtest2.makeDirect(true);
494   - trailer.replaceKey("/QTest2", qtest2);
  475 + std::cout << QUtil::double_to_string(
  476 + qnumbers.getArrayItem(i).getNumericValue(), 3, false)
  477 + << std::endl;
495 478 }
  479 + }
  480 +}
  481 +
  482 +static void test_6(QPDF& pdf, char const* arg2)
  483 +{
  484 + QPDFObjectHandle root = pdf.getRoot();
  485 + QPDFObjectHandle metadata = root.getKey("/Metadata");
  486 + if (! metadata.isStream())
  487 + {
  488 + throw std::logic_error("test 6 run on file with no metadata");
  489 + }
  490 + Pl_Buffer bufpl("buffer");
  491 + metadata.pipeStreamData(&bufpl, 0, qpdf_dl_none);
  492 + Buffer* buf = bufpl.getBuffer();
  493 + unsigned char const* data = buf->getBuffer();
  494 + bool cleartext = false;
  495 + if ((buf->getSize() > 9) &&
  496 + (strncmp(reinterpret_cast<char const*>(data),
  497 + "<?xpacket", 9) == 0))
  498 + {
  499 + cleartext = true;
  500 + }
  501 + delete buf;
  502 + std::cout << "encrypted="
  503 + << (pdf.isEncrypted() ? 1 : 0)
  504 + << "; cleartext="
  505 + << (cleartext ? 1 : 0)
  506 + << std::endl;
  507 +}
496 508  
497   - trailer.replaceKey("/Info", pdf.makeIndirectObject(qtest));
498   - QPDFWriter w(pdf, 0);
499   - w.setQDFMode(true);
500   - w.setStaticID(true);
501   - w.write();
  509 +static void test_7(QPDF& pdf, char const* arg2)
  510 +{
  511 + QPDFObjectHandle root = pdf.getRoot();
  512 + QPDFObjectHandle qstream = root.getKey("/QStream");
  513 + if (! qstream.isStream())
  514 + {
  515 + throw std::logic_error("test 7 run on file with no QStream");
  516 + }
  517 + qstream.replaceStreamData(
  518 + "new data for stream\n",
  519 + QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
  520 + QPDFWriter w(pdf, "a.pdf");
  521 + w.setStaticID(true);
  522 + w.setStreamDataMode(qpdf_s_preserve);
  523 + w.write();
  524 +}
502 525  
503   - // Prevent "done" message from getting appended
504   - exit(0);
  526 +static void test_8(QPDF& pdf, char const* arg2)
  527 +{
  528 + QPDFObjectHandle root = pdf.getRoot();
  529 + QPDFObjectHandle qstream = root.getKey("/QStream");
  530 + if (! qstream.isStream())
  531 + {
  532 + throw std::logic_error("test 7 run on file with no QStream");
  533 + }
  534 + Pl_Buffer p1("buffer");
  535 + Pl_Flate p2("compress", &p1, Pl_Flate::a_deflate);
  536 + p2.write(QUtil::unsigned_char_pointer("new data for stream\n"),
  537 + 20); // no null!
  538 + p2.finish();
  539 + PointerHolder<Buffer> b = p1.getBuffer();
  540 + // This is a bogus way to use StreamDataProvider, but it does
  541 + // adequately test its functionality.
  542 + Provider* provider = new Provider(b);
  543 + PointerHolder<QPDFObjectHandle::StreamDataProvider> p = provider;
  544 + qstream.replaceStreamData(
  545 + p, QPDFObjectHandle::newName("/FlateDecode"),
  546 + QPDFObjectHandle::newNull());
  547 + provider->badLength(false);
  548 + QPDFWriter w(pdf, "a.pdf");
  549 + w.setStaticID(true);
  550 + // Linearize to force the provider to be called multiple times.
  551 + w.setLinearization(true);
  552 + w.setStreamDataMode(qpdf_s_preserve);
  553 + w.write();
  554 +
  555 + // Every time a provider pipes stream data, it has to provide
  556 + // the same amount of data.
  557 + provider->badLength(true);
  558 + try
  559 + {
  560 + qstream.getStreamData();
  561 + std::cout << "oops -- getStreamData didn't throw" << std::endl;
505 562 }
506   - else if (n == 5)
  563 + catch (std::exception const& e)
507 564 {
508   - QPDFPageDocumentHelper dh(pdf);
509   - std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
510   - int pageno = 0;
511   - for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
512   - iter != pages.end(); ++iter)
513   - {
514   - QPDFPageObjectHelper& page(*iter);
515   - ++pageno;
516   -
517   - std::cout << "page " << pageno << ":" << std::endl;
518   -
519   - std::cout << " images:" << std::endl;
520   - std::map<std::string, QPDFObjectHandle> images = page.getImages();
521   - for (auto const& iter2: images)
522   - {
523   - std::string const& name = iter2.first;
524   - QPDFObjectHandle image = iter2.second;
525   - QPDFObjectHandle dict = image.getDict();
526   - long long width = dict.getKey("/Width").getIntValue();
527   - long long height = dict.getKey("/Height").getIntValue();
528   - std::cout << " " << name
529   - << ": " << width << " x " << height
530   - << std::endl;
531   - }
532   -
533   - std::cout << " content:" << std::endl;
534   - std::vector<QPDFObjectHandle> content = page.getPageContents();
535   - for (auto& iter2: content)
536   - {
537   - std::cout << " " << iter2.unparse() << std::endl;
538   - }
539   -
540   - std::cout << "end page " << pageno << std::endl;
541   - }
542   -
543   - QPDFObjectHandle root = pdf.getRoot();
544   - QPDFObjectHandle qstrings = root.getKey("/QStrings");
545   - if (qstrings.isArray())
546   - {
547   - std::cout << "QStrings:" << std::endl;
548   - int nitems = qstrings.getArrayNItems();
549   - for (int i = 0; i < nitems; ++i)
550   - {
551   - std::cout << qstrings.getArrayItem(i).getUTF8Value()
552   - << std::endl;
553   - }
554   - }
  565 + std::cout << "exception: " << e.what() << std::endl;
  566 + }
  567 +}
555 568  
556   - QPDFObjectHandle qnumbers = root.getKey("/QNumbers");
557   - if (qnumbers.isArray())
558   - {
559   - std::cout << "QNumbers:" << std::endl;
560   - int nitems = qnumbers.getArrayNItems();
561   - for (int i = 0; i < nitems; ++i)
562   - {
563   - std::cout << QUtil::double_to_string(
564   - qnumbers.getArrayItem(i).getNumericValue(), 3, false)
565   - << std::endl;
566   - }
567   - }
  569 +static void test_9(QPDF& pdf, char const* arg2)
  570 +{
  571 + QPDFObjectHandle root = pdf.getRoot();
  572 + // Explicitly exercise the Buffer version of newStream
  573 + PointerHolder<Buffer> buf = new Buffer(20);
  574 + unsigned char* bp = buf->getBuffer();
  575 + memcpy(bp, "data for new stream\n", 20); // no null!
  576 + QPDFObjectHandle qstream = QPDFObjectHandle::newStream(
  577 + &pdf, buf);
  578 + QPDFObjectHandle rstream = QPDFObjectHandle::newStream(&pdf);
  579 + try
  580 + {
  581 + rstream.getStreamData();
  582 + std::cout << "oops -- getStreamData didn't throw" << std::endl;
568 583 }
569   - else if (n == 6)
  584 + catch (std::logic_error const& e)
570 585 {
571   - QPDFObjectHandle root = pdf.getRoot();
572   - QPDFObjectHandle metadata = root.getKey("/Metadata");
573   - if (! metadata.isStream())
574   - {
575   - throw std::logic_error("test 6 run on file with no metadata");
576   - }
577   - Pl_Buffer bufpl("buffer");
578   - metadata.pipeStreamData(&bufpl, 0, qpdf_dl_none);
579   - Buffer* buf = bufpl.getBuffer();
580   - unsigned char const* data = buf->getBuffer();
581   - bool cleartext = false;
582   - if ((buf->getSize() > 9) &&
583   - (strncmp(reinterpret_cast<char const*>(data),
584   - "<?xpacket", 9) == 0))
585   - {
586   - cleartext = true;
587   - }
588   - delete buf;
589   - std::cout << "encrypted="
590   - << (pdf.isEncrypted() ? 1 : 0)
591   - << "; cleartext="
592   - << (cleartext ? 1 : 0)
593   - << std::endl;
594   - }
595   - else if (n == 7)
596   - {
597   - QPDFObjectHandle root = pdf.getRoot();
598   - QPDFObjectHandle qstream = root.getKey("/QStream");
599   - if (! qstream.isStream())
600   - {
601   - throw std::logic_error("test 7 run on file with no QStream");
602   - }
603   - qstream.replaceStreamData(
604   - "new data for stream\n",
605   - QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
606   - QPDFWriter w(pdf, "a.pdf");
607   - w.setStaticID(true);
608   - w.setStreamDataMode(qpdf_s_preserve);
609   - w.write();
610   - }
611   - else if (n == 8)
612   - {
613   - QPDFObjectHandle root = pdf.getRoot();
614   - QPDFObjectHandle qstream = root.getKey("/QStream");
615   - if (! qstream.isStream())
616   - {
617   - throw std::logic_error("test 7 run on file with no QStream");
618   - }
619   - Pl_Buffer p1("buffer");
620   - Pl_Flate p2("compress", &p1, Pl_Flate::a_deflate);
621   - p2.write(QUtil::unsigned_char_pointer("new data for stream\n"),
622   - 20); // no null!
623   - p2.finish();
624   - PointerHolder<Buffer> b = p1.getBuffer();
625   - // This is a bogus way to use StreamDataProvider, but it does
626   - // adequately test its functionality.
627   - Provider* provider = new Provider(b);
628   - PointerHolder<QPDFObjectHandle::StreamDataProvider> p = provider;
629   - qstream.replaceStreamData(
630   - p, QPDFObjectHandle::newName("/FlateDecode"),
631   - QPDFObjectHandle::newNull());
632   - provider->badLength(false);
633   - QPDFWriter w(pdf, "a.pdf");
634   - w.setStaticID(true);
635   - // Linearize to force the provider to be called multiple times.
636   - w.setLinearization(true);
637   - w.setStreamDataMode(qpdf_s_preserve);
638   - w.write();
639   -
640   - // Every time a provider pipes stream data, it has to provide
641   - // the same amount of data.
642   - provider->badLength(true);
643   - try
644   - {
645   - qstream.getStreamData();
646   - std::cout << "oops -- getStreamData didn't throw" << std::endl;
647   - }
648   - catch (std::exception const& e)
649   - {
650   - std::cout << "exception: " << e.what() << std::endl;
651   - }
  586 + std::cout << "exception: " << e.what() << std::endl;
652 587 }
653   - else if (n == 9)
654   - {
655   - QPDFObjectHandle root = pdf.getRoot();
656   - // Explicitly exercise the Buffer version of newStream
657   - PointerHolder<Buffer> buf = new Buffer(20);
658   - unsigned char* bp = buf->getBuffer();
659   - memcpy(bp, "data for new stream\n", 20); // no null!
660   - QPDFObjectHandle qstream = QPDFObjectHandle::newStream(
661   - &pdf, buf);
662   - QPDFObjectHandle rstream = QPDFObjectHandle::newStream(&pdf);
663   - try
664   - {
665   - rstream.getStreamData();
666   - std::cout << "oops -- getStreamData didn't throw" << std::endl;
667   - }
668   - catch (std::logic_error const& e)
669   - {
670   - std::cout << "exception: " << e.what() << std::endl;
671   - }
672   - rstream.replaceStreamData(
673   - "data for other stream\n",
674   - QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
675   - root.replaceKey("/QStream", qstream);
676   - root.replaceKey("/RStream", rstream);
677   - QPDFWriter w(pdf, "a.pdf");
678   - w.setStaticID(true);
679   - w.setStreamDataMode(qpdf_s_preserve);
680   - w.write();
681   - }
682   - else if (n == 10)
683   - {
684   - std::vector<QPDFPageObjectHelper> pages =
685   - QPDFPageDocumentHelper(pdf).getAllPages();
686   - QPDFPageObjectHelper& ph(pages.at(0));
687   - ph.addPageContents(
688   - QPDFObjectHandle::newStream(
689   - &pdf, "BT /F1 12 Tf 72 620 Td (Baked) Tj ET\n"), true);
690   - ph.addPageContents(
691   - QPDFObjectHandle::newStream(
692   - &pdf, "BT /F1 18 Tf 72 520 Td (Mashed) Tj ET\n"), false);
693   -
694   - QPDFWriter w(pdf, "a.pdf");
695   - w.setStaticID(true);
696   - w.setStreamDataMode(qpdf_s_preserve);
697   - w.write();
698   - }
699   - else if (n == 11)
700   - {
701   - QPDFObjectHandle root = pdf.getRoot();
702   - QPDFObjectHandle qstream = root.getKey("/QStream");
703   - PointerHolder<Buffer> b1 = qstream.getStreamData();
704   - PointerHolder<Buffer> b2 = qstream.getRawStreamData();
705   - if ((b1->getSize() == 7) &&
706   - (memcmp(b1->getBuffer(), "potato\n", 7) == 0))
707   - {
708   - std::cout << "filtered stream data okay" << std::endl;
709   - }
710   - if ((b2->getSize() == 15) &&
711   - (memcmp(b2->getBuffer(), "706F7461746F0A\n", 15) == 0))
712   - {
713   - std::cout << "raw stream data okay" << std::endl;
714   - }
  588 + rstream.replaceStreamData(
  589 + "data for other stream\n",
  590 + QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
  591 + root.replaceKey("/QStream", qstream);
  592 + root.replaceKey("/RStream", rstream);
  593 + QPDFWriter w(pdf, "a.pdf");
  594 + w.setStaticID(true);
  595 + w.setStreamDataMode(qpdf_s_preserve);
  596 + w.write();
  597 +}
  598 +
  599 +static void test_10(QPDF& pdf, char const* arg2)
  600 +{
  601 + std::vector<QPDFPageObjectHelper> pages =
  602 + QPDFPageDocumentHelper(pdf).getAllPages();
  603 + QPDFPageObjectHelper& ph(pages.at(0));
  604 + ph.addPageContents(
  605 + QPDFObjectHandle::newStream(
  606 + &pdf, "BT /F1 12 Tf 72 620 Td (Baked) Tj ET\n"), true);
  607 + ph.addPageContents(
  608 + QPDFObjectHandle::newStream(
  609 + &pdf, "BT /F1 18 Tf 72 520 Td (Mashed) Tj ET\n"), false);
  610 +
  611 + QPDFWriter w(pdf, "a.pdf");
  612 + w.setStaticID(true);
  613 + w.setStreamDataMode(qpdf_s_preserve);
  614 + w.write();
  615 +}
  616 +
  617 +static void test_11(QPDF& pdf, char const* arg2)
  618 +{
  619 + QPDFObjectHandle root = pdf.getRoot();
  620 + QPDFObjectHandle qstream = root.getKey("/QStream");
  621 + PointerHolder<Buffer> b1 = qstream.getStreamData();
  622 + PointerHolder<Buffer> b2 = qstream.getRawStreamData();
  623 + if ((b1->getSize() == 7) &&
  624 + (memcmp(b1->getBuffer(), "potato\n", 7) == 0))
  625 + {
  626 + std::cout << "filtered stream data okay" << std::endl;
715 627 }
716   - else if (n == 12)
  628 + if ((b2->getSize() == 15) &&
  629 + (memcmp(b2->getBuffer(), "706F7461746F0A\n", 15) == 0))
717 630 {
718   - pdf.setOutputStreams(0, 0);
719   - pdf.showLinearizationData();
  631 + std::cout << "raw stream data okay" << std::endl;
720 632 }
721   - else if (n == 13)
  633 +}
  634 +
  635 +static void test_12(QPDF& pdf, char const* arg2)
  636 +{
  637 + pdf.setOutputStreams(0, 0);
  638 + pdf.showLinearizationData();
  639 +}
  640 +
  641 +static void test_13(QPDF& pdf, char const* arg2)
  642 +{
  643 + std::ostringstream out;
  644 + std::ostringstream err;
  645 + pdf.setOutputStreams(&out, &err);
  646 + pdf.showLinearizationData();
  647 + std::cout << "---output---" << std::endl
  648 + << out.str()
  649 + << "---error---" << std::endl
  650 + << err.str();
  651 +}
  652 +
  653 +static void test_14(QPDF& pdf, char const* arg2)
  654 +{
  655 + // Exercise swap and replace. This test case is designed for
  656 + // a specific file.
  657 + std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
  658 + if (pages.size() != 4)
  659 + {
  660 + throw std::logic_error("test 14 not called 4-page file");
  661 + }
  662 + // Swap pages 2 and 3
  663 + auto orig_page2 = pages.at(1);
  664 + auto orig_page3 = pages.at(2);
  665 + assert(orig_page2.getKey("/OrigPage").getIntValue() == 2);
  666 + assert(orig_page3.getKey("/OrigPage").getIntValue() == 3);
  667 + pdf.swapObjects(orig_page2.getObjGen(), orig_page3.getObjGen());
  668 + assert(orig_page2.getKey("/OrigPage").getIntValue() == 3);
  669 + assert(orig_page3.getKey("/OrigPage").getIntValue() == 2);
  670 + // Replace object and swap objects
  671 + QPDFObjectHandle trailer = pdf.getTrailer();
  672 + QPDFObjectHandle qdict = trailer.getKey("/QDict");
  673 + QPDFObjectHandle qarray = trailer.getKey("/QArray");
  674 + // Force qdict but not qarray to resolve
  675 + qdict.isDictionary();
  676 + QPDFObjectHandle new_dict = QPDFObjectHandle::newDictionary();
  677 + new_dict.replaceKey("/NewDict", QPDFObjectHandle::newInteger(2));
  678 + try
722 679 {
723   - std::ostringstream out;
724   - std::ostringstream err;
725   - pdf.setOutputStreams(&out, &err);
726   - pdf.showLinearizationData();
727   - std::cout << "---output---" << std::endl
728   - << out.str()
729   - << "---error---" << std::endl
730   - << err.str();
  680 + // Do it wrong first...
  681 + pdf.replaceObject(qdict.getObjGen(), qdict);
731 682 }
732   - else if (n == 14)
  683 + catch (std::logic_error const&)
733 684 {
734   - // Exercise swap and replace. This test case is designed for
735   - // a specific file.
736   - std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
737   - if (pages.size() != 4)
738   - {
739   - throw std::logic_error("test " + QUtil::int_to_string(n) +
740   - " not called 4-page file");
741   - }
742   - // Swap pages 2 and 3
743   - auto orig_page2 = pages.at(1);
744   - auto orig_page3 = pages.at(2);
745   - assert(orig_page2.getKey("/OrigPage").getIntValue() == 2);
746   - assert(orig_page3.getKey("/OrigPage").getIntValue() == 3);
747   - pdf.swapObjects(orig_page2.getObjGen(), orig_page3.getObjGen());
748   - assert(orig_page2.getKey("/OrigPage").getIntValue() == 3);
749   - assert(orig_page3.getKey("/OrigPage").getIntValue() == 2);
750   - // Replace object and swap objects
751   - QPDFObjectHandle trailer = pdf.getTrailer();
752   - QPDFObjectHandle qdict = trailer.getKey("/QDict");
753   - QPDFObjectHandle qarray = trailer.getKey("/QArray");
754   - // Force qdict but not qarray to resolve
755   - qdict.isDictionary();
756   - QPDFObjectHandle new_dict = QPDFObjectHandle::newDictionary();
757   - new_dict.replaceKey("/NewDict", QPDFObjectHandle::newInteger(2));
758   - try
759   - {
760   - // Do it wrong first...
761   - pdf.replaceObject(qdict.getObjGen(), qdict);
762   - }
763   - catch (std::logic_error const&)
764   - {
765   - std::cout << "caught logic error as expected" << std::endl;
766   - }
767   - pdf.replaceObject(qdict.getObjGen(), new_dict);
768   - // Now qdict points to the new dictionary
769   - std::cout << "old dict: " << qdict.getKey("/NewDict").getIntValue()
770   - << std::endl;
771   - // Swap dict and array
772   - pdf.swapObjects(qdict.getObjGen(), qarray.getObjGen());
773   - // Now qarray will resolve to new object and qdict resolves to
774   - // the array
775   - std::cout << "swapped array: " << qdict.getArrayItem(0).getName()
776   - << std::endl;
777   - std::cout << "new dict: " << qarray.getKey("/NewDict").getIntValue()
778   - << std::endl;
779   - // Reread qdict, still pointing to an array
780   - qdict = pdf.getObjectByObjGen(qdict.getObjGen());
781   - std::cout << "swapped array: " << qdict.getArrayItem(0).getName()
782   - << std::endl;
783   -
784   - // Exercise getAsMap and getAsArray
785   - std::vector<QPDFObjectHandle> array_elements =
786   - qdict.getArrayAsVector();
787   - std::map<std::string, QPDFObjectHandle> dict_items =
788   - qarray.getDictAsMap();
789   - if ((array_elements.size() == 1) &&
790   - (array_elements.at(0).getName() == "/Array") &&
791   - (dict_items.size() == 1) &&
792   - (dict_items["/NewDict"].getIntValue() == 2))
793   - {
794   - std::cout << "array and dictionary contents are correct"
795   - << std::endl;
796   - }
797   -
798   - // Exercise writing to memory buffer
799   - for (int i = 0; i < 2; ++i)
800   - {
801   - QPDFWriter w(pdf);
802   - w.setOutputMemory();
803   - // Exercise setOutputMemory with and without static ID
804   - w.setStaticID(i == 0);
805   - w.setStreamDataMode(qpdf_s_preserve);
806   - w.write();
807   - Buffer* b = w.getBuffer();
808   - std::string const filename = (i == 0 ? "a.pdf" : "b.pdf");
809   - FILE* f = QUtil::safe_fopen(filename.c_str(), "wb");
810   - fwrite(b->getBuffer(), b->getSize(), 1, f);
811   - fclose(f);
812   - delete b;
813   - }
  685 + std::cout << "caught logic error as expected" << std::endl;
814 686 }
815   - else if (n == 15)
816   - {
817   - std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
818   - // Reference to original page numbers for this test case are
819   - // numbered from 0.
820   -
821   - // Remove pages from various places, checking to make sure
822   - // that our pages reference is getting updated.
823   - assert(pages.size() == 10);
824   - pdf.removePage(pages.back()); // original page 9
825   - assert(pages.size() == 9);
826   - pdf.removePage(*pages.begin()); // original page 0
827   - assert(pages.size() == 8);
828   - checkPageContents(pages.at(4), "Original page 5");
829   - pdf.removePage(pages.at(4)); // original page 5
830   - assert(pages.size() == 7);
831   - checkPageContents(pages.at(4), "Original page 6");
832   - checkPageContents(pages.at(0), "Original page 1");
833   - checkPageContents(pages.at(6), "Original page 8");
834   -
835   - // Insert pages
836   -
837   - // Create some content streams.
838   - std::vector<QPDFObjectHandle> contents;
839   - contents.push_back(createPageContents(pdf, "New page 1"));
840   - contents.push_back(createPageContents(pdf, "New page 0"));
841   - contents.push_back(createPageContents(pdf, "New page 5"));
842   - contents.push_back(createPageContents(pdf, "New page 6"));
843   - contents.push_back(createPageContents(pdf, "New page 11"));
844   - contents.push_back(createPageContents(pdf, "New page 12"));
845   -
846   - // Create some page objects. Start with an existing
847   - // dictionary and modify it. Using the results of
848   - // getDictAsMap to create a new dictionary effectively creates
849   - // a shallow copy.
850   - QPDFObjectHandle page_template = pages.at(0);
851   - std::vector<QPDFObjectHandle> new_pages;
852   - for (std::vector<QPDFObjectHandle>::iterator iter = contents.begin();
853   - iter != contents.end(); ++iter)
854   - {
855   - // We will retain indirect object references to other
856   - // indirect objects other than page content.
857   - QPDFObjectHandle page = page_template.shallowCopy();
858   - page.replaceKey("/Contents", *iter);
859   - if (iter == contents.begin())
860   - {
861   - // leave direct
862   - new_pages.push_back(page);
863   - }
864   - else
865   - {
866   - new_pages.push_back(pdf.makeIndirectObject(page));
867   - }
868   - }
  687 + pdf.replaceObject(qdict.getObjGen(), new_dict);
  688 + // Now qdict points to the new dictionary
  689 + std::cout << "old dict: " << qdict.getKey("/NewDict").getIntValue()
  690 + << std::endl;
  691 + // Swap dict and array
  692 + pdf.swapObjects(qdict.getObjGen(), qarray.getObjGen());
  693 + // Now qarray will resolve to new object and qdict resolves to
  694 + // the array
  695 + std::cout << "swapped array: " << qdict.getArrayItem(0).getName()
  696 + << std::endl;
  697 + std::cout << "new dict: " << qarray.getKey("/NewDict").getIntValue()
  698 + << std::endl;
  699 + // Reread qdict, still pointing to an array
  700 + qdict = pdf.getObjectByObjGen(qdict.getObjGen());
  701 + std::cout << "swapped array: " << qdict.getArrayItem(0).getName()
  702 + << std::endl;
869 703  
870   - // Now insert the pages
871   - pdf.addPage(new_pages.at(0), true);
872   - checkPageContents(pages.at(0), "New page 1");
873   - pdf.addPageAt(new_pages.at(1), true, pages.at(0));
874   - assert(pages.at(0).getObjGen() == new_pages.at(1).getObjGen());
875   - pdf.addPageAt(new_pages.at(2), true, pages.at(5));
876   - assert(pages.at(5).getObjGen() == new_pages.at(2).getObjGen());
877   - pdf.addPageAt(new_pages.at(3), false, pages.at(5));
878   - assert(pages.at(6).getObjGen() == new_pages.at(3).getObjGen());
879   - assert(pages.size() == 11);
880   - pdf.addPage(new_pages.at(4), false);
881   - assert(pages.at(11).getObjGen() == new_pages.at(4).getObjGen());
882   - pdf.addPageAt(new_pages.at(5), false, pages.back());
883   - assert(pages.size() == 13);
884   - checkPageContents(pages.at(0), "New page 0");
885   - checkPageContents(pages.at(1), "New page 1");
886   - checkPageContents(pages.at(5), "New page 5");
887   - checkPageContents(pages.at(6), "New page 6");
888   - checkPageContents(pages.at(11), "New page 11");
889   - checkPageContents(pages.at(12), "New page 12");
890   -
891   - // Exercise writing to FILE*
892   - FILE* out = QUtil::safe_fopen("a.pdf", "wb");
893   - QPDFWriter w(pdf, "FILE* a.pdf", out, true);
894   - w.setStaticID(true);
895   - w.setStreamDataMode(qpdf_s_preserve);
896   - w.write();
897   - }
898   - else if (n == 16)
899   - {
900   - // Insert a page manually and then update the cache.
901   - std::vector<QPDFObjectHandle> const& all_pages = pdf.getAllPages();
902   -
903   - QPDFObjectHandle contents = createPageContents(pdf, "New page 10");
904   - QPDFObjectHandle page =
905   - pdf.makeIndirectObject(
906   - QPDFObjectHandle(all_pages.at(0)).shallowCopy());
907   - page.replaceKey("/Contents", contents);
908   -
909   - // Insert the page manually.
910   - QPDFObjectHandle root = pdf.getRoot();
911   - QPDFObjectHandle pages = root.getKey("/Pages");
912   - QPDFObjectHandle kids = pages.getKey("/Kids");
913   - page.replaceKey("/Parent", pages);
914   - pages.replaceKey(
915   - "/Count",
916   - QPDFObjectHandle::newInteger(
917   - 1 + QIntC::to_longlong(all_pages.size())));
918   - kids.appendItem(page);
919   - assert(all_pages.size() == 10);
920   - pdf.updateAllPagesCache();
921   - assert(all_pages.size() == 11);
922   - assert(all_pages.back().getObjGen() == page.getObjGen());
923   -
924   - QPDFWriter w(pdf, "a.pdf");
925   - w.setStaticID(true);
926   - w.setStreamDataMode(qpdf_s_preserve);
927   - w.write();
928   - }
929   - else if (n == 17)
930   - {
931   - // The input file to this test case has a duplicated page.
932   - QPDFObjectHandle page_kids =
933   - pdf.getRoot().getKey("/Pages").getKey("/Kids");
934   - assert(page_kids.getArrayItem(0).getObjGen() ==
935   - page_kids.getArrayItem(1).getObjGen());
936   - std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
937   - assert(pages.size() == 3);
938   - assert(! (pages.at(0).getObjGen() == pages.at(1).getObjGen()));
939   - assert(QPDFObjectHandle(pages.at(0)).getKey("/Contents").getObjGen() ==
940   - QPDFObjectHandle(pages.at(1)).getKey("/Contents").getObjGen());
941   - pdf.removePage(pages.at(0));
942   - assert(pages.size() == 2);
943   - PointerHolder<Buffer> b = QPDFObjectHandle(pages.at(0)).
944   - getKey("/Contents").getStreamData();
945   - std::string contents = std::string(
946   - reinterpret_cast<char const*>(b->getBuffer()),
947   - b->getSize());
948   - assert(contents.find("page 0") != std::string::npos);
949   - }
950   - else if (n == 18)
951   - {
952   - // Remove a page and re-insert it in the same file.
953   - std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
954   -
955   - // Remove pages from various places, checking to make sure
956   - // that our pages reference is getting updated.
957   - assert(pages.size() == 10);
958   - QPDFObjectHandle page5 = pages.at(5);
959   - pdf.removePage(page5);
960   - assert(pages.size() == 9);
961   - pdf.addPage(page5, false);
962   - assert(pages.size() == 10);
963   - assert(pages.back().getObjGen() == page5.getObjGen());
964   -
965   - QPDFWriter w(pdf, "a.pdf");
966   - w.setStaticID(true);
967   - w.setStreamDataMode(qpdf_s_preserve);
968   - w.write();
969   - }
970   - else if (n == 19)
971   - {
972   - // Remove a page and re-insert it in the same file.
973   - std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
974   -
975   - // Try to insert a page that's already there. A shallow copy
976   - // gets inserted instead.
977   - auto newpage = pages.at(5);
978   - size_t count = pages.size();
979   - pdf.addPage(newpage, false);
980   - auto last = pages.back();
981   - assert(pages.size() == count + 1);
982   - assert(! (last.getObjGen() == newpage.getObjGen()));
983   - assert(last.getKey("/Contents").getObjGen() ==
984   - newpage.getKey("/Contents").getObjGen());
985   - }
986   - else if (n == 20)
987   - {
988   - // Shallow copy an array
989   - QPDFObjectHandle trailer = pdf.getTrailer();
990   - QPDFObjectHandle qtest = trailer.getKey("/QTest");
991   - QPDFObjectHandle copy = qtest.shallowCopy();
992   - // Append shallow copy of a scalar
993   - copy.appendItem(trailer.getKey("/Size").shallowCopy());
994   - trailer.replaceKey("/QTest2", copy);
995   -
996   - QPDFWriter w(pdf, "a.pdf");
997   - w.setStaticID(true);
998   - w.setStreamDataMode(qpdf_s_preserve);
999   - w.write();
1000   - }
1001   - else if (n == 21)
1002   - {
1003   - // Try to shallow copy a stream
1004   - std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
1005   - QPDFObjectHandle page = pages.at(0);
1006   - QPDFObjectHandle contents = page.getKey("/Contents");
1007   - contents.shallowCopy();
1008   - std::cout << "you can't see this" << std::endl;
1009   - }
1010   - else if (n == 22)
1011   - {
1012   - // Try to remove a page we don't have
1013   - QPDFPageDocumentHelper dh(pdf);
1014   - std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
1015   - QPDFPageObjectHelper& page = pages.at(0);
1016   - dh.removePage(page);
1017   - dh.removePage(page);
1018   - std::cout << "you can't see this" << std::endl;
  704 + // Exercise getAsMap and getAsArray
  705 + std::vector<QPDFObjectHandle> array_elements =
  706 + qdict.getArrayAsVector();
  707 + std::map<std::string, QPDFObjectHandle> dict_items =
  708 + qarray.getDictAsMap();
  709 + if ((array_elements.size() == 1) &&
  710 + (array_elements.at(0).getName() == "/Array") &&
  711 + (dict_items.size() == 1) &&
  712 + (dict_items["/NewDict"].getIntValue() == 2))
  713 + {
  714 + std::cout << "array and dictionary contents are correct"
  715 + << std::endl;
1019 716 }
1020   - else if (n == 23)
  717 +
  718 + // Exercise writing to memory buffer
  719 + for (int i = 0; i < 2; ++i)
1021 720 {
1022   - QPDFPageDocumentHelper dh(pdf);
1023   - std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
1024   - dh.removePage(pages.back());
1025   - }
1026   - else if (n == 24)
1027   - {
1028   - // Test behavior of reserved objects
1029   - QPDFObjectHandle res1 = QPDFObjectHandle::newReserved(&pdf);
1030   - QPDFObjectHandle res2 = QPDFObjectHandle::newReserved(&pdf);
1031   - QPDFObjectHandle trailer = pdf.getTrailer();
1032   - trailer.replaceKey("Array1", res1);
1033   - trailer.replaceKey("Array2", res2);
1034   -
1035   - QPDFObjectHandle array1 = QPDFObjectHandle::newArray();
1036   - QPDFObjectHandle array2 = QPDFObjectHandle::newArray();
1037   - array1.appendItem(res2);
1038   - array1.appendItem(QPDFObjectHandle::newInteger(1));
1039   - array2.appendItem(res1);
1040   - array2.appendItem(QPDFObjectHandle::newInteger(2));
1041   - // Make sure trying to ask questions about a reserved object
1042   - // doesn't break it.
1043   - if (res1.isArray())
1044   - {
1045   - std::cout << "oops -- res1 is an array" << std::endl;
1046   - }
1047   - if (res1.isReserved())
1048   - {
1049   - std::cout << "res1 is still reserved after checking if array"
1050   - << std::endl;
1051   - }
1052   - pdf.replaceReserved(res1, array1);
1053   - if (res1.isReserved())
1054   - {
1055   - std::cout << "oops -- res1 is still reserved" << std::endl;
  721 + QPDFWriter w(pdf);
  722 + w.setOutputMemory();
  723 + // Exercise setOutputMemory with and without static ID
  724 + w.setStaticID(i == 0);
  725 + w.setStreamDataMode(qpdf_s_preserve);
  726 + w.write();
  727 + Buffer* b = w.getBuffer();
  728 + std::string const filename = (i == 0 ? "a.pdf" : "b.pdf");
  729 + FILE* f = QUtil::safe_fopen(filename.c_str(), "wb");
  730 + fwrite(b->getBuffer(), b->getSize(), 1, f);
  731 + fclose(f);
  732 + delete b;
  733 + }
  734 +}
  735 +
  736 +static void test_15(QPDF& pdf, char const* arg2)
  737 +{
  738 + std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
  739 + // Reference to original page numbers for this test case are
  740 + // numbered from 0.
  741 +
  742 + // Remove pages from various places, checking to make sure
  743 + // that our pages reference is getting updated.
  744 + assert(pages.size() == 10);
  745 + pdf.removePage(pages.back()); // original page 9
  746 + assert(pages.size() == 9);
  747 + pdf.removePage(*pages.begin()); // original page 0
  748 + assert(pages.size() == 8);
  749 + checkPageContents(pages.at(4), "Original page 5");
  750 + pdf.removePage(pages.at(4)); // original page 5
  751 + assert(pages.size() == 7);
  752 + checkPageContents(pages.at(4), "Original page 6");
  753 + checkPageContents(pages.at(0), "Original page 1");
  754 + checkPageContents(pages.at(6), "Original page 8");
  755 +
  756 + // Insert pages
  757 +
  758 + // Create some content streams.
  759 + std::vector<QPDFObjectHandle> contents;
  760 + contents.push_back(createPageContents(pdf, "New page 1"));
  761 + contents.push_back(createPageContents(pdf, "New page 0"));
  762 + contents.push_back(createPageContents(pdf, "New page 5"));
  763 + contents.push_back(createPageContents(pdf, "New page 6"));
  764 + contents.push_back(createPageContents(pdf, "New page 11"));
  765 + contents.push_back(createPageContents(pdf, "New page 12"));
  766 +
  767 + // Create some page objects. Start with an existing
  768 + // dictionary and modify it. Using the results of
  769 + // getDictAsMap to create a new dictionary effectively creates
  770 + // a shallow copy.
  771 + QPDFObjectHandle page_template = pages.at(0);
  772 + std::vector<QPDFObjectHandle> new_pages;
  773 + for (std::vector<QPDFObjectHandle>::iterator iter = contents.begin();
  774 + iter != contents.end(); ++iter)
  775 + {
  776 + // We will retain indirect object references to other
  777 + // indirect objects other than page content.
  778 + QPDFObjectHandle page = page_template.shallowCopy();
  779 + page.replaceKey("/Contents", *iter);
  780 + if (iter == contents.begin())
  781 + {
  782 + // leave direct
  783 + new_pages.push_back(page);
1056 784 }
1057 785 else
1058 786 {
1059   - std::cout << "res1 is no longer reserved" << std::endl;
1060   - }
1061   - res1.assertArray();
1062   - std::cout << "res1 is an array" << std::endl;
  787 + new_pages.push_back(pdf.makeIndirectObject(page));
  788 + }
  789 + }
  790 +
  791 + // Now insert the pages
  792 + pdf.addPage(new_pages.at(0), true);
  793 + checkPageContents(pages.at(0), "New page 1");
  794 + pdf.addPageAt(new_pages.at(1), true, pages.at(0));
  795 + assert(pages.at(0).getObjGen() == new_pages.at(1).getObjGen());
  796 + pdf.addPageAt(new_pages.at(2), true, pages.at(5));
  797 + assert(pages.at(5).getObjGen() == new_pages.at(2).getObjGen());
  798 + pdf.addPageAt(new_pages.at(3), false, pages.at(5));
  799 + assert(pages.at(6).getObjGen() == new_pages.at(3).getObjGen());
  800 + assert(pages.size() == 11);
  801 + pdf.addPage(new_pages.at(4), false);
  802 + assert(pages.at(11).getObjGen() == new_pages.at(4).getObjGen());
  803 + pdf.addPageAt(new_pages.at(5), false, pages.back());
  804 + assert(pages.size() == 13);
  805 + checkPageContents(pages.at(0), "New page 0");
  806 + checkPageContents(pages.at(1), "New page 1");
  807 + checkPageContents(pages.at(5), "New page 5");
  808 + checkPageContents(pages.at(6), "New page 6");
  809 + checkPageContents(pages.at(11), "New page 11");
  810 + checkPageContents(pages.at(12), "New page 12");
  811 +
  812 + // Exercise writing to FILE*
  813 + FILE* out = QUtil::safe_fopen("a.pdf", "wb");
  814 + QPDFWriter w(pdf, "FILE* a.pdf", out, true);
  815 + w.setStaticID(true);
  816 + w.setStreamDataMode(qpdf_s_preserve);
  817 + w.write();
  818 +}
1063 819  
1064   - try
1065   - {
1066   - res2.unparseResolved();
1067   - std::cout << "oops -- didn't throw" << std::endl;
1068   - }
1069   - catch (std::logic_error const& e)
1070   - {
1071   - std::cout << "logic error: " << e.what() << std::endl;
1072   - }
1073   - try
1074   - {
1075   - res2.makeDirect();
1076   - std::cout << "oops -- didn't throw" << std::endl;
1077   - }
1078   - catch (std::logic_error const& e)
1079   - {
1080   - std::cout << "logic error: " << e.what() << std::endl;
1081   - }
  820 +static void test_16(QPDF& pdf, char const* arg2)
  821 +{
  822 + // Insert a page manually and then update the cache.
  823 + std::vector<QPDFObjectHandle> const& all_pages = pdf.getAllPages();
  824 +
  825 + QPDFObjectHandle contents = createPageContents(pdf, "New page 10");
  826 + QPDFObjectHandle page =
  827 + pdf.makeIndirectObject(
  828 + QPDFObjectHandle(all_pages.at(0)).shallowCopy());
  829 + page.replaceKey("/Contents", contents);
  830 +
  831 + // Insert the page manually.
  832 + QPDFObjectHandle root = pdf.getRoot();
  833 + QPDFObjectHandle pages = root.getKey("/Pages");
  834 + QPDFObjectHandle kids = pages.getKey("/Kids");
  835 + page.replaceKey("/Parent", pages);
  836 + pages.replaceKey(
  837 + "/Count",
  838 + QPDFObjectHandle::newInteger(
  839 + 1 + QIntC::to_longlong(all_pages.size())));
  840 + kids.appendItem(page);
  841 + assert(all_pages.size() == 10);
  842 + pdf.updateAllPagesCache();
  843 + assert(all_pages.size() == 11);
  844 + assert(all_pages.back().getObjGen() == page.getObjGen());
  845 +
  846 + QPDFWriter w(pdf, "a.pdf");
  847 + w.setStaticID(true);
  848 + w.setStreamDataMode(qpdf_s_preserve);
  849 + w.write();
  850 +}
1082 851  
1083   - pdf.replaceReserved(res2, array2);
  852 +static void test_17(QPDF& pdf, char const* arg2)
  853 +{
  854 + // The input file to this test case has a duplicated page.
  855 + QPDFObjectHandle page_kids =
  856 + pdf.getRoot().getKey("/Pages").getKey("/Kids");
  857 + assert(page_kids.getArrayItem(0).getObjGen() ==
  858 + page_kids.getArrayItem(1).getObjGen());
  859 + std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
  860 + assert(pages.size() == 3);
  861 + assert(! (pages.at(0).getObjGen() == pages.at(1).getObjGen()));
  862 + assert(QPDFObjectHandle(pages.at(0)).getKey("/Contents").getObjGen() ==
  863 + QPDFObjectHandle(pages.at(1)).getKey("/Contents").getObjGen());
  864 + pdf.removePage(pages.at(0));
  865 + assert(pages.size() == 2);
  866 + PointerHolder<Buffer> b = QPDFObjectHandle(pages.at(0)).
  867 + getKey("/Contents").getStreamData();
  868 + std::string contents = std::string(
  869 + reinterpret_cast<char const*>(b->getBuffer()),
  870 + b->getSize());
  871 + assert(contents.find("page 0") != std::string::npos);
  872 +}
1084 873  
1085   - res2.assertArray();
1086   - std::cout << "res2 is an array" << std::endl;
  874 +static void test_18(QPDF& pdf, char const* arg2)
  875 +{
  876 + // Remove a page and re-insert it in the same file.
  877 + std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
  878 +
  879 + // Remove pages from various places, checking to make sure
  880 + // that our pages reference is getting updated.
  881 + assert(pages.size() == 10);
  882 + QPDFObjectHandle page5 = pages.at(5);
  883 + pdf.removePage(page5);
  884 + assert(pages.size() == 9);
  885 + pdf.addPage(page5, false);
  886 + assert(pages.size() == 10);
  887 + assert(pages.back().getObjGen() == page5.getObjGen());
  888 +
  889 + QPDFWriter w(pdf, "a.pdf");
  890 + w.setStaticID(true);
  891 + w.setStreamDataMode(qpdf_s_preserve);
  892 + w.write();
  893 +}
1087 894  
1088   - // Verify that the previously added reserved keys can be
1089   - // dereferenced properly now
1090   - int i1 = res1.getArrayItem(0).getArrayItem(1).getIntValueAsInt();
1091   - int i2 = res2.getArrayItem(0).getArrayItem(1).getIntValueAsInt();
1092   - if ((i1 == 2) && (i2 == 1))
1093   - {
1094   - std::cout << "circular access and lazy resolution worked" << std::endl;
1095   - }
  895 +static void test_19(QPDF& pdf, char const* arg2)
  896 +{
  897 + // Remove a page and re-insert it in the same file.
  898 + std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
  899 +
  900 + // Try to insert a page that's already there. A shallow copy
  901 + // gets inserted instead.
  902 + auto newpage = pages.at(5);
  903 + size_t count = pages.size();
  904 + pdf.addPage(newpage, false);
  905 + auto last = pages.back();
  906 + assert(pages.size() == count + 1);
  907 + assert(! (last.getObjGen() == newpage.getObjGen()));
  908 + assert(last.getKey("/Contents").getObjGen() ==
  909 + newpage.getKey("/Contents").getObjGen());
  910 +}
  911 +
  912 +static void test_20(QPDF& pdf, char const* arg2)
  913 +{
  914 + // Shallow copy an array
  915 + QPDFObjectHandle trailer = pdf.getTrailer();
  916 + QPDFObjectHandle qtest = trailer.getKey("/QTest");
  917 + QPDFObjectHandle copy = qtest.shallowCopy();
  918 + // Append shallow copy of a scalar
  919 + copy.appendItem(trailer.getKey("/Size").shallowCopy());
  920 + trailer.replaceKey("/QTest2", copy);
  921 +
  922 + QPDFWriter w(pdf, "a.pdf");
  923 + w.setStaticID(true);
  924 + w.setStreamDataMode(qpdf_s_preserve);
  925 + w.write();
  926 +}
  927 +
  928 +static void test_21(QPDF& pdf, char const* arg2)
  929 +{
  930 + // Try to shallow copy a stream
  931 + std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
  932 + QPDFObjectHandle page = pages.at(0);
  933 + QPDFObjectHandle contents = page.getKey("/Contents");
  934 + contents.shallowCopy();
  935 + std::cout << "you can't see this" << std::endl;
  936 +}
  937 +
  938 +static void test_22(QPDF& pdf, char const* arg2)
  939 +{
  940 + // Try to remove a page we don't have
  941 + QPDFPageDocumentHelper dh(pdf);
  942 + std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
  943 + QPDFPageObjectHelper& page = pages.at(0);
  944 + dh.removePage(page);
  945 + dh.removePage(page);
  946 + std::cout << "you can't see this" << std::endl;
  947 +}
  948 +
  949 +static void test_23(QPDF& pdf, char const* arg2)
  950 +{
  951 + QPDFPageDocumentHelper dh(pdf);
  952 + std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
  953 + dh.removePage(pages.back());
  954 +}
1096 955  
1097   - QPDFWriter w(pdf, "a.pdf");
1098   - w.setStaticID(true);
1099   - w.setStreamDataMode(qpdf_s_preserve);
1100   - w.write();
  956 +static void test_24(QPDF& pdf, char const* arg2)
  957 +{
  958 + // Test behavior of reserved objects
  959 + QPDFObjectHandle res1 = QPDFObjectHandle::newReserved(&pdf);
  960 + QPDFObjectHandle res2 = QPDFObjectHandle::newReserved(&pdf);
  961 + QPDFObjectHandle trailer = pdf.getTrailer();
  962 + trailer.replaceKey("Array1", res1);
  963 + trailer.replaceKey("Array2", res2);
  964 +
  965 + QPDFObjectHandle array1 = QPDFObjectHandle::newArray();
  966 + QPDFObjectHandle array2 = QPDFObjectHandle::newArray();
  967 + array1.appendItem(res2);
  968 + array1.appendItem(QPDFObjectHandle::newInteger(1));
  969 + array2.appendItem(res1);
  970 + array2.appendItem(QPDFObjectHandle::newInteger(2));
  971 + // Make sure trying to ask questions about a reserved object
  972 + // doesn't break it.
  973 + if (res1.isArray())
  974 + {
  975 + std::cout << "oops -- res1 is an array" << std::endl;
  976 + }
  977 + if (res1.isReserved())
  978 + {
  979 + std::cout << "res1 is still reserved after checking if array"
  980 + << std::endl;
  981 + }
  982 + pdf.replaceReserved(res1, array1);
  983 + if (res1.isReserved())
  984 + {
  985 + std::cout << "oops -- res1 is still reserved" << std::endl;
  986 + }
  987 + else
  988 + {
  989 + std::cout << "res1 is no longer reserved" << std::endl;
  990 + }
  991 + res1.assertArray();
  992 + std::cout << "res1 is an array" << std::endl;
  993 +
  994 + try
  995 + {
  996 + res2.unparseResolved();
  997 + std::cout << "oops -- didn't throw" << std::endl;
  998 + }
  999 + catch (std::logic_error const& e)
  1000 + {
  1001 + std::cout << "logic error: " << e.what() << std::endl;
1101 1002 }
1102   - else if (n == 25)
  1003 + try
  1004 + {
  1005 + res2.makeDirect();
  1006 + std::cout << "oops -- didn't throw" << std::endl;
  1007 + }
  1008 + catch (std::logic_error const& e)
1103 1009 {
1104   - // The copy object tests are designed to work with a specific
1105   - // file. Look at the test suite for the file, and look at the
1106   - // file for comments about the file's structure.
  1010 + std::cout << "logic error: " << e.what() << std::endl;
  1011 + }
1107 1012  
1108   - // Copy qtest without crossing page boundaries. Should get O1
1109   - // and O2 and their streams but not O3 or any other pages.
  1013 + pdf.replaceReserved(res2, array2);
1110 1014  
1111   - assert(arg2 != 0);
1112   - {
1113   - // Make sure original PDF is out of scope when we write.
1114   - QPDF oldpdf;
1115   - oldpdf.processFile(arg2);
1116   - QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
1117   - pdf.getTrailer().replaceKey(
1118   - "/QTest", pdf.copyForeignObject(qtest));
1119   - }
  1015 + res2.assertArray();
  1016 + std::cout << "res2 is an array" << std::endl;
1120 1017  
1121   - QPDFWriter w(pdf, "a.pdf");
1122   - w.setStaticID(true);
1123   - w.setStreamDataMode(qpdf_s_preserve);
1124   - w.write();
1125   - }
1126   - else if (n == 26)
  1018 + // Verify that the previously added reserved keys can be
  1019 + // dereferenced properly now
  1020 + int i1 = res1.getArrayItem(0).getArrayItem(1).getIntValueAsInt();
  1021 + int i2 = res2.getArrayItem(0).getArrayItem(1).getIntValueAsInt();
  1022 + if ((i1 == 2) && (i2 == 1))
1127 1023 {
1128   - // Copy the O3 page using addPage. Copy qtest without
1129   - // crossing page boundaries. In addition to previous results,
1130   - // should get page O3 but no other pages including the page
1131   - // that O3 points to. Also, inherited object will have been
1132   - // pushed down and will be preserved.
  1024 + std::cout << "circular access and lazy resolution worked" << std::endl;
  1025 + }
1133 1026  
1134   - {
1135   - // Make sure original PDF is out of scope when we write.
1136   - assert(arg2 != 0);
1137   - QPDF oldpdf;
1138   - oldpdf.processFile(arg2);
1139   - QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
1140   - QPDFObjectHandle O3 = qtest.getKey("/O3");
1141   - QPDFPageDocumentHelper(pdf).addPage(O3, false);
1142   - pdf.getTrailer().replaceKey(
1143   - "/QTest", pdf.copyForeignObject(qtest));
1144   - }
  1027 + QPDFWriter w(pdf, "a.pdf");
  1028 + w.setStaticID(true);
  1029 + w.setStreamDataMode(qpdf_s_preserve);
  1030 + w.write();
  1031 +}
1145 1032  
1146   - QPDFWriter w(pdf, "a.pdf");
1147   - w.setStaticID(true);
1148   - w.setStreamDataMode(qpdf_s_preserve);
1149   - w.write();
  1033 +static void test_25(QPDF& pdf, char const* arg2)
  1034 +{
  1035 + // The copy object tests are designed to work with a specific
  1036 + // file. Look at the test suite for the file, and look at the
  1037 + // file for comments about the file's structure.
  1038 +
  1039 + // Copy qtest without crossing page boundaries. Should get O1
  1040 + // and O2 and their streams but not O3 or any other pages.
  1041 +
  1042 + assert(arg2 != 0);
  1043 + {
  1044 + // Make sure original PDF is out of scope when we write.
  1045 + QPDF oldpdf;
  1046 + oldpdf.processFile(arg2);
  1047 + QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
  1048 + pdf.getTrailer().replaceKey(
  1049 + "/QTest", pdf.copyForeignObject(qtest));
1150 1050 }
1151   - else if (n == 27)
  1051 +
  1052 + QPDFWriter w(pdf, "a.pdf");
  1053 + w.setStaticID(true);
  1054 + w.setStreamDataMode(qpdf_s_preserve);
  1055 + w.write();
  1056 +}
  1057 +
  1058 +static void test_26(QPDF& pdf, char const* arg2)
  1059 +{
  1060 + // Copy the O3 page using addPage. Copy qtest without
  1061 + // crossing page boundaries. In addition to previous results,
  1062 + // should get page O3 but no other pages including the page
  1063 + // that O3 points to. Also, inherited object will have been
  1064 + // pushed down and will be preserved.
  1065 +
1152 1066 {
1153   - // Copy O3 and the page O3 refers to before copying qtest.
1154   - // Should get qtest plus only the O3 page and the page that O3
1155   - // points to. Inherited objects should be preserved. This test
1156   - // also exercises copying from a stream that has a buffer and
1157   - // a provider, including copying a provider multiple times. We
1158   - // also exercise setImmediateCopyFrom.
  1067 + // Make sure original PDF is out of scope when we write.
  1068 + assert(arg2 != 0);
  1069 + QPDF oldpdf;
  1070 + oldpdf.processFile(arg2);
  1071 + QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
  1072 + QPDFObjectHandle O3 = qtest.getKey("/O3");
  1073 + QPDFPageDocumentHelper(pdf).addPage(O3, false);
  1074 + pdf.getTrailer().replaceKey(
  1075 + "/QTest", pdf.copyForeignObject(qtest));
  1076 + }
  1077 +
  1078 + QPDFWriter w(pdf, "a.pdf");
  1079 + w.setStaticID(true);
  1080 + w.setStreamDataMode(qpdf_s_preserve);
  1081 + w.write();
  1082 +}
1159 1083  
1160   - // Create a provider. The provider stays in scope.
1161   - PointerHolder<QPDFObjectHandle::StreamDataProvider> p1;
  1084 +static void test_27(QPDF& pdf, char const* arg2)
  1085 +{
  1086 + // Copy O3 and the page O3 refers to before copying qtest.
  1087 + // Should get qtest plus only the O3 page and the page that O3
  1088 + // points to. Inherited objects should be preserved. This test
  1089 + // also exercises copying from a stream that has a buffer and
  1090 + // a provider, including copying a provider multiple times. We
  1091 + // also exercise setImmediateCopyFrom.
  1092 +
  1093 + // Create a provider. The provider stays in scope.
  1094 + PointerHolder<QPDFObjectHandle::StreamDataProvider> p1;
  1095 + {
  1096 + // Local scope
  1097 + Pl_Buffer pl("buffer");
  1098 + pl.write(QUtil::unsigned_char_pointer("new data for stream\n"),
  1099 + 20); // no null!
  1100 + pl.finish();
  1101 + PointerHolder<Buffer> b = pl.getBuffer();
  1102 + Provider* provider = new Provider(b);
  1103 + p1 = provider;
  1104 + }
  1105 + // Create a stream that uses a provider in empty1 and copy it
  1106 + // to empty2. It is copied from empty2 to the final pdf.
  1107 + QPDF empty1;
  1108 + empty1.emptyPDF();
  1109 + QPDFObjectHandle s1 = QPDFObjectHandle::newStream(&empty1);
  1110 + s1.replaceStreamData(
  1111 + p1, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
  1112 + QPDF empty2;
  1113 + empty2.emptyPDF();
  1114 + s1 = empty2.copyForeignObject(s1);
  1115 + {
  1116 + // Make sure some source PDFs are out of scope when we
  1117 + // write.
  1118 +
  1119 + PointerHolder<QPDFObjectHandle::StreamDataProvider> p2;
  1120 + // Create another provider. This one will go out of scope
  1121 + // along with its containing qpdf, which has
  1122 + // setImmediateCopyFrom(true).
1162 1123 {
1163 1124 // Local scope
1164 1125 Pl_Buffer pl("buffer");
1165   - pl.write(QUtil::unsigned_char_pointer("new data for stream\n"),
1166   - 20); // no null!
  1126 + pl.write(QUtil::unsigned_char_pointer(
  1127 + "more data for stream\n"),
  1128 + 21); // no null!
1167 1129 pl.finish();
1168 1130 PointerHolder<Buffer> b = pl.getBuffer();
1169 1131 Provider* provider = new Provider(b);
1170   - p1 = provider;
1171   - }
1172   - // Create a stream that uses a provider in empty1 and copy it
1173   - // to empty2. It is copied from empty2 to the final pdf.
1174   - QPDF empty1;
1175   - empty1.emptyPDF();
1176   - QPDFObjectHandle s1 = QPDFObjectHandle::newStream(&empty1);
1177   - s1.replaceStreamData(
1178   - p1, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
1179   - QPDF empty2;
1180   - empty2.emptyPDF();
1181   - s1 = empty2.copyForeignObject(s1);
1182   - {
1183   - // Make sure some source PDFs are out of scope when we
1184   - // write.
1185   -
1186   - PointerHolder<QPDFObjectHandle::StreamDataProvider> p2;
1187   - // Create another provider. This one will go out of scope
1188   - // along with its containing qpdf, which has
1189   - // setImmediateCopyFrom(true).
1190   - {
1191   - // Local scope
1192   - Pl_Buffer pl("buffer");
1193   - pl.write(QUtil::unsigned_char_pointer(
1194   - "more data for stream\n"),
1195   - 21); // no null!
1196   - pl.finish();
1197   - PointerHolder<Buffer> b = pl.getBuffer();
1198   - Provider* provider = new Provider(b);
1199   - p2 = provider;
1200   - }
1201   - QPDF empty3;
1202   - empty3.emptyPDF();
1203   - empty3.setImmediateCopyFrom(true);
1204   - QPDFObjectHandle s3 = QPDFObjectHandle::newStream(&empty3);
1205   - s3.replaceStreamData(
1206   - p2, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
1207   - assert(arg2 != 0);
1208   - QPDF oldpdf;
1209   - oldpdf.processFile(arg2);
1210   - QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
1211   - QPDFObjectHandle O3 = qtest.getKey("/O3");
1212   - QPDFPageDocumentHelper dh(pdf);
1213   - dh.addPage(O3.getKey("/OtherPage"), false);
1214   - dh.addPage(O3, false);
1215   - QPDFObjectHandle s2 = QPDFObjectHandle::newStream(
1216   - &oldpdf, "potato\n");
1217   - pdf.getTrailer().replaceKey(
1218   - "/QTest", pdf.copyForeignObject(qtest));
1219   - pdf.getTrailer().replaceKey(
1220   - "/QTest2", QPDFObjectHandle::newArray());
1221   - pdf.getTrailer().getKey("/QTest2").appendItem(
1222   - pdf.copyForeignObject(s1));
1223   - pdf.getTrailer().getKey("/QTest2").appendItem(
1224   - pdf.copyForeignObject(s2));
1225   - pdf.getTrailer().getKey("/QTest2").appendItem(
1226   - pdf.copyForeignObject(s3));
1227   - }
  1132 + p2 = provider;
  1133 + }
  1134 + QPDF empty3;
  1135 + empty3.emptyPDF();
  1136 + empty3.setImmediateCopyFrom(true);
  1137 + QPDFObjectHandle s3 = QPDFObjectHandle::newStream(&empty3);
  1138 + s3.replaceStreamData(
  1139 + p2, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
  1140 + assert(arg2 != 0);
  1141 + QPDF oldpdf;
  1142 + oldpdf.processFile(arg2);
  1143 + QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
  1144 + QPDFObjectHandle O3 = qtest.getKey("/O3");
  1145 + QPDFPageDocumentHelper dh(pdf);
  1146 + dh.addPage(O3.getKey("/OtherPage"), false);
  1147 + dh.addPage(O3, false);
  1148 + QPDFObjectHandle s2 = QPDFObjectHandle::newStream(
  1149 + &oldpdf, "potato\n");
  1150 + pdf.getTrailer().replaceKey(
  1151 + "/QTest", pdf.copyForeignObject(qtest));
  1152 + pdf.getTrailer().replaceKey(
  1153 + "/QTest2", QPDFObjectHandle::newArray());
  1154 + pdf.getTrailer().getKey("/QTest2").appendItem(
  1155 + pdf.copyForeignObject(s1));
  1156 + pdf.getTrailer().getKey("/QTest2").appendItem(
  1157 + pdf.copyForeignObject(s2));
  1158 + pdf.getTrailer().getKey("/QTest2").appendItem(
  1159 + pdf.copyForeignObject(s3));
  1160 + }
  1161 +
  1162 + QPDFWriter w(pdf, "a.pdf");
  1163 + w.setStaticID(true);
  1164 + w.setCompressStreams(false);
  1165 + w.setDecodeLevel(qpdf_dl_generalized);
  1166 + w.write();
  1167 +}
1228 1168  
1229   - QPDFWriter w(pdf, "a.pdf");
1230   - w.setStaticID(true);
1231   - w.setCompressStreams(false);
1232   - w.setDecodeLevel(qpdf_dl_generalized);
1233   - w.write();
  1169 +static void test_28(QPDF& pdf, char const* arg2)
  1170 +{
  1171 + // Copy foreign object errors
  1172 + try
  1173 + {
  1174 + pdf.copyForeignObject(pdf.getTrailer().getKey("/QTest"));
  1175 + std::cout << "oops -- didn't throw" << std::endl;
1234 1176 }
1235   - else if (n == 28)
  1177 + catch (std::logic_error const& e)
1236 1178 {
1237   - // Copy foreign object errors
1238   - try
1239   - {
1240   - pdf.copyForeignObject(pdf.getTrailer().getKey("/QTest"));
1241   - std::cout << "oops -- didn't throw" << std::endl;
1242   - }
1243   - catch (std::logic_error const& e)
1244   - {
1245   - std::cout << "logic error: " << e.what() << std::endl;
1246   - }
1247   - try
1248   - {
1249   - pdf.copyForeignObject(QPDFObjectHandle::newInteger(1));
1250   - std::cout << "oops -- didn't throw" << std::endl;
1251   - }
1252   - catch (std::logic_error const& e)
1253   - {
1254   - std::cout << "logic error: " << e.what() << std::endl;
1255   - }
  1179 + std::cout << "logic error: " << e.what() << std::endl;
  1180 + }
  1181 + try
  1182 + {
  1183 + pdf.copyForeignObject(QPDFObjectHandle::newInteger(1));
  1184 + std::cout << "oops -- didn't throw" << std::endl;
1256 1185 }
1257   - else if (n == 29)
  1186 + catch (std::logic_error const& e)
1258 1187 {
1259   - // Detect mixed objects in QPDFWriter
1260   - assert(arg2 != 0);
1261   - QPDF other;
1262   - other.processFile(arg2);
1263   - // Should use copyForeignObject instead
1264   - other.getTrailer().replaceKey(
1265   - "/QTest", pdf.getTrailer().getKey("/QTest"));
  1188 + std::cout << "logic error: " << e.what() << std::endl;
  1189 + }
  1190 +}
1266 1191  
1267   - try
1268   - {
1269   - QPDFWriter w(other, "a.pdf");
1270   - w.write();
1271   - std::cout << "oops -- didn't throw" << std::endl;
1272   - }
1273   - catch (std::logic_error const& e)
1274   - {
1275   - std::cout << "logic error: " << e.what() << std::endl;
1276   - }
  1192 +static void test_29(QPDF& pdf, char const* arg2)
  1193 +{
  1194 + // Detect mixed objects in QPDFWriter
  1195 + assert(arg2 != 0);
  1196 + QPDF other;
  1197 + other.processFile(arg2);
  1198 + // Should use copyForeignObject instead
  1199 + other.getTrailer().replaceKey(
  1200 + "/QTest", pdf.getTrailer().getKey("/QTest"));
1277 1201  
1278   - // Detect adding a foreign object
1279   - auto root1 = pdf.getRoot();
1280   - auto root2 = other.getRoot();
1281   - try
1282   - {
1283   - root1.replaceKey("/Oops", root2);
1284   - }
1285   - catch (std::logic_error const& e)
1286   - {
1287   - std::cout << "logic error: " << e.what() << std::endl;
1288   - }
  1202 + try
  1203 + {
  1204 + QPDFWriter w(other, "a.pdf");
  1205 + w.write();
  1206 + std::cout << "oops -- didn't throw" << std::endl;
1289 1207 }
1290   - else if (n == 30)
  1208 + catch (std::logic_error const& e)
1291 1209 {
1292   - assert(arg2 != 0);
1293   - QPDF encrypted;
1294   - encrypted.processFile(arg2, "user");
1295   - QPDFWriter w(pdf, "b.pdf");
1296   - w.setStreamDataMode(qpdf_s_preserve);
1297   - w.copyEncryptionParameters(encrypted);
1298   - w.write();
1299   -
1300   - // Make sure the contents are actually the same
1301   - QPDF final;
1302   - final.processFile("b.pdf", "user");
1303   - std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
1304   - std::string orig_contents = getPageContents(pages.at(0));
1305   - pages = final.getAllPages();
1306   - std::string new_contents = getPageContents(pages.at(0));
1307   - if (orig_contents != new_contents)
1308   - {
1309   - std::cout << "oops -- page contents don't match" << std::endl
1310   - << "original:\n" << orig_contents
1311   - << "new:\n" << new_contents
1312   - << std::endl;
1313   - }
  1210 + std::cout << "logic error: " << e.what() << std::endl;
  1211 + }
  1212 +
  1213 + // Detect adding a foreign object
  1214 + auto root1 = pdf.getRoot();
  1215 + auto root2 = other.getRoot();
  1216 + try
  1217 + {
  1218 + root1.replaceKey("/Oops", root2);
1314 1219 }
1315   - else if (n == 31)
  1220 + catch (std::logic_error const& e)
1316 1221 {
1317   - // Test object parsing from a string. The input file is not used.
  1222 + std::cout << "logic error: " << e.what() << std::endl;
  1223 + }
  1224 +}
1318 1225  
1319   - QPDFObjectHandle o1 =
1320   - QPDFObjectHandle::parse(
1321   - "[/name 16059 3.14159 false\n"
1322   - " << /key true /other [ (string1) (string2) ] >> null]");
1323   - std::cout << o1.unparse() << std::endl;
1324   - QPDFObjectHandle o2 = QPDFObjectHandle::parse(" 12345 \f ");
1325   - assert(o2.isInteger() && (o2.getIntValue() == 12345));
1326   - try
1327   - {
1328   - QPDFObjectHandle::parse("[1 0 R]", "indirect test");
1329   - std::cout << "oops -- didn't throw" << std::endl;
1330   - }
1331   - catch (std::logic_error const& e)
1332   - {
1333   - std::cout << "logic error parsing indirect: " << e.what()
1334   - << std::endl;
1335   - }
1336   - try
1337   - {
1338   - QPDFObjectHandle::parse("0 trailing", "trailing test");
1339   - std::cout << "oops -- didn't throw" << std::endl;
1340   - }
1341   - catch (std::runtime_error const& e)
1342   - {
1343   - std::cout << "trailing data: " << e.what()
1344   - << std::endl;
1345   - }
1346   - assert(QPDFObjectHandle::parse(
1347   - &pdf, "[1 0 R]", "indirect test").unparse() ==
1348   - "[ 1 0 R ]");
  1226 +static void test_30(QPDF& pdf, char const* arg2)
  1227 +{
  1228 + assert(arg2 != 0);
  1229 + QPDF encrypted;
  1230 + encrypted.processFile(arg2, "user");
  1231 + QPDFWriter w(pdf, "b.pdf");
  1232 + w.setStreamDataMode(qpdf_s_preserve);
  1233 + w.copyEncryptionParameters(encrypted);
  1234 + w.write();
  1235 +
  1236 + // Make sure the contents are actually the same
  1237 + QPDF final;
  1238 + final.processFile("b.pdf", "user");
  1239 + std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
  1240 + std::string orig_contents = getPageContents(pages.at(0));
  1241 + pages = final.getAllPages();
  1242 + std::string new_contents = getPageContents(pages.at(0));
  1243 + if (orig_contents != new_contents)
  1244 + {
  1245 + std::cout << "oops -- page contents don't match" << std::endl
  1246 + << "original:\n" << orig_contents
  1247 + << "new:\n" << new_contents
  1248 + << std::endl;
1349 1249 }
1350   - else if (n == 32)
  1250 +}
  1251 +
  1252 +static void test_31(QPDF& pdf, char const* arg2)
  1253 +{
  1254 + // Test object parsing from a string. The input file is not used.
  1255 +
  1256 + QPDFObjectHandle o1 =
  1257 + QPDFObjectHandle::parse(
  1258 + "[/name 16059 3.14159 false\n"
  1259 + " << /key true /other [ (string1) (string2) ] >> null]");
  1260 + std::cout << o1.unparse() << std::endl;
  1261 + QPDFObjectHandle o2 = QPDFObjectHandle::parse(" 12345 \f ");
  1262 + assert(o2.isInteger() && (o2.getIntValue() == 12345));
  1263 + try
1351 1264 {
1352   - // Extra header text
1353   - char const* filenames[] = {"a.pdf", "b.pdf", "c.pdf", "d.pdf"};
1354   - for (int i = 0; i < 4; ++i)
1355   - {
1356   - bool linearized = ((i & 1) != 0);
1357   - bool newline = ((i & 2) != 0);
1358   - QPDFWriter w(pdf, filenames[i]);
1359   - w.setStaticID(true);
1360   - std::cout
1361   - << "file: " << filenames[i] << std::endl
1362   - << "linearized: " << (linearized ? "yes" : "no") << std::endl
1363   - << "newline: " << (newline ? "yes" : "no") << std::endl;
1364   - w.setLinearization(linearized);
1365   - w.setExtraHeaderText(newline
1366   - ? "%% Comment with newline\n"
1367   - : "%% Comment\n% No newline");
1368   - w.write();
1369   - }
  1265 + QPDFObjectHandle::parse("[1 0 R]", "indirect test");
  1266 + std::cout << "oops -- didn't throw" << std::endl;
1370 1267 }
1371   - else if (n == 33)
  1268 + catch (std::logic_error const& e)
1372 1269 {
1373   - // Test writing to a custom pipeline
1374   - Pl_Buffer p("buffer");
1375   - QPDFWriter w(pdf);
1376   - w.setStaticID(true);
1377   - w.setOutputPipeline(&p);
1378   - w.write();
1379   - PointerHolder<Buffer> b = p.getBuffer();
1380   - FILE* f = QUtil::safe_fopen("a.pdf", "wb");
1381   - fwrite(b->getBuffer(), b->getSize(), 1, f);
1382   - fclose(f);
  1270 + std::cout << "logic error parsing indirect: " << e.what()
  1271 + << std::endl;
1383 1272 }
1384   - else if (n == 34)
  1273 + try
1385 1274 {
1386   - // Look at Extensions dictionary
1387   - std::cout << "version: " << pdf.getPDFVersion() << std::endl
1388   - << "extension level: " << pdf.getExtensionLevel() << std::endl
1389   - << pdf.getRoot().getKey("/Extensions").unparse() << std::endl;
  1275 + QPDFObjectHandle::parse("0 trailing", "trailing test");
  1276 + std::cout << "oops -- didn't throw" << std::endl;
1390 1277 }
1391   - else if (n == 35)
  1278 + catch (std::runtime_error const& e)
1392 1279 {
1393   - // Extract attachments
  1280 + std::cout << "trailing data: " << e.what()
  1281 + << std::endl;
  1282 + }
  1283 + assert(QPDFObjectHandle::parse(
  1284 + &pdf, "[1 0 R]", "indirect test").unparse() ==
  1285 + "[ 1 0 R ]");
  1286 +}
1394 1287  
1395   - std::map<std::string, PointerHolder<Buffer> > attachments;
1396   - QPDFObjectHandle root = pdf.getRoot();
1397   - QPDFObjectHandle names = root.getKey("/Names");
1398   - QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
1399   - names = embeddedFiles.getKey("/Names");
1400   - for (int i = 0; i < names.getArrayNItems(); ++i)
1401   - {
1402   - QPDFObjectHandle item = names.getArrayItem(i);
1403   - if (item.isDictionary() &&
1404   - item.getKey("/Type").isName() &&
1405   - (item.getKey("/Type").getName() == "/Filespec") &&
1406   - item.getKey("/EF").isDictionary() &&
1407   - item.getKey("/EF").getKey("/F").isStream())
  1288 +static void test_32(QPDF& pdf, char const* arg2)
  1289 +{
  1290 + // Extra header text
  1291 + char const* filenames[] = {"a.pdf", "b.pdf", "c.pdf", "d.pdf"};
  1292 + for (int i = 0; i < 4; ++i)
  1293 + {
  1294 + bool linearized = ((i & 1) != 0);
  1295 + bool newline = ((i & 2) != 0);
  1296 + QPDFWriter w(pdf, filenames[i]);
  1297 + w.setStaticID(true);
  1298 + std::cout
  1299 + << "file: " << filenames[i] << std::endl
  1300 + << "linearized: " << (linearized ? "yes" : "no") << std::endl
  1301 + << "newline: " << (newline ? "yes" : "no") << std::endl;
  1302 + w.setLinearization(linearized);
  1303 + w.setExtraHeaderText(newline
  1304 + ? "%% Comment with newline\n"
  1305 + : "%% Comment\n% No newline");
  1306 + w.write();
  1307 + }
  1308 +}
  1309 +
  1310 +static void test_33(QPDF& pdf, char const* arg2)
  1311 +{
  1312 + // Test writing to a custom pipeline
  1313 + Pl_Buffer p("buffer");
  1314 + QPDFWriter w(pdf);
  1315 + w.setStaticID(true);
  1316 + w.setOutputPipeline(&p);
  1317 + w.write();
  1318 + PointerHolder<Buffer> b = p.getBuffer();
  1319 + FILE* f = QUtil::safe_fopen("a.pdf", "wb");
  1320 + fwrite(b->getBuffer(), b->getSize(), 1, f);
  1321 + fclose(f);
  1322 +}
  1323 +
  1324 +static void test_34(QPDF& pdf, char const* arg2)
  1325 +{
  1326 + // Look at Extensions dictionary
  1327 + std::cout << "version: " << pdf.getPDFVersion() << std::endl
  1328 + << "extension level: " << pdf.getExtensionLevel() << std::endl
  1329 + << pdf.getRoot().getKey("/Extensions").unparse() << std::endl;
  1330 +}
  1331 +
  1332 +static void test_35(QPDF& pdf, char const* arg2)
  1333 +{
  1334 + // Extract attachments
  1335 +
  1336 + std::map<std::string, PointerHolder<Buffer> > attachments;
  1337 + QPDFObjectHandle root = pdf.getRoot();
  1338 + QPDFObjectHandle names = root.getKey("/Names");
  1339 + QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
  1340 + names = embeddedFiles.getKey("/Names");
  1341 + for (int i = 0; i < names.getArrayNItems(); ++i)
  1342 + {
  1343 + QPDFObjectHandle item = names.getArrayItem(i);
  1344 + if (item.isDictionary() &&
  1345 + item.getKey("/Type").isName() &&
  1346 + (item.getKey("/Type").getName() == "/Filespec") &&
  1347 + item.getKey("/EF").isDictionary() &&
  1348 + item.getKey("/EF").getKey("/F").isStream())
  1349 + {
  1350 + std::string filename = item.getKey("/F").getStringValue();
  1351 + QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
  1352 + attachments[filename] = stream.getStreamData();
  1353 + }
  1354 + }
  1355 + for (std::map<std::string, PointerHolder<Buffer> >::iterator iter =
  1356 + attachments.begin(); iter != attachments.end(); ++iter)
  1357 + {
  1358 + std::string const& filename = (*iter).first;
  1359 + std::string data = std::string(
  1360 + reinterpret_cast<char const*>((*iter).second->getBuffer()),
  1361 + (*iter).second->getSize());
  1362 + bool is_binary = false;
  1363 + for (size_t i = 0; i < data.size(); ++i)
  1364 + {
  1365 + if ((data.at(i) < 0) || (data.at(i) > 126))
1408 1366 {
1409   - std::string filename = item.getKey("/F").getStringValue();
1410   - QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
1411   - attachments[filename] = stream.getStreamData();
  1367 + is_binary = true;
  1368 + break;
1412 1369 }
1413 1370 }
1414   - for (std::map<std::string, PointerHolder<Buffer> >::iterator iter =
1415   - attachments.begin(); iter != attachments.end(); ++iter)
  1371 + if (is_binary)
1416 1372 {
1417   - std::string const& filename = (*iter).first;
1418   - std::string data = std::string(
1419   - reinterpret_cast<char const*>((*iter).second->getBuffer()),
1420   - (*iter).second->getSize());
1421   - bool is_binary = false;
1422   - for (size_t i = 0; i < data.size(); ++i)
  1373 + std::string t;
  1374 + for (size_t i = 0;
  1375 + i < std::min(data.size(), QIntC::to_size(20));
  1376 + ++i)
1423 1377 {
1424   - if ((data.at(i) < 0) || (data.at(i) > 126))
  1378 + if ((data.at(i) >= 32) && (data.at(i) <= 126))
1425 1379 {
1426   - is_binary = true;
1427   - break;
  1380 + t += data.at(i);
1428 1381 }
1429   - }
1430   - if (is_binary)
1431   - {
1432   - std::string t;
1433   - for (size_t i = 0;
1434   - i < std::min(data.size(), QIntC::to_size(20));
1435   - ++i)
  1382 + else
1436 1383 {
1437   - if ((data.at(i) >= 32) && (data.at(i) <= 126))
1438   - {
1439   - t += data.at(i);
1440   - }
1441   - else
1442   - {
1443   - t += ".";
1444   - }
  1384 + t += ".";
1445 1385 }
1446   - t += " (" + QUtil::uint_to_string(data.size()) + " bytes)";
1447   - data = t;
1448 1386 }
1449   - std::cout << filename << ":\n" << data << "--END--\n";
  1387 + t += " (" + QUtil::uint_to_string(data.size()) + " bytes)";
  1388 + data = t;
1450 1389 }
  1390 + std::cout << filename << ":\n" << data << "--END--\n";
1451 1391 }
1452   - else if (n == 36)
1453   - {
1454   - // Extract raw unfilterable attachment
  1392 +}
1455 1393  
1456   - QPDFObjectHandle root = pdf.getRoot();
1457   - QPDFObjectHandle names = root.getKey("/Names");
1458   - QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
1459   - names = embeddedFiles.getKey("/Names");
1460   - for (int i = 0; i < names.getArrayNItems(); ++i)
1461   - {
1462   - QPDFObjectHandle item = names.getArrayItem(i);
1463   - if (item.isDictionary() &&
1464   - item.getKey("/Type").isName() &&
1465   - (item.getKey("/Type").getName() == "/Filespec") &&
1466   - item.getKey("/EF").isDictionary() &&
1467   - item.getKey("/EF").getKey("/F").isStream() &&
1468   - (item.getKey("/F").getStringValue() == "attachment1.txt"))
1469   - {
1470   - std::string filename = item.getKey("/F").getStringValue();
1471   - QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
1472   - Pl_Buffer p1("buffer");
1473   - Pl_Flate p2("compress", &p1, Pl_Flate::a_inflate);
1474   - stream.pipeStreamData(&p2, 0, qpdf_dl_none);
1475   - PointerHolder<Buffer> buf = p1.getBuffer();
1476   - std::string data = std::string(
1477   - reinterpret_cast<char const*>(buf->getBuffer()),
1478   - buf->getSize());
1479   - std::cout << stream.getDict().unparse()
1480   - << filename << ":\n" << data << "--END--\n";
1481   - }
1482   - }
1483   - }
1484   - else if (n == 37)
1485   - {
1486   - // Parse content streams of all pages
1487   - std::vector<QPDFPageObjectHelper> pages =
1488   - QPDFPageDocumentHelper(pdf).getAllPages();
1489   - for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
1490   - iter != pages.end(); ++iter)
1491   - {
1492   - QPDFPageObjectHelper& page(*iter);
1493   - ParserCallbacks cb;
1494   - page.parseContents(&cb);
1495   - }
1496   - }
1497   - else if (n == 38)
1498   - {
1499   - // Designed for override-compressed-object.pdf
1500   - QPDFObjectHandle qtest = pdf.getRoot().getKey("/QTest");
1501   - for (int i = 0; i < qtest.getArrayNItems(); ++i)
1502   - {
1503   - std::cout << qtest.getArrayItem(i).unparseResolved() << std::endl;
1504   - }
1505   - }
1506   - else if (n == 39)
1507   - {
1508   - // Display image filter and color set for each image on each page
1509   - std::vector<QPDFPageObjectHelper> pages =
1510   - QPDFPageDocumentHelper(pdf).getAllPages();
1511   - int pageno = 0;
1512   - for (std::vector<QPDFPageObjectHelper>::iterator p_iter =
1513   - pages.begin();
1514   - p_iter != pages.end(); ++p_iter)
1515   - {
1516   - std::cout << "page " << ++pageno << std::endl;
1517   - std::map<std::string, QPDFObjectHandle> images =
1518   - (*p_iter).getImages();
1519   - for (std::map<std::string, QPDFObjectHandle>::iterator i_iter =
1520   - images.begin(); i_iter != images.end(); ++i_iter)
1521   - {
1522   - QPDFObjectHandle image_dict = (*i_iter).second.getDict();
1523   - std::cout << "filter: "
1524   - << image_dict.getKey("/Filter").unparseResolved()
1525   - << ", color space: "
1526   - << image_dict.getKey("/ColorSpace").unparseResolved()
1527   - << std::endl;
1528   - }
  1394 +static void test_36(QPDF& pdf, char const* arg2)
  1395 +{
  1396 + // Extract raw unfilterable attachment
  1397 +
  1398 + QPDFObjectHandle root = pdf.getRoot();
  1399 + QPDFObjectHandle names = root.getKey("/Names");
  1400 + QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
  1401 + names = embeddedFiles.getKey("/Names");
  1402 + for (int i = 0; i < names.getArrayNItems(); ++i)
  1403 + {
  1404 + QPDFObjectHandle item = names.getArrayItem(i);
  1405 + if (item.isDictionary() &&
  1406 + item.getKey("/Type").isName() &&
  1407 + (item.getKey("/Type").getName() == "/Filespec") &&
  1408 + item.getKey("/EF").isDictionary() &&
  1409 + item.getKey("/EF").getKey("/F").isStream() &&
  1410 + (item.getKey("/F").getStringValue() == "attachment1.txt"))
  1411 + {
  1412 + std::string filename = item.getKey("/F").getStringValue();
  1413 + QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
  1414 + Pl_Buffer p1("buffer");
  1415 + Pl_Flate p2("compress", &p1, Pl_Flate::a_inflate);
  1416 + stream.pipeStreamData(&p2, 0, qpdf_dl_none);
  1417 + PointerHolder<Buffer> buf = p1.getBuffer();
  1418 + std::string data = std::string(
  1419 + reinterpret_cast<char const*>(buf->getBuffer()),
  1420 + buf->getSize());
  1421 + std::cout << stream.getDict().unparse()
  1422 + << filename << ":\n" << data << "--END--\n";
1529 1423 }
1530 1424 }
1531   - else if (n == 40)
  1425 +}
  1426 +
  1427 +static void test_37(QPDF& pdf, char const* arg2)
  1428 +{
  1429 + // Parse content streams of all pages
  1430 + std::vector<QPDFPageObjectHelper> pages =
  1431 + QPDFPageDocumentHelper(pdf).getAllPages();
  1432 + for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
  1433 + iter != pages.end(); ++iter)
1532 1434 {
1533   - // Write PCLm. This requires specially crafted PDF files. This
1534   - // feature was implemented by Sahil Arora
1535   - // <sahilarora.535@gmail.com> as part of a Google Summer of
1536   - // Code project in 2017.
1537   - assert(arg2 != 0);
1538   - QPDFWriter w(pdf, arg2);
1539   - w.setPCLm(true);
1540   - w.setStaticID(true);
1541   - w.write();
  1435 + QPDFPageObjectHelper& page(*iter);
  1436 + ParserCallbacks cb;
  1437 + page.parseContents(&cb);
1542 1438 }
1543   - else if (n == 41)
  1439 +}
  1440 +
  1441 +static void test_38(QPDF& pdf, char const* arg2)
  1442 +{
  1443 + // Designed for override-compressed-object.pdf
  1444 + QPDFObjectHandle qtest = pdf.getRoot().getKey("/QTest");
  1445 + for (int i = 0; i < qtest.getArrayNItems(); ++i)
1544 1446 {
1545   - // Apply a token filter. This test case is crafted to work
1546   - // with coalesce.pdf.
1547   - std::vector<QPDFPageObjectHelper> pages =
1548   - QPDFPageDocumentHelper(pdf).getAllPages();
1549   - for (std::vector<QPDFPageObjectHelper>::iterator iter =
1550   - pages.begin();
1551   - iter != pages.end(); ++iter)
1552   - {
1553   - (*iter).addContentTokenFilter(new TokenFilter);
1554   - }
1555   - QPDFWriter w(pdf, "a.pdf");
1556   - w.setQDFMode(true);
1557   - w.setStaticID(true);
1558   - w.write();
  1447 + std::cout << qtest.getArrayItem(i).unparseResolved() << std::endl;
1559 1448 }
1560   - else if (n == 42)
1561   - {
1562   - // Access objects as wrong type. This test case is crafted to
1563   - // work with object-types.pdf.
1564   - QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
1565   - QPDFObjectHandle array = qtest.getKey("/Dictionary").getKey("/Key2");
1566   - QPDFObjectHandle dictionary = qtest.getKey("/Dictionary");
1567   - QPDFObjectHandle integer = qtest.getKey("/Integer");
1568   - QPDFObjectHandle null = QPDFObjectHandle::newNull();
1569   - assert(array.isArray());
1570   - {
1571   - // Exercise iterators directly
1572   - auto ai = array.aitems();
1573   - auto i = ai.begin();
1574   - assert(i->getName() == "/Item0");
1575   - auto& i_value = *i;
1576   - --i;
1577   - assert(i->getName() == "/Item0");
1578   - ++i;
1579   - ++i;
1580   - ++i;
1581   - assert(i == ai.end());
1582   - ++i;
1583   - assert(i == ai.end());
1584   - assert(! i_value.isInitialized());
1585   - --i;
1586   - assert(i_value.getName() == "/Item2");
1587   - assert(i->getName() == "/Item2");
1588   - }
1589   - assert(dictionary.isDictionary());
1590   - {
1591   - // Exercise iterators directly
1592   - auto di = dictionary.ditems();
1593   - auto i = di.begin();
1594   - assert(i->first == "/Key1");
1595   - auto& i_value = *i;
1596   - assert(i->second.getName() == "/Value1");
1597   - ++i;
1598   - ++i;
1599   - assert(i == di.end());
1600   - assert(! i_value.second.isInitialized());
  1449 +}
  1450 +
  1451 +static void test_39(QPDF& pdf, char const* arg2)
  1452 +{
  1453 + // Display image filter and color set for each image on each page
  1454 + std::vector<QPDFPageObjectHelper> pages =
  1455 + QPDFPageDocumentHelper(pdf).getAllPages();
  1456 + int pageno = 0;
  1457 + for (std::vector<QPDFPageObjectHelper>::iterator p_iter =
  1458 + pages.begin();
  1459 + p_iter != pages.end(); ++p_iter)
  1460 + {
  1461 + std::cout << "page " << ++pageno << std::endl;
  1462 + std::map<std::string, QPDFObjectHandle> images =
  1463 + (*p_iter).getImages();
  1464 + for (std::map<std::string, QPDFObjectHandle>::iterator i_iter =
  1465 + images.begin(); i_iter != images.end(); ++i_iter)
  1466 + {
  1467 + QPDFObjectHandle image_dict = (*i_iter).second.getDict();
  1468 + std::cout << "filter: "
  1469 + << image_dict.getKey("/Filter").unparseResolved()
  1470 + << ", color space: "
  1471 + << image_dict.getKey("/ColorSpace").unparseResolved()
  1472 + << std::endl;
1601 1473 }
1602   - assert("" == qtest.getStringValue());
1603   - array.getArrayItem(-1).assertNull();
1604   - array.getArrayItem(16059).assertNull();
1605   - integer.getArrayItem(0).assertNull();
1606   - integer.appendItem(null);
1607   - array.eraseItem(-1);
1608   - array.eraseItem(16059);
1609   - integer.eraseItem(0);
1610   - integer.insertItem(0, null);
1611   - integer.setArrayFromVector(std::vector<QPDFObjectHandle>());
1612   - integer.setArrayItem(0, null);
1613   - assert(0 == integer.getArrayNItems());
1614   - assert(integer.getArrayAsVector().empty());
1615   - assert(false == integer.getBoolValue());
1616   - assert(integer.getDictAsMap().empty());
1617   - assert(integer.getKeys().empty());
1618   - assert(false == integer.hasKey("/Potato"));
1619   - integer.removeKey("/Potato");
1620   - integer.replaceOrRemoveKey("/Potato", null);
1621   - integer.replaceOrRemoveKey("/Potato", QPDFObjectHandle::newInteger(1));
1622   - integer.replaceKey("/Potato", QPDFObjectHandle::newInteger(1));
1623   - qtest.getKey("/Integer").getKey("/Potato");
1624   - assert(integer.getInlineImageValue().empty());
1625   - assert(0 == dictionary.getIntValue());
1626   - assert("/QPDFFakeName" == integer.getName());
1627   - assert("QPDFFAKE" == integer.getOperatorValue());
1628   - assert("0.0" == dictionary.getRealValue());
1629   - assert(integer.getStringValue().empty());
1630   - assert(integer.getUTF8Value().empty());
1631   - assert(0.0 == dictionary.getNumericValue());
1632   - // Make sure error messages are okay for nested values
1633   - std::cerr << "One error\n";
1634   - assert(array.getArrayItem(0).getStringValue().empty());
1635   - std::cerr << "One error\n";
1636   - assert(dictionary.getKey("/Quack").getStringValue().empty());
1637   - assert(array.getArrayItem(1).isDictionary());
1638   - assert(array.getArrayItem(1).getKey("/K").isArray());
1639   - assert(array.getArrayItem(1).getKey("/K").getArrayItem(0).isName());
1640   - assert("/V" ==
1641   - array.getArrayItem(1).getKey("/K").getArrayItem(0).getName());
1642   - std::cerr << "Two errors\n";
1643   - assert(array.getArrayItem(16059).getStringValue().empty());
1644   - std::cerr << "One error\n";
1645   - array.getArrayItem(1).getKey("/K").getArrayItem(0).getStringValue();
1646   - // Stream dictionary
1647   - QPDFObjectHandle page = pdf.getAllPages().at(0);
1648   - assert("/QPDFFakeName" ==
1649   - page.getKey("/Contents").getDict().getKey("/Potato").getName());
1650   - // Rectangles
1651   - QPDFObjectHandle::Rectangle r0 = integer.getArrayAsRectangle();
1652   - assert((r0.llx == 0) && (r0.lly == 0) &&
1653   - (r0.urx == 0) && (r0.ury == 0));
1654   - QPDFObjectHandle rect = QPDFObjectHandle::newFromRectangle(
1655   - QPDFObjectHandle::Rectangle(1.2, 3.4, 5.6, 7.8));
1656   - QPDFObjectHandle::Rectangle r1 = rect.getArrayAsRectangle();
1657   - assert((r1.llx > 1.19) && (r1.llx < 1.21) &&
1658   - (r1.lly > 3.39) && (r1.lly < 3.41) &&
1659   - (r1.urx > 5.59) && (r1.urx < 5.61) &&
1660   - (r1.ury > 7.79) && (r1.ury < 7.81));
1661   - QPDFObjectHandle uninitialized;
1662   - assert(! uninitialized.isInitialized());
1663   - assert(! uninitialized.isInteger());
1664   - assert(! uninitialized.isDictionary());
1665 1474 }
1666   - else if (n == 43)
1667   - {
1668   - // Forms
1669   - QPDFAcroFormDocumentHelper afdh(pdf);
1670   - if (! afdh.hasAcroForm())
1671   - {
1672   - std::cout << "no forms\n";
1673   - return;
1674   - }
1675   - std::cout << "iterating over form fields\n";
1676   - std::vector<QPDFFormFieldObjectHelper> form_fields =
1677   - afdh.getFormFields();
1678   - for (std::vector<QPDFFormFieldObjectHelper>::iterator iter =
1679   - form_fields.begin();
1680   - iter != form_fields.end(); ++iter)
1681   - {
1682   - QPDFFormFieldObjectHelper ffh(*iter);
1683   - std::cout << "Field: " << ffh.getObjectHandle().unparse()
  1475 +}
  1476 +
  1477 +static void test_40(QPDF& pdf, char const* arg2)
  1478 +{
  1479 + // Write PCLm. This requires specially crafted PDF files. This
  1480 + // feature was implemented by Sahil Arora
  1481 + // <sahilarora.535@gmail.com> as part of a Google Summer of
  1482 + // Code project in 2017.
  1483 + assert(arg2 != 0);
  1484 + QPDFWriter w(pdf, arg2);
  1485 + w.setPCLm(true);
  1486 + w.setStaticID(true);
  1487 + w.write();
  1488 +}
  1489 +
  1490 +static void test_41(QPDF& pdf, char const* arg2)
  1491 +{
  1492 + // Apply a token filter. This test case is crafted to work
  1493 + // with coalesce.pdf.
  1494 + std::vector<QPDFPageObjectHelper> pages =
  1495 + QPDFPageDocumentHelper(pdf).getAllPages();
  1496 + for (std::vector<QPDFPageObjectHelper>::iterator iter =
  1497 + pages.begin();
  1498 + iter != pages.end(); ++iter)
  1499 + {
  1500 + (*iter).addContentTokenFilter(new TokenFilter);
  1501 + }
  1502 + QPDFWriter w(pdf, "a.pdf");
  1503 + w.setQDFMode(true);
  1504 + w.setStaticID(true);
  1505 + w.write();
  1506 +}
  1507 +
  1508 +static void test_42(QPDF& pdf, char const* arg2)
  1509 +{
  1510 + // Access objects as wrong type. This test case is crafted to
  1511 + // work with object-types.pdf.
  1512 + QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
  1513 + QPDFObjectHandle array = qtest.getKey("/Dictionary").getKey("/Key2");
  1514 + QPDFObjectHandle dictionary = qtest.getKey("/Dictionary");
  1515 + QPDFObjectHandle integer = qtest.getKey("/Integer");
  1516 + QPDFObjectHandle null = QPDFObjectHandle::newNull();
  1517 + assert(array.isArray());
  1518 + {
  1519 + // Exercise iterators directly
  1520 + auto ai = array.aitems();
  1521 + auto i = ai.begin();
  1522 + assert(i->getName() == "/Item0");
  1523 + auto& i_value = *i;
  1524 + --i;
  1525 + assert(i->getName() == "/Item0");
  1526 + ++i;
  1527 + ++i;
  1528 + ++i;
  1529 + assert(i == ai.end());
  1530 + ++i;
  1531 + assert(i == ai.end());
  1532 + assert(! i_value.isInitialized());
  1533 + --i;
  1534 + assert(i_value.getName() == "/Item2");
  1535 + assert(i->getName() == "/Item2");
  1536 + }
  1537 + assert(dictionary.isDictionary());
  1538 + {
  1539 + // Exercise iterators directly
  1540 + auto di = dictionary.ditems();
  1541 + auto i = di.begin();
  1542 + assert(i->first == "/Key1");
  1543 + auto& i_value = *i;
  1544 + assert(i->second.getName() == "/Value1");
  1545 + ++i;
  1546 + ++i;
  1547 + assert(i == di.end());
  1548 + assert(! i_value.second.isInitialized());
  1549 + }
  1550 + assert("" == qtest.getStringValue());
  1551 + array.getArrayItem(-1).assertNull();
  1552 + array.getArrayItem(16059).assertNull();
  1553 + integer.getArrayItem(0).assertNull();
  1554 + integer.appendItem(null);
  1555 + array.eraseItem(-1);
  1556 + array.eraseItem(16059);
  1557 + integer.eraseItem(0);
  1558 + integer.insertItem(0, null);
  1559 + integer.setArrayFromVector(std::vector<QPDFObjectHandle>());
  1560 + integer.setArrayItem(0, null);
  1561 + assert(0 == integer.getArrayNItems());
  1562 + assert(integer.getArrayAsVector().empty());
  1563 + assert(false == integer.getBoolValue());
  1564 + assert(integer.getDictAsMap().empty());
  1565 + assert(integer.getKeys().empty());
  1566 + assert(false == integer.hasKey("/Potato"));
  1567 + integer.removeKey("/Potato");
  1568 + integer.replaceOrRemoveKey("/Potato", null);
  1569 + integer.replaceOrRemoveKey("/Potato", QPDFObjectHandle::newInteger(1));
  1570 + integer.replaceKey("/Potato", QPDFObjectHandle::newInteger(1));
  1571 + qtest.getKey("/Integer").getKey("/Potato");
  1572 + assert(integer.getInlineImageValue().empty());
  1573 + assert(0 == dictionary.getIntValue());
  1574 + assert("/QPDFFakeName" == integer.getName());
  1575 + assert("QPDFFAKE" == integer.getOperatorValue());
  1576 + assert("0.0" == dictionary.getRealValue());
  1577 + assert(integer.getStringValue().empty());
  1578 + assert(integer.getUTF8Value().empty());
  1579 + assert(0.0 == dictionary.getNumericValue());
  1580 + // Make sure error messages are okay for nested values
  1581 + std::cerr << "One error\n";
  1582 + assert(array.getArrayItem(0).getStringValue().empty());
  1583 + std::cerr << "One error\n";
  1584 + assert(dictionary.getKey("/Quack").getStringValue().empty());
  1585 + assert(array.getArrayItem(1).isDictionary());
  1586 + assert(array.getArrayItem(1).getKey("/K").isArray());
  1587 + assert(array.getArrayItem(1).getKey("/K").getArrayItem(0).isName());
  1588 + assert("/V" ==
  1589 + array.getArrayItem(1).getKey("/K").getArrayItem(0).getName());
  1590 + std::cerr << "Two errors\n";
  1591 + assert(array.getArrayItem(16059).getStringValue().empty());
  1592 + std::cerr << "One error\n";
  1593 + array.getArrayItem(1).getKey("/K").getArrayItem(0).getStringValue();
  1594 + // Stream dictionary
  1595 + QPDFObjectHandle page = pdf.getAllPages().at(0);
  1596 + assert("/QPDFFakeName" ==
  1597 + page.getKey("/Contents").getDict().getKey("/Potato").getName());
  1598 + // Rectangles
  1599 + QPDFObjectHandle::Rectangle r0 = integer.getArrayAsRectangle();
  1600 + assert((r0.llx == 0) && (r0.lly == 0) &&
  1601 + (r0.urx == 0) && (r0.ury == 0));
  1602 + QPDFObjectHandle rect = QPDFObjectHandle::newFromRectangle(
  1603 + QPDFObjectHandle::Rectangle(1.2, 3.4, 5.6, 7.8));
  1604 + QPDFObjectHandle::Rectangle r1 = rect.getArrayAsRectangle();
  1605 + assert((r1.llx > 1.19) && (r1.llx < 1.21) &&
  1606 + (r1.lly > 3.39) && (r1.lly < 3.41) &&
  1607 + (r1.urx > 5.59) && (r1.urx < 5.61) &&
  1608 + (r1.ury > 7.79) && (r1.ury < 7.81));
  1609 + QPDFObjectHandle uninitialized;
  1610 + assert(! uninitialized.isInitialized());
  1611 + assert(! uninitialized.isInteger());
  1612 + assert(! uninitialized.isDictionary());
  1613 +}
  1614 +
  1615 +static void test_43(QPDF& pdf, char const* arg2)
  1616 +{
  1617 + // Forms
  1618 + QPDFAcroFormDocumentHelper afdh(pdf);
  1619 + if (! afdh.hasAcroForm())
  1620 + {
  1621 + std::cout << "no forms\n";
  1622 + return;
  1623 + }
  1624 + std::cout << "iterating over form fields\n";
  1625 + std::vector<QPDFFormFieldObjectHelper> form_fields =
  1626 + afdh.getFormFields();
  1627 + for (std::vector<QPDFFormFieldObjectHelper>::iterator iter =
  1628 + form_fields.begin();
  1629 + iter != form_fields.end(); ++iter)
  1630 + {
  1631 + QPDFFormFieldObjectHelper ffh(*iter);
  1632 + std::cout << "Field: " << ffh.getObjectHandle().unparse()
  1633 + << std::endl;
  1634 + QPDFFormFieldObjectHelper node = ffh;
  1635 + while (! node.isNull())
  1636 + {
  1637 + QPDFFormFieldObjectHelper parent(node.getParent());
  1638 + std::cout << " Parent: "
  1639 + << (parent.isNull()
  1640 + ? std::string("none")
  1641 + : parent.getObjectHandle().unparse())
1684 1642 << std::endl;
1685   - QPDFFormFieldObjectHelper node = ffh;
1686   - while (! node.isNull())
1687   - {
1688   - QPDFFormFieldObjectHelper parent(node.getParent());
1689   - std::cout << " Parent: "
1690   - << (parent.isNull()
1691   - ? std::string("none")
1692   - : parent.getObjectHandle().unparse())
1693   - << std::endl;
1694   - node = parent;
1695   - }
1696   - std::cout << " Fully qualified name: "
1697   - << ffh.getFullyQualifiedName() << std::endl;
1698   - std::cout << " Partial name: "
1699   - << ffh.getPartialName() << std::endl;
1700   - std::cout << " Alternative name: "
1701   - << ffh.getAlternativeName() << std::endl;
1702   - std::cout << " Mapping name: "
1703   - << ffh.getMappingName() << std::endl;
1704   - std::cout << " Field type: "
1705   - << ffh.getFieldType() << std::endl;
1706   - std::cout << " Value: "
1707   - << ffh.getValue().unparse() << std::endl;
1708   - std::cout << " Value as string: "
1709   - << ffh.getValueAsString() << std::endl;
1710   - std::cout << " Default value: "
1711   - << ffh.getDefaultValue().unparse() << std::endl;
1712   - std::cout << " Default value as string: "
1713   - << ffh.getDefaultValueAsString() << std::endl;
1714   - std::cout << " Default appearance: "
1715   - << ffh.getDefaultAppearance() << std::endl;
1716   - std::cout << " Quadding: "
1717   - << ffh.getQuadding() << std::endl;
1718   - std::vector<QPDFAnnotationObjectHelper> annotations =
1719   - afdh.getAnnotationsForField(ffh);
1720   - for (std::vector<QPDFAnnotationObjectHelper>::iterator i2 =
1721   - annotations.begin();
1722   - i2 != annotations.end(); ++i2)
1723   - {
1724   - std::cout << " Annotation: "
1725   - << (*i2).getObjectHandle().unparse() << std::endl;
1726   - }
1727   - }
1728   - std::cout << "iterating over annotations per page\n";
1729   - std::vector<QPDFPageObjectHelper> pages =
1730   - QPDFPageDocumentHelper(pdf).getAllPages();
1731   - for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
1732   - iter != pages.end(); ++iter)
1733   - {
1734   - std::cout << "Page: " << (*iter).getObjectHandle().unparse()
  1643 + node = parent;
  1644 + }
  1645 + std::cout << " Fully qualified name: "
  1646 + << ffh.getFullyQualifiedName() << std::endl;
  1647 + std::cout << " Partial name: "
  1648 + << ffh.getPartialName() << std::endl;
  1649 + std::cout << " Alternative name: "
  1650 + << ffh.getAlternativeName() << std::endl;
  1651 + std::cout << " Mapping name: "
  1652 + << ffh.getMappingName() << std::endl;
  1653 + std::cout << " Field type: "
  1654 + << ffh.getFieldType() << std::endl;
  1655 + std::cout << " Value: "
  1656 + << ffh.getValue().unparse() << std::endl;
  1657 + std::cout << " Value as string: "
  1658 + << ffh.getValueAsString() << std::endl;
  1659 + std::cout << " Default value: "
  1660 + << ffh.getDefaultValue().unparse() << std::endl;
  1661 + std::cout << " Default value as string: "
  1662 + << ffh.getDefaultValueAsString() << std::endl;
  1663 + std::cout << " Default appearance: "
  1664 + << ffh.getDefaultAppearance() << std::endl;
  1665 + std::cout << " Quadding: "
  1666 + << ffh.getQuadding() << std::endl;
  1667 + std::vector<QPDFAnnotationObjectHelper> annotations =
  1668 + afdh.getAnnotationsForField(ffh);
  1669 + for (std::vector<QPDFAnnotationObjectHelper>::iterator i2 =
  1670 + annotations.begin();
  1671 + i2 != annotations.end(); ++i2)
  1672 + {
  1673 + std::cout << " Annotation: "
  1674 + << (*i2).getObjectHandle().unparse() << std::endl;
  1675 + }
  1676 + }
  1677 + std::cout << "iterating over annotations per page\n";
  1678 + std::vector<QPDFPageObjectHelper> pages =
  1679 + QPDFPageDocumentHelper(pdf).getAllPages();
  1680 + for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
  1681 + iter != pages.end(); ++iter)
  1682 + {
  1683 + std::cout << "Page: " << (*iter).getObjectHandle().unparse()
  1684 + << std::endl;
  1685 + std::vector<QPDFAnnotationObjectHelper> annotations =
  1686 + afdh.getWidgetAnnotationsForPage(*iter);
  1687 + for (std::vector<QPDFAnnotationObjectHelper>::iterator i2 =
  1688 + annotations.begin();
  1689 + i2 != annotations.end(); ++i2)
  1690 + {
  1691 + QPDFAnnotationObjectHelper ah(*i2);
  1692 + std::cout << " Annotation: " << ah.getObjectHandle().unparse()
1735 1693 << std::endl;
1736   - std::vector<QPDFAnnotationObjectHelper> annotations =
1737   - afdh.getWidgetAnnotationsForPage(*iter);
1738   - for (std::vector<QPDFAnnotationObjectHelper>::iterator i2 =
1739   - annotations.begin();
1740   - i2 != annotations.end(); ++i2)
  1694 + std::cout << " Field: "
  1695 + << (afdh.getFieldForAnnotation(ah).
  1696 + getObjectHandle().unparse())
  1697 + << std::endl;
  1698 + std::cout << " Subtype: " << ah.getSubtype() << std::endl;
  1699 + std::cout << " Rect: ";
  1700 + print_rect(std::cout, ah.getRect());
  1701 + std::cout << std::endl;
  1702 + std::string state = ah.getAppearanceState();
  1703 + if (! state.empty())
1741 1704 {
1742   - QPDFAnnotationObjectHelper ah(*i2);
1743   - std::cout << " Annotation: " << ah.getObjectHandle().unparse()
1744   - << std::endl;
1745   - std::cout << " Field: "
1746   - << (afdh.getFieldForAnnotation(ah).
1747   - getObjectHandle().unparse())
1748   - << std::endl;
1749   - std::cout << " Subtype: " << ah.getSubtype() << std::endl;
1750   - std::cout << " Rect: ";
1751   - print_rect(std::cout, ah.getRect());
1752   - std::cout << std::endl;
1753   - std::string state = ah.getAppearanceState();
1754   - if (! state.empty())
1755   - {
1756   - std::cout << " Appearance state: " << state
1757   - << std::endl;
1758   - }
1759   - std::cout << " Appearance stream (/N): "
1760   - << ah.getAppearanceStream("/N").unparse()
1761   - << std::endl;
1762   - std::cout << " Appearance stream (/N, /3): "
1763   - << ah.getAppearanceStream("/N", "/3").unparse()
  1705 + std::cout << " Appearance state: " << state
1764 1706 << std::endl;
1765 1707 }
  1708 + std::cout << " Appearance stream (/N): "
  1709 + << ah.getAppearanceStream("/N").unparse()
  1710 + << std::endl;
  1711 + std::cout << " Appearance stream (/N, /3): "
  1712 + << ah.getAppearanceStream("/N", "/3").unparse()
  1713 + << std::endl;
1766 1714 }
1767 1715 }
1768   - else if (n == 44)
1769   - {
1770   - // Set form fields.
1771   - QPDFAcroFormDocumentHelper afdh(pdf);
1772   - std::vector<QPDFFormFieldObjectHelper> fields = afdh.getFormFields();
1773   - for (std::vector<QPDFFormFieldObjectHelper>::iterator iter =
1774   - fields.begin();
1775   - iter != fields.end(); ++iter)
1776   - {
1777   - QPDFFormFieldObjectHelper& field(*iter);
1778   - QPDFObjectHandle ft = field.getInheritableFieldValue("/FT");
1779   - if (ft.isName() && (ft.getName() == "/Tx"))
1780   - {
1781   - // \xc3\xb7 is utf-8 for U+00F7 (divided by)
1782   - field.setV("3.14 \xc3\xb7 0");
1783   - std::cout << "Set field value: "
1784   - << field.getFullyQualifiedName()
1785   - << " -> "
1786   - << field.getValueAsString()
1787   - << std::endl;
1788   - }
  1716 +}
  1717 +
  1718 +static void test_44(QPDF& pdf, char const* arg2)
  1719 +{
  1720 + // Set form fields.
  1721 + QPDFAcroFormDocumentHelper afdh(pdf);
  1722 + std::vector<QPDFFormFieldObjectHelper> fields = afdh.getFormFields();
  1723 + for (std::vector<QPDFFormFieldObjectHelper>::iterator iter =
  1724 + fields.begin();
  1725 + iter != fields.end(); ++iter)
  1726 + {
  1727 + QPDFFormFieldObjectHelper& field(*iter);
  1728 + QPDFObjectHandle ft = field.getInheritableFieldValue("/FT");
  1729 + if (ft.isName() && (ft.getName() == "/Tx"))
  1730 + {
  1731 + // \xc3\xb7 is utf-8 for U+00F7 (divided by)
  1732 + field.setV("3.14 \xc3\xb7 0");
  1733 + std::cout << "Set field value: "
  1734 + << field.getFullyQualifiedName()
  1735 + << " -> "
  1736 + << field.getValueAsString()
  1737 + << std::endl;
1789 1738 }
1790   - QPDFWriter w(pdf, "a.pdf");
1791   - w.setQDFMode(true);
1792   - w.setStaticID(true);
1793   - w.setSuppressOriginalObjectIDs(true);
1794   - w.write();
1795 1739 }
1796   - else if (n == 45)
  1740 + QPDFWriter w(pdf, "a.pdf");
  1741 + w.setQDFMode(true);
  1742 + w.setStaticID(true);
  1743 + w.setSuppressOriginalObjectIDs(true);
  1744 + w.write();
  1745 +}
  1746 +
  1747 +static void test_45(QPDF& pdf, char const* arg2)
  1748 +{
  1749 + // Decode obfuscated files. This is here to help test with
  1750 + // files that trigger anti-virus warnings. See comments in
  1751 + // qpdf.test for details.
  1752 + QPDFWriter w(pdf, "a.pdf");
  1753 + w.setStaticID(true);
  1754 + w.write();
  1755 + if (! pdf.getWarnings().empty())
1797 1756 {
1798   - // Decode obfuscated files. This is here to help test with
1799   - // files that trigger anti-virus warnings. See comments in
1800   - // qpdf.test for details.
1801   - QPDFWriter w(pdf, "a.pdf");
1802   - w.setStaticID(true);
1803   - w.write();
1804   - if (! pdf.getWarnings().empty())
1805   - {
1806   - exit(3);
1807   - }
  1757 + exit(3);
1808 1758 }
1809   - else if (n == 46)
1810   - {
1811   - // Test number tree. This test is crafted to work with
1812   - // number-tree.pdf
1813   - QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
1814   - QPDFNumberTreeObjectHelper ntoh(qtest, pdf);
1815   - for (auto& iter: ntoh)
1816   - {
1817   - std::cout << iter.first << " "
1818   - << iter.second.getStringValue()
1819   - << std::endl;
1820   - }
1821   - QPDFNumberTreeObjectHelper::idx_map ntoh_map = ntoh.getAsMap();
1822   - for (auto& iter: ntoh_map)
1823   - {
1824   - std::cout << iter.first << " "
1825   - << iter.second.getStringValue()
1826   - << std::endl;
1827   - }
1828   - assert(1 == ntoh.getMin());
1829   - assert(29 == ntoh.getMax());
1830   - assert(ntoh.hasIndex(6));
1831   - assert(! ntoh.hasIndex(500));
1832   - QPDFObjectHandle oh;
1833   - assert(! ntoh.findObject(4, oh));
1834   - assert(ntoh.findObject(3, oh));
1835   - assert("three" == oh.getStringValue());
1836   - QPDFNumberTreeObjectHelper::numtree_number offset = 0;
1837   - assert(! ntoh.findObjectAtOrBelow(0, oh, offset));
1838   - assert(ntoh.findObjectAtOrBelow(8, oh, offset));
1839   - assert("six" == oh.getStringValue());
1840   - assert(2 == offset);
1841   -
1842   - auto new1 = QPDFNumberTreeObjectHelper::newEmpty(pdf);
1843   - auto iter1 = new1.begin();
1844   - assert(iter1 == new1.end());
1845   - ++iter1;
1846   - assert(iter1 == new1.end());
1847   - --iter1;
1848   - assert(iter1 == new1.end());
1849   - new1.insert(1, QPDFObjectHandle::newString("1"));
1850   - ++iter1;
1851   - assert((*iter1).first == 1); // exercise operator* explicitly
1852   - auto& iter1_val = *iter1;
1853   - --iter1;
1854   - assert(iter1 == new1.end());
1855   - --iter1;
1856   - assert(iter1->first == 1);
1857   - assert(iter1_val.first == 1);
1858   - new1.insert(2, QPDFObjectHandle::newString("2"));
1859   - ++iter1;
1860   - assert(iter1->first == 2);
1861   - assert(iter1_val.first == 2);
1862   - ++iter1;
1863   - assert(iter1 == new1.end());
1864   - assert(! iter1_val.second.isInitialized());
1865   - ++iter1;
1866   - assert(iter1->first == 1);
1867   - --iter1;
1868   - assert(iter1 == new1.end());
1869   - --iter1;
1870   - assert(iter1->first == 2);
1871   -
1872   - std::cout << "insertAfter" << std::endl;
1873   - auto new2 = QPDFNumberTreeObjectHelper::newEmpty(pdf);
1874   - auto iter2 = new2.begin();
1875   - assert(iter2 == new2.end());
1876   - iter2.insertAfter(3, QPDFObjectHandle::newString("3!"));
1877   - assert(iter2->first == 3);
1878   - iter2.insertAfter(4, QPDFObjectHandle::newString("4!"));
1879   - assert(iter2->first == 4);
1880   - for (auto& i: new2)
1881   - {
1882   - std::cout << i.first << " " << i.second.unparse() << std::endl;
1883   - }
  1759 +}
1884 1760  
1885   - // Exercise deprecated API until qpdf 11
1886   - std::cout << "/Bad1: deprecated API" << std::endl;
  1761 +static void test_46(QPDF& pdf, char const* arg2)
  1762 +{
  1763 + // Test number tree. This test is crafted to work with
  1764 + // number-tree.pdf
  1765 + QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
  1766 + QPDFNumberTreeObjectHelper ntoh(qtest, pdf);
  1767 + for (auto& iter: ntoh)
  1768 + {
  1769 + std::cout << iter.first << " "
  1770 + << iter.second.getStringValue()
  1771 + << std::endl;
  1772 + }
  1773 + QPDFNumberTreeObjectHelper::idx_map ntoh_map = ntoh.getAsMap();
  1774 + for (auto& iter: ntoh_map)
  1775 + {
  1776 + std::cout << iter.first << " "
  1777 + << iter.second.getStringValue()
  1778 + << std::endl;
  1779 + }
  1780 + assert(1 == ntoh.getMin());
  1781 + assert(29 == ntoh.getMax());
  1782 + assert(ntoh.hasIndex(6));
  1783 + assert(! ntoh.hasIndex(500));
  1784 + QPDFObjectHandle oh;
  1785 + assert(! ntoh.findObject(4, oh));
  1786 + assert(ntoh.findObject(3, oh));
  1787 + assert("three" == oh.getStringValue());
  1788 + QPDFNumberTreeObjectHelper::numtree_number offset = 0;
  1789 + assert(! ntoh.findObjectAtOrBelow(0, oh, offset));
  1790 + assert(ntoh.findObjectAtOrBelow(8, oh, offset));
  1791 + assert("six" == oh.getStringValue());
  1792 + assert(2 == offset);
  1793 +
  1794 + auto new1 = QPDFNumberTreeObjectHelper::newEmpty(pdf);
  1795 + auto iter1 = new1.begin();
  1796 + assert(iter1 == new1.end());
  1797 + ++iter1;
  1798 + assert(iter1 == new1.end());
  1799 + --iter1;
  1800 + assert(iter1 == new1.end());
  1801 + new1.insert(1, QPDFObjectHandle::newString("1"));
  1802 + ++iter1;
  1803 + assert((*iter1).first == 1); // exercise operator* explicitly
  1804 + auto& iter1_val = *iter1;
  1805 + --iter1;
  1806 + assert(iter1 == new1.end());
  1807 + --iter1;
  1808 + assert(iter1->first == 1);
  1809 + assert(iter1_val.first == 1);
  1810 + new1.insert(2, QPDFObjectHandle::newString("2"));
  1811 + ++iter1;
  1812 + assert(iter1->first == 2);
  1813 + assert(iter1_val.first == 2);
  1814 + ++iter1;
  1815 + assert(iter1 == new1.end());
  1816 + assert(! iter1_val.second.isInitialized());
  1817 + ++iter1;
  1818 + assert(iter1->first == 1);
  1819 + --iter1;
  1820 + assert(iter1 == new1.end());
  1821 + --iter1;
  1822 + assert(iter1->first == 2);
  1823 +
  1824 + std::cout << "insertAfter" << std::endl;
  1825 + auto new2 = QPDFNumberTreeObjectHelper::newEmpty(pdf);
  1826 + auto iter2 = new2.begin();
  1827 + assert(iter2 == new2.end());
  1828 + iter2.insertAfter(3, QPDFObjectHandle::newString("3!"));
  1829 + assert(iter2->first == 3);
  1830 + iter2.insertAfter(4, QPDFObjectHandle::newString("4!"));
  1831 + assert(iter2->first == 4);
  1832 + for (auto& i: new2)
  1833 + {
  1834 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1835 + }
  1836 +
  1837 + // Exercise deprecated API until qpdf 11
  1838 + std::cout << "/Bad1: deprecated API" << std::endl;
1887 1839 #ifdef _MSC_VER
1888 1840 # pragma warning (disable: 4996)
1889 1841 #endif
... ... @@ -1891,211 +1843,213 @@ void runtest(int n, char const* filename1, char const* arg2)
1891 1843 # pragma GCC diagnostic push
1892 1844 # pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1893 1845 #endif
1894   - auto bad1 = QPDFNumberTreeObjectHelper(
1895   - pdf.getTrailer().getKey("/Bad1"));
  1846 + auto bad1 = QPDFNumberTreeObjectHelper(
  1847 + pdf.getTrailer().getKey("/Bad1"));
1896 1848 #if (defined(__GNUC__) || defined(__clang__))
1897 1849 # pragma GCC diagnostic pop
1898 1850 #endif
1899   - assert(bad1.begin() == bad1.end());
1900   -
1901   - std::cout << "/Bad1" << std::endl;
1902   - bad1 = QPDFNumberTreeObjectHelper(
1903   - pdf.getTrailer().getKey("/Bad1"), pdf);
1904   - assert(bad1.begin() == bad1.end());
1905   - assert(bad1.last() == bad1.end());
1906   -
1907   - std::cout << "/Bad2" << std::endl;
1908   - auto bad2 = QPDFNumberTreeObjectHelper(
1909   - pdf.getTrailer().getKey("/Bad2"), pdf);
1910   - for (auto& i: bad2)
1911   - {
1912   - std::cout << i.first << " " << i.second.unparse() << std::endl;
1913   - }
  1851 + assert(bad1.begin() == bad1.end());
  1852 +
  1853 + std::cout << "/Bad1" << std::endl;
  1854 + bad1 = QPDFNumberTreeObjectHelper(
  1855 + pdf.getTrailer().getKey("/Bad1"), pdf);
  1856 + assert(bad1.begin() == bad1.end());
  1857 + assert(bad1.last() == bad1.end());
  1858 +
  1859 + std::cout << "/Bad2" << std::endl;
  1860 + auto bad2 = QPDFNumberTreeObjectHelper(
  1861 + pdf.getTrailer().getKey("/Bad2"), pdf);
  1862 + for (auto& i: bad2)
  1863 + {
  1864 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1865 + }
  1866 +
  1867 + std::vector<std::string> empties = {"/Empty1", "/Empty2"};
  1868 + for (auto const& k: empties)
  1869 + {
  1870 + std::cout << k << std::endl;
  1871 + auto empty = QPDFNumberTreeObjectHelper(
  1872 + pdf.getTrailer().getKey(k), pdf);
  1873 + assert(empty.begin() == empty.end());
  1874 + assert(empty.last() == empty.end());
  1875 + auto i = empty.insert(5, QPDFObjectHandle::newString("5"));
  1876 + assert(i->first == 5);
  1877 + assert(i->second.getStringValue() == "5");
  1878 + assert(empty.begin()->first == 5);
  1879 + assert(empty.last()->first == 5);
  1880 + assert(empty.begin()->second.getStringValue() == "5");
  1881 + i = empty.insert(5, QPDFObjectHandle::newString("5+"));
  1882 + assert(i->first == 5);
  1883 + assert(i->second.getStringValue() == "5+");
  1884 + assert(empty.begin()->second.getStringValue() == "5+");
  1885 + i = empty.insert(6, QPDFObjectHandle::newString("6"));
  1886 + assert(i->first == 6);
  1887 + assert(i->second.getStringValue() == "6");
  1888 + assert(empty.begin()->second.getStringValue() == "5+");
  1889 + assert(empty.last()->first == 6);
  1890 + assert(empty.last()->second.getStringValue() == "6");
  1891 + }
  1892 + std::cout << "Insert into invalid" << std::endl;
  1893 + auto invalid1 = QPDFNumberTreeObjectHelper(
  1894 + QPDFObjectHandle::newDictionary(), pdf);
  1895 + try
  1896 + {
  1897 + invalid1.insert(1, QPDFObjectHandle::newNull());
  1898 + }
  1899 + catch (QPDFExc& e)
  1900 + {
  1901 + std::cout << e.what() << std::endl;
  1902 + }
1914 1903  
1915   - std::vector<std::string> empties = {"/Empty1", "/Empty2"};
1916   - for (auto const& k: empties)
1917   - {
1918   - std::cout << k << std::endl;
1919   - auto empty = QPDFNumberTreeObjectHelper(
1920   - pdf.getTrailer().getKey(k), pdf);
1921   - assert(empty.begin() == empty.end());
1922   - assert(empty.last() == empty.end());
1923   - auto i = empty.insert(5, QPDFObjectHandle::newString("5"));
1924   - assert(i->first == 5);
1925   - assert(i->second.getStringValue() == "5");
1926   - assert(empty.begin()->first == 5);
1927   - assert(empty.last()->first == 5);
1928   - assert(empty.begin()->second.getStringValue() == "5");
1929   - i = empty.insert(5, QPDFObjectHandle::newString("5+"));
1930   - assert(i->first == 5);
1931   - assert(i->second.getStringValue() == "5+");
1932   - assert(empty.begin()->second.getStringValue() == "5+");
1933   - i = empty.insert(6, QPDFObjectHandle::newString("6"));
1934   - assert(i->first == 6);
1935   - assert(i->second.getStringValue() == "6");
1936   - assert(empty.begin()->second.getStringValue() == "5+");
1937   - assert(empty.last()->first == 6);
1938   - assert(empty.last()->second.getStringValue() == "6");
1939   - }
1940   - std::cout << "Insert into invalid" << std::endl;
1941   - auto invalid1 = QPDFNumberTreeObjectHelper(
1942   - QPDFObjectHandle::newDictionary(), pdf);
1943   - try
1944   - {
1945   - invalid1.insert(1, QPDFObjectHandle::newNull());
1946   - }
1947   - catch (QPDFExc& e)
1948   - {
1949   - std::cout << e.what() << std::endl;
1950   - }
  1904 + std::cout << "/Bad3, no repair" << std::endl;
  1905 + auto bad3_oh = pdf.getTrailer().getKey("/Bad3");
  1906 + auto bad3 = QPDFNumberTreeObjectHelper(bad3_oh, pdf, false);
  1907 + for (auto& i: bad3)
  1908 + {
  1909 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1910 + }
  1911 + assert(! bad3_oh.getKey("/Kids").getArrayItem(0).isIndirect());
1951 1912  
1952   - std::cout << "/Bad3, no repair" << std::endl;
1953   - auto bad3_oh = pdf.getTrailer().getKey("/Bad3");
1954   - auto bad3 = QPDFNumberTreeObjectHelper(bad3_oh, pdf, false);
1955   - for (auto& i: bad3)
1956   - {
1957   - std::cout << i.first << " " << i.second.unparse() << std::endl;
1958   - }
1959   - assert(! bad3_oh.getKey("/Kids").getArrayItem(0).isIndirect());
  1913 + std::cout << "/Bad3, repair" << std::endl;
  1914 + bad3 = QPDFNumberTreeObjectHelper(bad3_oh, pdf, true);
  1915 + for (auto& i: bad3)
  1916 + {
  1917 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1918 + }
  1919 + assert(bad3_oh.getKey("/Kids").getArrayItem(0).isIndirect());
1960 1920  
1961   - std::cout << "/Bad3, repair" << std::endl;
1962   - bad3 = QPDFNumberTreeObjectHelper(bad3_oh, pdf, true);
1963   - for (auto& i: bad3)
1964   - {
1965   - std::cout << i.first << " " << i.second.unparse() << std::endl;
1966   - }
1967   - assert(bad3_oh.getKey("/Kids").getArrayItem(0).isIndirect());
  1921 + std::cout << "/Bad4 -- missing limits" << std::endl;
  1922 + auto bad4 = QPDFNumberTreeObjectHelper(
  1923 + pdf.getTrailer().getKey("/Bad4"), pdf);
  1924 + bad4.insert(5, QPDFObjectHandle::newString("5"));
  1925 + for (auto& i: bad4)
  1926 + {
  1927 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1928 + }
1968 1929  
1969   - std::cout << "/Bad4 -- missing limits" << std::endl;
1970   - auto bad4 = QPDFNumberTreeObjectHelper(
1971   - pdf.getTrailer().getKey("/Bad4"), pdf);
1972   - bad4.insert(5, QPDFObjectHandle::newString("5"));
1973   - for (auto& i: bad4)
1974   - {
1975   - std::cout << i.first << " " << i.second.unparse() << std::endl;
1976   - }
  1930 + std::cout << "/Bad5 -- limit errors" << std::endl;
  1931 + auto bad5 = QPDFNumberTreeObjectHelper(
  1932 + pdf.getTrailer().getKey("/Bad5"), pdf);
  1933 + assert(bad5.find(10) == bad5.end());
  1934 +}
1977 1935  
1978   - std::cout << "/Bad5 -- limit errors" << std::endl;
1979   - auto bad5 = QPDFNumberTreeObjectHelper(
1980   - pdf.getTrailer().getKey("/Bad5"), pdf);
1981   - assert(bad5.find(10) == bad5.end());
1982   - }
1983   - else if (n == 47)
1984   - {
1985   - // Test page labels.
1986   - QPDFPageLabelDocumentHelper pldh(pdf);
1987   - long long npages = pdf.getRoot().getKey("/Pages").
1988   - getKey("/Count").getIntValue();
1989   - std::vector<QPDFObjectHandle> labels;
1990   - pldh.getLabelsForPageRange(0, npages - 1, 1, labels);
1991   - assert(labels.size() % 2 == 0);
1992   - for (size_t i = 0; i < labels.size(); i+= 2)
1993   - {
1994   - std::cout << labels.at(i).getIntValue() << " "
1995   - << labels.at(i+1).unparse() << std::endl;
1996   - }
  1936 +static void test_47(QPDF& pdf, char const* arg2)
  1937 +{
  1938 + // Test page labels.
  1939 + QPDFPageLabelDocumentHelper pldh(pdf);
  1940 + long long npages = pdf.getRoot().getKey("/Pages").
  1941 + getKey("/Count").getIntValue();
  1942 + std::vector<QPDFObjectHandle> labels;
  1943 + pldh.getLabelsForPageRange(0, npages - 1, 1, labels);
  1944 + assert(labels.size() % 2 == 0);
  1945 + for (size_t i = 0; i < labels.size(); i+= 2)
  1946 + {
  1947 + std::cout << labels.at(i).getIntValue() << " "
  1948 + << labels.at(i+1).unparse() << std::endl;
1997 1949 }
1998   - else if (n == 48)
1999   - {
2000   - // Test name tree. This test is crafted to work with
2001   - // name-tree.pdf
2002   - QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
2003   - QPDFNameTreeObjectHelper ntoh(qtest, pdf);
2004   - for (auto& iter: ntoh)
2005   - {
2006   - std::cout << iter.first << " -> "
2007   - << iter.second.getStringValue()
2008   - << std::endl;
2009   - }
2010   - std::map<std::string, QPDFObjectHandle> ntoh_map = ntoh.getAsMap();
2011   - for (auto& iter: ntoh_map)
2012   - {
2013   - std::cout << iter.first << " -> "
2014   - << iter.second.getStringValue()
2015   - << std::endl;
2016   - }
2017   - assert(ntoh.hasName("11 elephant"));
2018   - assert(ntoh.hasName("07 sev\xe2\x80\xa2n"));
2019   - assert(! ntoh.hasName("potato"));
2020   - QPDFObjectHandle oh;
2021   - assert(! ntoh.findObject("potato", oh));
2022   - assert(ntoh.findObject("07 sev\xe2\x80\xa2n", oh));
2023   - assert("seven!" == oh.getStringValue());
2024   - auto last = ntoh.last();
2025   - assert(last->first == "29 twenty-nine");
2026   - assert(last->second.getUTF8Value() == "twenty-nine!");
2027   -
2028   - auto new1 = QPDFNameTreeObjectHelper::newEmpty(pdf);
2029   - auto iter1 = new1.begin();
2030   - assert(iter1 == new1.end());
2031   - ++iter1;
2032   - assert(iter1 == new1.end());
2033   - --iter1;
2034   - assert(iter1 == new1.end());
2035   - new1.insert("1", QPDFObjectHandle::newString("1"));
2036   - ++iter1;
2037   - assert(iter1->first == "1");
2038   - auto& iter1_val = *iter1;
2039   - --iter1;
2040   - assert(iter1 == new1.end());
2041   - --iter1;
2042   - assert(iter1->first == "1");
2043   - assert(iter1_val.first == "1");
2044   - new1.insert("2", QPDFObjectHandle::newString("2"));
2045   - ++iter1;
2046   - assert(iter1->first == "2");
2047   - assert(iter1_val.first == "2");
2048   - ++iter1;
2049   - assert(iter1 == new1.end());
2050   - assert(! iter1_val.second.isInitialized());
2051   - ++iter1;
2052   - assert(iter1->first == "1");
2053   - --iter1;
2054   - assert(iter1 == new1.end());
2055   - --iter1;
2056   - assert(iter1->first == "2");
2057   -
2058   - std::cout << "insertAfter" << std::endl;
2059   - auto new2 = QPDFNameTreeObjectHelper::newEmpty(pdf);
2060   - auto iter2 = new2.begin();
2061   - assert(iter2 == new2.end());
2062   - iter2.insertAfter("3", QPDFObjectHandle::newString("3!"));
2063   - assert(iter2->first == "3");
2064   - iter2.insertAfter("4", QPDFObjectHandle::newString("4!"));
2065   - assert(iter2->first == "4");
2066   - for (auto& i: new2)
2067   - {
2068   - std::cout << i.first << " " << i.second.unparse() << std::endl;
2069   - }
2070   -
2071   - std::vector<std::string> empties = {"/Empty1", "/Empty2"};
2072   - for (auto const& k: empties)
2073   - {
2074   - std::cout << k << std::endl;
2075   - auto empty = QPDFNameTreeObjectHelper(
2076   - pdf.getTrailer().getKey(k), pdf);
2077   - assert(empty.begin() == empty.end());
2078   - assert(empty.last() == empty.end());
2079   - auto i = empty.insert("five", QPDFObjectHandle::newString("5"));
2080   - assert(i->first == "five");
2081   - assert(i->second.getStringValue() == "5");
2082   - assert(empty.begin()->first == "five");
2083   - assert(empty.last()->first == "five");
2084   - assert(empty.begin()->second.getStringValue() == "5");
2085   - i = empty.insert("five", QPDFObjectHandle::newString("5+"));
2086   - assert(i->first == "five");
2087   - assert(i->second.getStringValue() == "5+");
2088   - assert(empty.begin()->second.getStringValue() == "5+");
2089   - i = empty.insert("six", QPDFObjectHandle::newString("6"));
2090   - assert(i->first == "six");
2091   - assert(i->second.getStringValue() == "6");
2092   - assert(empty.begin()->second.getStringValue() == "5+");
2093   - assert(empty.last()->first == "six");
2094   - assert(empty.last()->second.getStringValue() == "6");
2095   - }
  1950 +}
2096 1951  
2097   - // Exercise deprecated API until qpdf 11
2098   - std::cout << "/Bad1: deprecated API" << std::endl;
  1952 +static void test_48(QPDF& pdf, char const* arg2)
  1953 +{
  1954 + // Test name tree. This test is crafted to work with
  1955 + // name-tree.pdf
  1956 + QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
  1957 + QPDFNameTreeObjectHelper ntoh(qtest, pdf);
  1958 + for (auto& iter: ntoh)
  1959 + {
  1960 + std::cout << iter.first << " -> "
  1961 + << iter.second.getStringValue()
  1962 + << std::endl;
  1963 + }
  1964 + std::map<std::string, QPDFObjectHandle> ntoh_map = ntoh.getAsMap();
  1965 + for (auto& iter: ntoh_map)
  1966 + {
  1967 + std::cout << iter.first << " -> "
  1968 + << iter.second.getStringValue()
  1969 + << std::endl;
  1970 + }
  1971 + assert(ntoh.hasName("11 elephant"));
  1972 + assert(ntoh.hasName("07 sev\xe2\x80\xa2n"));
  1973 + assert(! ntoh.hasName("potato"));
  1974 + QPDFObjectHandle oh;
  1975 + assert(! ntoh.findObject("potato", oh));
  1976 + assert(ntoh.findObject("07 sev\xe2\x80\xa2n", oh));
  1977 + assert("seven!" == oh.getStringValue());
  1978 + auto last = ntoh.last();
  1979 + assert(last->first == "29 twenty-nine");
  1980 + assert(last->second.getUTF8Value() == "twenty-nine!");
  1981 +
  1982 + auto new1 = QPDFNameTreeObjectHelper::newEmpty(pdf);
  1983 + auto iter1 = new1.begin();
  1984 + assert(iter1 == new1.end());
  1985 + ++iter1;
  1986 + assert(iter1 == new1.end());
  1987 + --iter1;
  1988 + assert(iter1 == new1.end());
  1989 + new1.insert("1", QPDFObjectHandle::newString("1"));
  1990 + ++iter1;
  1991 + assert(iter1->first == "1");
  1992 + auto& iter1_val = *iter1;
  1993 + --iter1;
  1994 + assert(iter1 == new1.end());
  1995 + --iter1;
  1996 + assert(iter1->first == "1");
  1997 + assert(iter1_val.first == "1");
  1998 + new1.insert("2", QPDFObjectHandle::newString("2"));
  1999 + ++iter1;
  2000 + assert(iter1->first == "2");
  2001 + assert(iter1_val.first == "2");
  2002 + ++iter1;
  2003 + assert(iter1 == new1.end());
  2004 + assert(! iter1_val.second.isInitialized());
  2005 + ++iter1;
  2006 + assert(iter1->first == "1");
  2007 + --iter1;
  2008 + assert(iter1 == new1.end());
  2009 + --iter1;
  2010 + assert(iter1->first == "2");
  2011 +
  2012 + std::cout << "insertAfter" << std::endl;
  2013 + auto new2 = QPDFNameTreeObjectHelper::newEmpty(pdf);
  2014 + auto iter2 = new2.begin();
  2015 + assert(iter2 == new2.end());
  2016 + iter2.insertAfter("3", QPDFObjectHandle::newString("3!"));
  2017 + assert(iter2->first == "3");
  2018 + iter2.insertAfter("4", QPDFObjectHandle::newString("4!"));
  2019 + assert(iter2->first == "4");
  2020 + for (auto& i: new2)
  2021 + {
  2022 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  2023 + }
  2024 +
  2025 + std::vector<std::string> empties = {"/Empty1", "/Empty2"};
  2026 + for (auto const& k: empties)
  2027 + {
  2028 + std::cout << k << std::endl;
  2029 + auto empty = QPDFNameTreeObjectHelper(
  2030 + pdf.getTrailer().getKey(k), pdf);
  2031 + assert(empty.begin() == empty.end());
  2032 + assert(empty.last() == empty.end());
  2033 + auto i = empty.insert("five", QPDFObjectHandle::newString("5"));
  2034 + assert(i->first == "five");
  2035 + assert(i->second.getStringValue() == "5");
  2036 + assert(empty.begin()->first == "five");
  2037 + assert(empty.last()->first == "five");
  2038 + assert(empty.begin()->second.getStringValue() == "5");
  2039 + i = empty.insert("five", QPDFObjectHandle::newString("5+"));
  2040 + assert(i->first == "five");
  2041 + assert(i->second.getStringValue() == "5+");
  2042 + assert(empty.begin()->second.getStringValue() == "5+");
  2043 + i = empty.insert("six", QPDFObjectHandle::newString("6"));
  2044 + assert(i->first == "six");
  2045 + assert(i->second.getStringValue() == "6");
  2046 + assert(empty.begin()->second.getStringValue() == "5+");
  2047 + assert(empty.last()->first == "six");
  2048 + assert(empty.last()->second.getStringValue() == "6");
  2049 + }
  2050 +
  2051 + // Exercise deprecated API until qpdf 11
  2052 + std::cout << "/Bad1: deprecated API" << std::endl;
2099 2053 #ifdef _MSC_VER
2100 2054 # pragma warning (disable: 4996)
2101 2055 #endif
... ... @@ -2103,983 +2057,1169 @@ void runtest(int n, char const* filename1, char const* arg2)
2103 2057 # pragma GCC diagnostic push
2104 2058 # pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2105 2059 #endif
2106   - auto bad1 = QPDFNameTreeObjectHelper(
2107   - pdf.getTrailer().getKey("/Bad1"));
  2060 + auto bad1 = QPDFNameTreeObjectHelper(
  2061 + pdf.getTrailer().getKey("/Bad1"));
2108 2062 #if (defined(__GNUC__) || defined(__clang__))
2109 2063 # pragma GCC diagnostic pop
2110 2064 #endif
2111   - try
2112   - {
2113   - bad1.find("G", true);
2114   - assert(false);
2115   - }
2116   - catch (std::runtime_error& e)
  2065 + try
  2066 + {
  2067 + bad1.find("G", true);
  2068 + assert(false);
  2069 + }
  2070 + catch (std::runtime_error& e)
  2071 + {
  2072 + std::cout << e.what() << std::endl;
  2073 + }
  2074 +
  2075 + std::cout << "/Bad1 -- wrong key type" << std::endl;
  2076 + bad1 = QPDFNameTreeObjectHelper(
  2077 + pdf.getTrailer().getKey("/Bad1"), pdf);
  2078 + assert(bad1.find("G", true)->first == "A");
  2079 + for (auto const& i: bad1)
  2080 + {
  2081 + std::cout << i.first << std::endl;
  2082 + }
  2083 +
  2084 + std::cout << "/Bad2 -- invalid kid" << std::endl;
  2085 + auto bad2 = QPDFNameTreeObjectHelper(
  2086 + pdf.getTrailer().getKey("/Bad2"), pdf);
  2087 + assert(bad2.find("G", true)->first == "B");
  2088 + for (auto const& i: bad2)
  2089 + {
  2090 + std::cout << i.first << std::endl;
  2091 + }
  2092 +
  2093 + std::cout << "/Bad3 -- invalid kid" << std::endl;
  2094 + auto bad3 = QPDFNameTreeObjectHelper(
  2095 + pdf.getTrailer().getKey("/Bad3"), pdf);
  2096 + assert(bad3.find("G", true) == bad3.end());
  2097 +
  2098 + std::cout << "/Bad4 -- invalid kid" << std::endl;
  2099 + auto bad4 = QPDFNameTreeObjectHelper(
  2100 + pdf.getTrailer().getKey("/Bad4"), pdf);
  2101 + assert(bad4.find("F", true)->first == "C");
  2102 + for (auto const& i: bad4)
  2103 + {
  2104 + std::cout << i.first << std::endl;
  2105 + }
  2106 +
  2107 + std::cout << "/Bad5 -- loop in find" << std::endl;
  2108 + auto bad5 = QPDFNameTreeObjectHelper(
  2109 + pdf.getTrailer().getKey("/Bad5"), pdf);
  2110 + assert(bad5.find("F", true)->first == "D");
  2111 +
  2112 + std::cout << "/Bad6 -- bad limits" << std::endl;
  2113 + auto bad6 = QPDFNameTreeObjectHelper(
  2114 + pdf.getTrailer().getKey("/Bad6"), pdf);
  2115 + assert(bad6.insert("H", QPDFObjectHandle::newNull())->first == "H");
  2116 +}
  2117 +
  2118 +static void test_49(QPDF& pdf, char const* arg2)
  2119 +{
  2120 + // Outlines
  2121 + std::vector<QPDFPageObjectHelper> pages =
  2122 + QPDFPageDocumentHelper(pdf).getAllPages();
  2123 + QPDFOutlineDocumentHelper odh(pdf);
  2124 + int pageno = 0;
  2125 + for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
  2126 + iter != pages.end(); ++iter, ++pageno)
  2127 + {
  2128 + std::vector<QPDFOutlineObjectHelper> outlines =
  2129 + odh.getOutlinesForPage((*iter).getObjectHandle().getObjGen());
  2130 + for (std::vector<QPDFOutlineObjectHelper>::iterator oiter =
  2131 + outlines.begin();
  2132 + oiter != outlines.end(); ++oiter)
2117 2133 {
2118   - std::cout << e.what() << std::endl;
  2134 + std::cout
  2135 + << "page " << pageno << ": "
  2136 + << (*oiter).getTitle() << " -> "
  2137 + << (*oiter).getDest().unparseResolved() << std::endl;
2119 2138 }
  2139 + }
  2140 +}
2120 2141  
2121   - std::cout << "/Bad1 -- wrong key type" << std::endl;
2122   - bad1 = QPDFNameTreeObjectHelper(
2123   - pdf.getTrailer().getKey("/Bad1"), pdf);
2124   - assert(bad1.find("G", true)->first == "A");
2125   - for (auto const& i: bad1)
  2142 +static void test_50(QPDF& pdf, char const* arg2)
  2143 +{
  2144 + // Test dictionary merge. This test is crafted to work with
  2145 + // merge-dict.pdf
  2146 + QPDFObjectHandle d1 = pdf.getTrailer().getKey("/Dict1");
  2147 + QPDFObjectHandle d2 = pdf.getTrailer().getKey("/Dict2");
  2148 + d1.mergeResources(d2);
  2149 + std::cout << d1.getJSON().unparse() << std::endl;
  2150 + // Top-level type mismatch
  2151 + d1.mergeResources(d2.getKey("/k1"));
  2152 + std::set<std::string> names = d1.getResourceNames();
  2153 + for (std::set<std::string>::iterator iter = names.begin();
  2154 + iter != names.end(); ++iter)
  2155 + {
  2156 + std::cout << *iter << std::endl;
  2157 + }
  2158 +}
  2159 +
  2160 +static void test_51(QPDF& pdf, char const* arg2)
  2161 +{
  2162 + // Test radio button and checkbox field setting. The input
  2163 + // files must have radios button called r1 and r2 and
  2164 + // checkboxes called checkbox1 and checkbox2. The files
  2165 + // button-set*.pdf are designed for this test case.
  2166 + QPDFObjectHandle acroform = pdf.getRoot().getKey("/AcroForm");
  2167 + QPDFObjectHandle fields = acroform.getKey("/Fields");
  2168 + int nitems = fields.getArrayNItems();
  2169 + for (int i = 0; i < nitems; ++i)
  2170 + {
  2171 + QPDFObjectHandle field = fields.getArrayItem(i);
  2172 + QPDFObjectHandle T = field.getKey("/T");
  2173 + if (! T.isString())
2126 2174 {
2127   - std::cout << i.first << std::endl;
  2175 + continue;
2128 2176 }
2129   -
2130   - std::cout << "/Bad2 -- invalid kid" << std::endl;
2131   - auto bad2 = QPDFNameTreeObjectHelper(
2132   - pdf.getTrailer().getKey("/Bad2"), pdf);
2133   - assert(bad2.find("G", true)->first == "B");
2134   - for (auto const& i: bad2)
  2177 + std::string Tval = T.getUTF8Value();
  2178 + if (Tval == "r1")
2135 2179 {
2136   - std::cout << i.first << std::endl;
  2180 + std::cout << "setting r1 via parent\n";
  2181 + QPDFFormFieldObjectHelper foh(field);
  2182 + foh.setV(QPDFObjectHandle::newName("/2"));
2137 2183 }
2138   -
2139   - std::cout << "/Bad3 -- invalid kid" << std::endl;
2140   - auto bad3 = QPDFNameTreeObjectHelper(
2141   - pdf.getTrailer().getKey("/Bad3"), pdf);
2142   - assert(bad3.find("G", true) == bad3.end());
2143   -
2144   - std::cout << "/Bad4 -- invalid kid" << std::endl;
2145   - auto bad4 = QPDFNameTreeObjectHelper(
2146   - pdf.getTrailer().getKey("/Bad4"), pdf);
2147   - assert(bad4.find("F", true)->first == "C");
2148   - for (auto const& i: bad4)
  2184 + else if (Tval == "r2")
2149 2185 {
2150   - std::cout << i.first << std::endl;
  2186 + std::cout << "setting r2 via child\n";
  2187 + field = field.getKey("/Kids").getArrayItem(1);
  2188 + QPDFFormFieldObjectHelper foh(field);
  2189 + foh.setV(QPDFObjectHandle::newName("/3"));
2151 2190 }
2152   -
2153   - std::cout << "/Bad5 -- loop in find" << std::endl;
2154   - auto bad5 = QPDFNameTreeObjectHelper(
2155   - pdf.getTrailer().getKey("/Bad5"), pdf);
2156   - assert(bad5.find("F", true)->first == "D");
2157   -
2158   - std::cout << "/Bad6 -- bad limits" << std::endl;
2159   - auto bad6 = QPDFNameTreeObjectHelper(
2160   - pdf.getTrailer().getKey("/Bad6"), pdf);
2161   - assert(bad6.insert("H", QPDFObjectHandle::newNull())->first == "H");
2162   - }
2163   - else if (n == 49)
2164   - {
2165   - // Outlines
2166   - std::vector<QPDFPageObjectHelper> pages =
2167   - QPDFPageDocumentHelper(pdf).getAllPages();
2168   - QPDFOutlineDocumentHelper odh(pdf);
2169   - int pageno = 0;
2170   - for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
2171   - iter != pages.end(); ++iter, ++pageno)
  2191 + else if (Tval == "checkbox1")
2172 2192 {
2173   - std::vector<QPDFOutlineObjectHelper> outlines =
2174   - odh.getOutlinesForPage((*iter).getObjectHandle().getObjGen());
2175   - for (std::vector<QPDFOutlineObjectHelper>::iterator oiter =
2176   - outlines.begin();
2177   - oiter != outlines.end(); ++oiter)
2178   - {
2179   - std::cout
2180   - << "page " << pageno << ": "
2181   - << (*oiter).getTitle() << " -> "
2182   - << (*oiter).getDest().unparseResolved() << std::endl;
2183   - }
  2193 + std::cout << "turning checkbox1 on\n";
  2194 + QPDFFormFieldObjectHelper foh(field);
  2195 + foh.setV(QPDFObjectHandle::newName("/Yes"));
2184 2196 }
2185   - }
2186   - else if (n == 50)
2187   - {
2188   - // Test dictionary merge. This test is crafted to work with
2189   - // merge-dict.pdf
2190   - QPDFObjectHandle d1 = pdf.getTrailer().getKey("/Dict1");
2191   - QPDFObjectHandle d2 = pdf.getTrailer().getKey("/Dict2");
2192   - d1.mergeResources(d2);
2193   - std::cout << d1.getJSON().unparse() << std::endl;
2194   - // Top-level type mismatch
2195   - d1.mergeResources(d2.getKey("/k1"));
2196   - std::set<std::string> names = d1.getResourceNames();
2197   - for (std::set<std::string>::iterator iter = names.begin();
2198   - iter != names.end(); ++iter)
  2197 + else if (Tval == "checkbox2")
2199 2198 {
2200   - std::cout << *iter << std::endl;
  2199 + std::cout << "turning checkbox2 off\n";
  2200 + QPDFFormFieldObjectHelper foh(field);
  2201 + foh.setV(QPDFObjectHandle::newName("/Off"));
2201 2202 }
2202 2203 }
2203   - else if (n == 51)
  2204 + QPDFWriter w(pdf, "a.pdf");
  2205 + w.setQDFMode(true);
  2206 + w.setStaticID(true);
  2207 + w.write();
  2208 +}
  2209 +
  2210 +static void test_52(QPDF& pdf, char const* arg2)
  2211 +{
  2212 + // This test just sets a field value for appearance stream
  2213 + // generating testing.
  2214 + QPDFObjectHandle acroform = pdf.getRoot().getKey("/AcroForm");
  2215 + QPDFObjectHandle fields = acroform.getKey("/Fields");
  2216 + int nitems = fields.getArrayNItems();
  2217 + for (int i = 0; i < nitems; ++i)
2204 2218 {
2205   - // Test radio button and checkbox field setting. The input
2206   - // files must have radios button called r1 and r2 and
2207   - // checkboxes called checkbox1 and checkbox2. The files
2208   - // button-set*.pdf are designed for this test case.
2209   - QPDFObjectHandle acroform = pdf.getRoot().getKey("/AcroForm");
2210   - QPDFObjectHandle fields = acroform.getKey("/Fields");
2211   - int nitems = fields.getArrayNItems();
2212   - for (int i = 0; i < nitems; ++i)
  2219 + QPDFObjectHandle field = fields.getArrayItem(i);
  2220 + QPDFObjectHandle T = field.getKey("/T");
  2221 + if (! T.isString())
2213 2222 {
2214   - QPDFObjectHandle field = fields.getArrayItem(i);
2215   - QPDFObjectHandle T = field.getKey("/T");
2216   - if (! T.isString())
2217   - {
2218   - continue;
2219   - }
2220   - std::string Tval = T.getUTF8Value();
2221   - if (Tval == "r1")
2222   - {
2223   - std::cout << "setting r1 via parent\n";
2224   - QPDFFormFieldObjectHelper foh(field);
2225   - foh.setV(QPDFObjectHandle::newName("/2"));
2226   - }
2227   - else if (Tval == "r2")
2228   - {
2229   - std::cout << "setting r2 via child\n";
2230   - field = field.getKey("/Kids").getArrayItem(1);
2231   - QPDFFormFieldObjectHelper foh(field);
2232   - foh.setV(QPDFObjectHandle::newName("/3"));
2233   - }
2234   - else if (Tval == "checkbox1")
2235   - {
2236   - std::cout << "turning checkbox1 on\n";
2237   - QPDFFormFieldObjectHelper foh(field);
2238   - foh.setV(QPDFObjectHandle::newName("/Yes"));
2239   - }
2240   - else if (Tval == "checkbox2")
2241   - {
2242   - std::cout << "turning checkbox2 off\n";
2243   - QPDFFormFieldObjectHelper foh(field);
2244   - foh.setV(QPDFObjectHandle::newName("/Off"));
2245   - }
  2223 + continue;
  2224 + }
  2225 + std::string Tval = T.getUTF8Value();
  2226 + if (Tval == "list1")
  2227 + {
  2228 + std::cout << "setting list1 value\n";
  2229 + QPDFFormFieldObjectHelper foh(field);
  2230 + foh.setV(QPDFObjectHandle::newString(arg2));
2246 2231 }
2247   - QPDFWriter w(pdf, "a.pdf");
2248   - w.setQDFMode(true);
2249   - w.setStaticID(true);
2250   - w.write();
2251 2232 }
2252   - else if (n == 52)
  2233 + QPDFWriter w(pdf, "a.pdf");
  2234 + w.write();
  2235 +}
  2236 +
  2237 +static void test_53(QPDF& pdf, char const* arg2)
  2238 +{
  2239 + // Test get all objects and dangling ref handling
  2240 + QPDFObjectHandle root = pdf.getRoot();
  2241 + root.replaceKey(
  2242 + "/Q1",
  2243 + pdf.makeIndirectObject(QPDFObjectHandle::newString("potato")));
  2244 + std::cout << "all objects" << std::endl;
  2245 + std::vector<QPDFObjectHandle> all = pdf.getAllObjects();
  2246 + for (std::vector<QPDFObjectHandle>::iterator iter = all.begin();
  2247 + iter != all.end(); ++iter)
  2248 + {
  2249 + std::cout << (*iter).unparse() << std::endl;
  2250 + }
  2251 +
  2252 + QPDFWriter w(pdf, "a.pdf");
  2253 + w.setStaticID(true);
  2254 + w.write();
  2255 +}
  2256 +
  2257 +static void test_54(QPDF& pdf, char const* arg2)
  2258 +{
  2259 + // Test getFinalVersion. This must be invoked with a file
  2260 + // whose final version is not 1.5.
  2261 + QPDFWriter w(pdf, "a.pdf");
  2262 + assert(pdf.getPDFVersion() != "1.5");
  2263 + w.setObjectStreamMode(qpdf_o_generate);
  2264 + if (w.getFinalVersion() != "1.5")
2253 2265 {
2254   - // This test just sets a field value for appearance stream
2255   - // generating testing.
2256   - QPDFObjectHandle acroform = pdf.getRoot().getKey("/AcroForm");
2257   - QPDFObjectHandle fields = acroform.getKey("/Fields");
2258   - int nitems = fields.getArrayNItems();
2259   - for (int i = 0; i < nitems; ++i)
2260   - {
2261   - QPDFObjectHandle field = fields.getArrayItem(i);
2262   - QPDFObjectHandle T = field.getKey("/T");
2263   - if (! T.isString())
2264   - {
2265   - continue;
2266   - }
2267   - std::string Tval = T.getUTF8Value();
2268   - if (Tval == "list1")
  2266 + std::cout << "oops: " << w.getFinalVersion() << std::endl;
  2267 + }
  2268 +}
  2269 +
  2270 +static void test_55(QPDF& pdf, char const* arg2)
  2271 +{
  2272 + // Form XObjects
  2273 + std::vector<QPDFPageObjectHelper> pages =
  2274 + QPDFPageDocumentHelper(pdf).getAllPages();
  2275 + QPDFObjectHandle qtest = QPDFObjectHandle::newArray();
  2276 + for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
  2277 + iter != pages.end(); ++iter)
  2278 + {
  2279 + QPDFPageObjectHelper& ph(*iter);
  2280 + qtest.appendItem(ph.getFormXObjectForPage());
  2281 + qtest.appendItem(ph.getFormXObjectForPage(false));
  2282 + }
  2283 + pdf.getTrailer().replaceKey("/QTest", qtest);
  2284 + QPDFWriter w(pdf, "a.pdf");
  2285 + w.setQDFMode(true);
  2286 + w.setStaticID(true);
  2287 + w.write();
  2288 +}
  2289 +
  2290 +static void test_56_59(QPDF& pdf, char const* arg2,
  2291 + bool handle_from_transformation,
  2292 + bool invert_to_transformation)
  2293 +{
  2294 + // red pages are from pdf, blue pages are from pdf2
  2295 + // red pages always have stated rotation absolutely
  2296 + // 56: blue pages are overlaid exactly on top of red pages
  2297 + // 57: blue pages have stated rotation relative to red pages
  2298 + // 58: blue pages have no rotation (absolutely upright)
  2299 + // 59: blue pages have stated rotation absolutely
  2300 +
  2301 + // Placing form XObjects
  2302 + assert(arg2);
  2303 + QPDF pdf2;
  2304 + pdf2.processFile(arg2);
  2305 +
  2306 + std::vector<QPDFPageObjectHelper> pages1 =
  2307 + QPDFPageDocumentHelper(pdf).getAllPages();
  2308 + std::vector<QPDFPageObjectHelper> pages2 =
  2309 + QPDFPageDocumentHelper(pdf2).getAllPages();
  2310 + size_t npages = (pages1.size() < pages2.size()
  2311 + ? pages1.size() : pages2.size());
  2312 + for (size_t i = 0; i < npages; ++i)
  2313 + {
  2314 + QPDFPageObjectHelper& ph1 = pages1.at(i);
  2315 + QPDFPageObjectHelper& ph2 = pages2.at(i);
  2316 + QPDFObjectHandle fo = pdf.copyForeignObject(
  2317 + ph2.getFormXObjectForPage(handle_from_transformation));
  2318 + int min_suffix = 1;
  2319 + QPDFObjectHandle resources = ph1.getAttribute("/Resources", true);
  2320 + std::string name = resources.getUniqueResourceName(
  2321 + "/Fx", min_suffix);
  2322 + std::string content =
  2323 + ph1.placeFormXObject(
  2324 + fo, name, ph1.getTrimBox().getArrayAsRectangle(),
  2325 + invert_to_transformation);
  2326 + if (! content.empty())
  2327 + {
  2328 + resources.mergeResources(
  2329 + QPDFObjectHandle::parse("<< /XObject << >> >>"));
  2330 + resources.getKey("/XObject").replaceKey(name, fo);
  2331 + ph1.addPageContents(
  2332 + QPDFObjectHandle::newStream(&pdf, "q\n"), true);
  2333 + ph1.addPageContents(
  2334 + QPDFObjectHandle::newStream(&pdf, "\nQ\n" + content),
  2335 + false);
  2336 + }
  2337 + }
  2338 + QPDFWriter w(pdf, "a.pdf");
  2339 + w.setQDFMode(true);
  2340 + w.setStaticID(true);
  2341 + w.write();
  2342 +}
  2343 +
  2344 +static void test_56(QPDF& pdf, char const* arg2)
  2345 +{
  2346 + test_56_59(pdf, arg2, false, false);
  2347 +}
  2348 +
  2349 +static void test_57(QPDF& pdf, char const* arg2)
  2350 +{
  2351 + test_56_59(pdf, arg2, true, false);
  2352 +}
  2353 +
  2354 +static void test_58(QPDF& pdf, char const* arg2)
  2355 +{
  2356 + test_56_59(pdf, arg2, false, true);
  2357 +}
  2358 +
  2359 +static void test_59(QPDF& pdf, char const* arg2)
  2360 +{
  2361 + test_56_59(pdf, arg2, true, true);
  2362 +}
  2363 +
  2364 +static void test_60(QPDF& pdf, char const* arg2)
  2365 +{
  2366 + // Boundary condition testing for getUniqueResourceName;
  2367 + // additional testing of mergeResources with conflict
  2368 + // detection
  2369 + QPDFObjectHandle r1 = QPDFObjectHandle::newDictionary();
  2370 + int min_suffix = 1;
  2371 + for (int i = 1; i < 3; ++i)
  2372 + {
  2373 + std::string name = r1.getUniqueResourceName("/Quack", min_suffix);
  2374 + r1.mergeResources(QPDFObjectHandle::parse("<< /Z << >> >>"));
  2375 + r1.getKey("/Z").replaceKey(
  2376 + name, QPDFObjectHandle::newString("moo"));
  2377 + }
  2378 + auto make_resource = [&](QPDFObjectHandle& dict,
  2379 + std::string const& key,
  2380 + std::string const& str) {
  2381 + auto o1 = QPDFObjectHandle::newArray();
  2382 + o1.appendItem(QPDFObjectHandle::newString(str));
  2383 + dict.replaceKey(key, pdf.makeIndirectObject(o1));
  2384 + };
  2385 +
  2386 + auto z = r1.getKey("/Z");
  2387 + r1.replaceKey("/Y", QPDFObjectHandle::newDictionary());
  2388 + auto y = r1.getKey("/Y");
  2389 + make_resource(z, "/F1", "r1.Z.F1");
  2390 + make_resource(z, "/F2", "r1.Z.F2");
  2391 + make_resource(y, "/F2", "r1.Y.F2");
  2392 + make_resource(y, "/F3", "r1.Y.F3");
  2393 + QPDFObjectHandle r2 =
  2394 + QPDFObjectHandle::parse("<< /Z << >> /Y << >> >>");
  2395 + z = r2.getKey("/Z");
  2396 + y = r2.getKey("/Y");
  2397 + make_resource(z, "/F2", "r2.Z.F2");
  2398 + make_resource(y, "/F3", "r2.Y.F3");
  2399 + make_resource(y, "/F4", "r2.Y.F4");
  2400 + // Add a direct object
  2401 + y.replaceKey("/F5", QPDFObjectHandle::newString("direct r2.Y.F5"));
  2402 +
  2403 + std::map<std::string, std::map<std::string, std::string>> conflicts;
  2404 + auto show_conflicts = [&](std::string const& msg) {
  2405 + std::cout << msg << std::endl;
  2406 + for (auto const& i1: conflicts)
  2407 + {
  2408 + std::cout << i1.first << ":" << std::endl;
  2409 + for (auto const& i2: i1.second)
2269 2410 {
2270   - std::cout << "setting list1 value\n";
2271   - QPDFFormFieldObjectHelper foh(field);
2272   - foh.setV(QPDFObjectHandle::newString(arg2));
  2411 + std::cout << " " << i2.first << " -> " << i2.second
  2412 + << std::endl;
2273 2413 }
2274 2414 }
2275   - QPDFWriter w(pdf, "a.pdf");
2276   - w.write();
2277   - }
2278   - else if (n == 53)
2279   - {
2280   - // Test get all objects and dangling ref handling
2281   - QPDFObjectHandle root = pdf.getRoot();
2282   - root.replaceKey(
2283   - "/Q1",
2284   - pdf.makeIndirectObject(QPDFObjectHandle::newString("potato")));
2285   - std::cout << "all objects" << std::endl;
2286   - std::vector<QPDFObjectHandle> all = pdf.getAllObjects();
2287   - for (std::vector<QPDFObjectHandle>::iterator iter = all.begin();
2288   - iter != all.end(); ++iter)
2289   - {
2290   - std::cout << (*iter).unparse() << std::endl;
2291   - }
  2415 + };
  2416 +
  2417 + r1.mergeResources(r2, &conflicts);
  2418 + show_conflicts("first merge");
  2419 + auto r3 = r1.shallowCopy();
  2420 + // Merge again. The direct object gets recopied. Everything
  2421 + // else is the same.
  2422 + r1.mergeResources(r2, &conflicts);
  2423 + show_conflicts("second merge");
  2424 +
  2425 + // Make all resources in r2 direct. Then merge two more times.
  2426 + // We should get the one previously direct object copied one
  2427 + // time as an indirect object.
  2428 + r2.makeResourcesIndirect(pdf);
  2429 + r1.mergeResources(r2, &conflicts);
  2430 + show_conflicts("third merge");
  2431 + r1.mergeResources(r2, &conflicts);
  2432 + show_conflicts("fourth merge");
  2433 +
  2434 + // The only differences between /QTest and /QTest3 should be
  2435 + // the direct objects merged from r2.
  2436 + pdf.getTrailer().replaceKey("/QTest1", r1);
  2437 + pdf.getTrailer().replaceKey("/QTest2", r2);
  2438 + pdf.getTrailer().replaceKey("/QTest3", r3);
  2439 + QPDFWriter w(pdf, "a.pdf");
  2440 + w.setQDFMode(true);
  2441 + w.setStaticID(true);
  2442 + w.write();
  2443 +}
2292 2444  
2293   - QPDFWriter w(pdf, "a.pdf");
2294   - w.setStaticID(true);
2295   - w.write();
  2445 +static void test_61(QPDF& pdf, char const* arg2)
  2446 +{
  2447 + // Test to make sure exceptions can be caught properly across
  2448 + // shared library boundaries.
  2449 + pdf.setAttemptRecovery(false);
  2450 + pdf.setSuppressWarnings(true);
  2451 + try
  2452 + {
  2453 + pdf.processMemoryFile("empty", "", 0);
2296 2454 }
2297   - else if (n == 54)
  2455 + catch (QPDFExc const&)
2298 2456 {
2299   - // Test getFinalVersion. This must be invoked with a file
2300   - // whose final version is not 1.5.
2301   - QPDFWriter w(pdf, "a.pdf");
2302   - assert(pdf.getPDFVersion() != "1.5");
2303   - w.setObjectStreamMode(qpdf_o_generate);
2304   - if (w.getFinalVersion() != "1.5")
2305   - {
2306   - std::cout << "oops: " << w.getFinalVersion() << std::endl;
2307   - }
  2457 + std::cout << "Caught QPDFExc as expected" << std::endl;
2308 2458 }
2309   - else if (n == 55)
  2459 + try
2310 2460 {
2311   - // Form XObjects
2312   - std::vector<QPDFPageObjectHelper> pages =
2313   - QPDFPageDocumentHelper(pdf).getAllPages();
2314   - QPDFObjectHandle qtest = QPDFObjectHandle::newArray();
2315   - for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
2316   - iter != pages.end(); ++iter)
2317   - {
2318   - QPDFPageObjectHelper& ph(*iter);
2319   - qtest.appendItem(ph.getFormXObjectForPage());
2320   - qtest.appendItem(ph.getFormXObjectForPage(false));
2321   - }
2322   - pdf.getTrailer().replaceKey("/QTest", qtest);
2323   - QPDFWriter w(pdf, "a.pdf");
2324   - w.setQDFMode(true);
2325   - w.setStaticID(true);
2326   - w.write();
  2461 + QUtil::safe_fopen("/does/not/exist", "r");
2327 2462 }
2328   - else if ((n >= 56) && (n <= 59))
  2463 + catch (QPDFSystemError const&)
2329 2464 {
2330   - // Placing form XObjects
2331   - assert(arg2);
2332   - QPDF pdf2;
2333   - pdf2.processFile(arg2);
2334   -
2335   - // red pages are from pdf, blue pages are from pdf2
2336   - // red pages always have stated rotation absolutely
2337   - // 56: blue pages are overlaid exactly on top of red pages
2338   - // 57: blue pages have stated rotation relative to red pages
2339   - // 58: blue pages have no rotation (absolutely upright)
2340   - // 59: blue pages have stated rotation absolutely
2341   - bool handle_from_transformation = ((n == 57) || (n == 59));
2342   - bool invert_to_transformation = ((n == 58) || (n == 59));
2343   -
2344   - std::vector<QPDFPageObjectHelper> pages1 =
2345   - QPDFPageDocumentHelper(pdf).getAllPages();
2346   - std::vector<QPDFPageObjectHelper> pages2 =
2347   - QPDFPageDocumentHelper(pdf2).getAllPages();
2348   - size_t npages = (pages1.size() < pages2.size()
2349   - ? pages1.size() : pages2.size());
2350   - for (size_t i = 0; i < npages; ++i)
2351   - {
2352   - QPDFPageObjectHelper& ph1 = pages1.at(i);
2353   - QPDFPageObjectHelper& ph2 = pages2.at(i);
2354   - QPDFObjectHandle fo = pdf.copyForeignObject(
2355   - ph2.getFormXObjectForPage(handle_from_transformation));
2356   - int min_suffix = 1;
2357   - QPDFObjectHandle resources = ph1.getAttribute("/Resources", true);
2358   - std::string name = resources.getUniqueResourceName(
2359   - "/Fx", min_suffix);
2360   - std::string content =
2361   - ph1.placeFormXObject(
2362   - fo, name, ph1.getTrimBox().getArrayAsRectangle(),
2363   - invert_to_transformation);
2364   - if (! content.empty())
2365   - {
2366   - resources.mergeResources(
2367   - QPDFObjectHandle::parse("<< /XObject << >> >>"));
2368   - resources.getKey("/XObject").replaceKey(name, fo);
2369   - ph1.addPageContents(
2370   - QPDFObjectHandle::newStream(&pdf, "q\n"), true);
2371   - ph1.addPageContents(
2372   - QPDFObjectHandle::newStream(&pdf, "\nQ\n" + content),
2373   - false);
2374   - }
2375   - }
2376   - QPDFWriter w(pdf, "a.pdf");
2377   - w.setQDFMode(true);
2378   - w.setStaticID(true);
2379   - w.write();
  2465 + std::cout << "Caught QPDFSystemError as expected" << std::endl;
2380 2466 }
2381   - else if (n == 60)
  2467 + try
2382 2468 {
2383   - // Boundary condition testing for getUniqueResourceName;
2384   - // additional testing of mergeResources with conflict
2385   - // detection
2386   - QPDFObjectHandle r1 = QPDFObjectHandle::newDictionary();
2387   - int min_suffix = 1;
2388   - for (int i = 1; i < 3; ++i)
2389   - {
2390   - std::string name = r1.getUniqueResourceName("/Quack", min_suffix);
2391   - r1.mergeResources(QPDFObjectHandle::parse("<< /Z << >> >>"));
2392   - r1.getKey("/Z").replaceKey(
2393   - name, QPDFObjectHandle::newString("moo"));
2394   - }
2395   - auto make_resource = [&](QPDFObjectHandle& dict,
2396   - std::string const& key,
2397   - std::string const& str) {
2398   - auto o1 = QPDFObjectHandle::newArray();
2399   - o1.appendItem(QPDFObjectHandle::newString(str));
2400   - dict.replaceKey(key, pdf.makeIndirectObject(o1));
2401   - };
2402   -
2403   - auto z = r1.getKey("/Z");
2404   - r1.replaceKey("/Y", QPDFObjectHandle::newDictionary());
2405   - auto y = r1.getKey("/Y");
2406   - make_resource(z, "/F1", "r1.Z.F1");
2407   - make_resource(z, "/F2", "r1.Z.F2");
2408   - make_resource(y, "/F2", "r1.Y.F2");
2409   - make_resource(y, "/F3", "r1.Y.F3");
2410   - QPDFObjectHandle r2 =
2411   - QPDFObjectHandle::parse("<< /Z << >> /Y << >> >>");
2412   - z = r2.getKey("/Z");
2413   - y = r2.getKey("/Y");
2414   - make_resource(z, "/F2", "r2.Z.F2");
2415   - make_resource(y, "/F3", "r2.Y.F3");
2416   - make_resource(y, "/F4", "r2.Y.F4");
2417   - // Add a direct object
2418   - y.replaceKey("/F5", QPDFObjectHandle::newString("direct r2.Y.F5"));
2419   -
2420   - std::map<std::string, std::map<std::string, std::string>> conflicts;
2421   - auto show_conflicts = [&](std::string const& msg) {
2422   - std::cout << msg << std::endl;
2423   - for (auto const& i1: conflicts)
2424   - {
2425   - std::cout << i1.first << ":" << std::endl;
2426   - for (auto const& i2: i1.second)
2427   - {
2428   - std::cout << " " << i2.first << " -> " << i2.second
2429   - << std::endl;
2430   - }
2431   - }
2432   - };
2433   -
2434   - r1.mergeResources(r2, &conflicts);
2435   - show_conflicts("first merge");
2436   - auto r3 = r1.shallowCopy();
2437   - // Merge again. The direct object gets recopied. Everything
2438   - // else is the same.
2439   - r1.mergeResources(r2, &conflicts);
2440   - show_conflicts("second merge");
2441   -
2442   - // Make all resources in r2 direct. Then merge two more times.
2443   - // We should get the one previously direct object copied one
2444   - // time as an indirect object.
2445   - r2.makeResourcesIndirect(pdf);
2446   - r1.mergeResources(r2, &conflicts);
2447   - show_conflicts("third merge");
2448   - r1.mergeResources(r2, &conflicts);
2449   - show_conflicts("fourth merge");
2450   -
2451   - // The only differences between /QTest and /QTest3 should be
2452   - // the direct objects merged from r2.
2453   - pdf.getTrailer().replaceKey("/QTest1", r1);
2454   - pdf.getTrailer().replaceKey("/QTest2", r2);
2455   - pdf.getTrailer().replaceKey("/QTest3", r3);
2456   - QPDFWriter w(pdf, "a.pdf");
2457   - w.setQDFMode(true);
2458   - w.setStaticID(true);
2459   - w.write();
  2469 + QUtil::int_to_string_base(0, 12);
2460 2470 }
2461   - else if (n == 61)
  2471 + catch (std::logic_error const&)
2462 2472 {
2463   - // Test to make sure exceptions can be caught properly across
2464   - // shared library boundaries.
2465   - pdf.setAttemptRecovery(false);
2466   - pdf.setSuppressWarnings(true);
2467   - try
2468   - {
2469   - pdf.processMemoryFile("empty", "", 0);
2470   - }
2471   - catch (QPDFExc const&)
2472   - {
2473   - std::cout << "Caught QPDFExc as expected" << std::endl;
2474   - }
2475   - try
2476   - {
2477   - QUtil::safe_fopen("/does/not/exist", "r");
2478   - }
2479   - catch (QPDFSystemError const&)
2480   - {
2481   - std::cout << "Caught QPDFSystemError as expected" << std::endl;
2482   - }
2483   - try
2484   - {
2485   - QUtil::int_to_string_base(0, 12);
2486   - }
2487   - catch (std::logic_error const&)
2488   - {
2489   - std::cout << "Caught logic_error as expected" << std::endl;
2490   - }
2491   - try
2492   - {
2493   - QUtil::toUTF8(0xffffffff);
2494   - }
2495   - catch (std::runtime_error const&)
2496   - {
2497   - std::cout << "Caught runtime_error as expected" << std::endl;
2498   - }
  2473 + std::cout << "Caught logic_error as expected" << std::endl;
2499 2474 }
2500   - else if (n == 62)
2501   - {
2502   - // Test int size checks. This test will fail if int and long
2503   - // long are the same size.
2504   - QPDFObjectHandle t = pdf.getTrailer();
2505   - unsigned long long q1_l = 3ULL * QIntC::to_ulonglong(INT_MAX);
2506   - long long q1 = QIntC::to_longlong(q1_l);
2507   - long long q2_l = 3LL * QIntC::to_longlong(INT_MIN);
2508   - long long q2 = QIntC::to_longlong(q2_l);
2509   - unsigned int q3_i = UINT_MAX;
2510   - long long q3 = QIntC::to_longlong(q3_i);
2511   - t.replaceKey("/Q1", QPDFObjectHandle::newInteger(q1));
2512   - t.replaceKey("/Q2", QPDFObjectHandle::newInteger(q2));
2513   - t.replaceKey("/Q3", QPDFObjectHandle::newInteger(q3));
2514   - assert_compare_numbers(q1, t.getKey("/Q1").getIntValue());
2515   - assert_compare_numbers(q1_l, t.getKey("/Q1").getUIntValue());
2516   - assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt());
2517   - assert_compare_numbers(UINT_MAX, t.getKey("/Q1").getUIntValueAsUInt());
2518   - assert_compare_numbers(q2_l, t.getKey("/Q2").getIntValue());
2519   - assert_compare_numbers(0U, t.getKey("/Q2").getUIntValue());
2520   - assert_compare_numbers(INT_MIN, t.getKey("/Q2").getIntValueAsInt());
2521   - assert_compare_numbers(0U, t.getKey("/Q2").getUIntValueAsUInt());
2522   - assert_compare_numbers(INT_MAX, t.getKey("/Q3").getIntValueAsInt());
2523   - assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt());
2524   - }
2525   - else if (n == 63)
  2475 + try
2526 2476 {
2527   - QPDFWriter w(pdf);
2528   - // Exercise setting encryption parameters before setting the
2529   - // output filename. The previous bug does not happen if static
2530   - // or deterministic ID is used because the filename is not
2531   - // used as part of the input data for ID generation in those
2532   - // cases.
2533   - w.setR6EncryptionParameters(
2534   - "u", "o", true, true, true, true, true, true, qpdf_r3p_full, true);
2535   - w.setOutputFilename("a.pdf");
2536   - w.write();
  2477 + QUtil::toUTF8(0xffffffff);
2537 2478 }
2538   - else if ((n >= 64) && (n <= 67))
  2479 + catch (std::runtime_error const&)
2539 2480 {
2540   - // Placing form XObjects: expand, shrink
2541   - assert(arg2);
2542   - QPDF pdf2;
2543   - pdf2.processFile(arg2);
2544   -
2545   - // Overlay file2 on file1.
2546   - // 64: allow neither shrink nor shrink
2547   - // 65: allow shrink but not expand
2548   - // 66: allow expand but not shrink
2549   - // 67: allow both shrink and expand
2550   - bool allow_shrink = ((n == 65) || (n == 67));
2551   - bool allow_expand = ((n == 66) || (n == 67));
2552   - std::vector<QPDFPageObjectHelper> pages1 =
2553   - QPDFPageDocumentHelper(pdf).getAllPages();
2554   - std::vector<QPDFPageObjectHelper> pages2 =
2555   - QPDFPageDocumentHelper(pdf2).getAllPages();
2556   - size_t npages = (pages1.size() < pages2.size()
2557   - ? pages1.size() : pages2.size());
2558   - for (size_t i = 0; i < npages; ++i)
2559   - {
2560   - QPDFPageObjectHelper& ph1 = pages1.at(i);
2561   - QPDFPageObjectHelper& ph2 = pages2.at(i);
2562   - QPDFObjectHandle fo = pdf.copyForeignObject(
2563   - ph2.getFormXObjectForPage());
2564   - int min_suffix = 1;
2565   - QPDFObjectHandle resources = ph1.getAttribute("/Resources", true);
2566   - std::string name = resources.getUniqueResourceName(
2567   - "/Fx", min_suffix);
2568   - std::string content =
2569   - ph1.placeFormXObject(
2570   - fo, name, ph1.getTrimBox().getArrayAsRectangle(),
2571   - false, allow_shrink, allow_expand);
2572   - if (! content.empty())
2573   - {
2574   - resources.mergeResources(
2575   - QPDFObjectHandle::parse("<< /XObject << >> >>"));
2576   - resources.getKey("/XObject").replaceKey(name, fo);
2577   - ph1.addPageContents(
2578   - QPDFObjectHandle::newStream(&pdf, "q\n"), true);
2579   - ph1.addPageContents(
2580   - QPDFObjectHandle::newStream(&pdf, "\nQ\n" + content),
2581   - false);
2582   - }
2583   - }
2584   - QPDFWriter w(pdf, "a.pdf");
2585   - w.setQDFMode(true);
2586   - w.setStaticID(true);
2587   - w.write();
  2481 + std::cout << "Caught runtime_error as expected" << std::endl;
2588 2482 }
2589   - else if (n == 68)
  2483 +}
  2484 +
  2485 +static void test_62(QPDF& pdf, char const* arg2)
  2486 +{
  2487 + // Test int size checks. This test will fail if int and long
  2488 + // long are the same size.
  2489 + QPDFObjectHandle t = pdf.getTrailer();
  2490 + unsigned long long q1_l = 3ULL * QIntC::to_ulonglong(INT_MAX);
  2491 + long long q1 = QIntC::to_longlong(q1_l);
  2492 + long long q2_l = 3LL * QIntC::to_longlong(INT_MIN);
  2493 + long long q2 = QIntC::to_longlong(q2_l);
  2494 + unsigned int q3_i = UINT_MAX;
  2495 + long long q3 = QIntC::to_longlong(q3_i);
  2496 + t.replaceKey("/Q1", QPDFObjectHandle::newInteger(q1));
  2497 + t.replaceKey("/Q2", QPDFObjectHandle::newInteger(q2));
  2498 + t.replaceKey("/Q3", QPDFObjectHandle::newInteger(q3));
  2499 + assert_compare_numbers(q1, t.getKey("/Q1").getIntValue());
  2500 + assert_compare_numbers(q1_l, t.getKey("/Q1").getUIntValue());
  2501 + assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt());
  2502 + assert_compare_numbers(UINT_MAX, t.getKey("/Q1").getUIntValueAsUInt());
  2503 + assert_compare_numbers(q2_l, t.getKey("/Q2").getIntValue());
  2504 + assert_compare_numbers(0U, t.getKey("/Q2").getUIntValue());
  2505 + assert_compare_numbers(INT_MIN, t.getKey("/Q2").getIntValueAsInt());
  2506 + assert_compare_numbers(0U, t.getKey("/Q2").getUIntValueAsUInt());
  2507 + assert_compare_numbers(INT_MAX, t.getKey("/Q3").getIntValueAsInt());
  2508 + assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt());
  2509 +}
  2510 +
  2511 +static void test_63(QPDF& pdf, char const* arg2)
  2512 +{
  2513 + QPDFWriter w(pdf);
  2514 + // Exercise setting encryption parameters before setting the
  2515 + // output filename. The previous bug does not happen if static
  2516 + // or deterministic ID is used because the filename is not
  2517 + // used as part of the input data for ID generation in those
  2518 + // cases.
  2519 + w.setR6EncryptionParameters(
  2520 + "u", "o", true, true, true, true, true, true, qpdf_r3p_full, true);
  2521 + w.setOutputFilename("a.pdf");
  2522 + w.write();
  2523 +}
  2524 +
  2525 +static void test_64_67(QPDF& pdf, char const* arg2,
  2526 + bool allow_shrink, bool allow_expand)
  2527 +{
  2528 + // Overlay file2 on file1.
  2529 + // 64: allow neither shrink nor shrink
  2530 + // 65: allow shrink but not expand
  2531 + // 66: allow expand but not shrink
  2532 + // 67: allow both shrink and expand
  2533 +
  2534 + // Placing form XObjects: expand, shrink
  2535 + assert(arg2);
  2536 + QPDF pdf2;
  2537 + pdf2.processFile(arg2);
  2538 +
  2539 + std::vector<QPDFPageObjectHelper> pages1 =
  2540 + QPDFPageDocumentHelper(pdf).getAllPages();
  2541 + std::vector<QPDFPageObjectHelper> pages2 =
  2542 + QPDFPageDocumentHelper(pdf2).getAllPages();
  2543 + size_t npages = (pages1.size() < pages2.size()
  2544 + ? pages1.size() : pages2.size());
  2545 + for (size_t i = 0; i < npages; ++i)
  2546 + {
  2547 + QPDFPageObjectHelper& ph1 = pages1.at(i);
  2548 + QPDFPageObjectHelper& ph2 = pages2.at(i);
  2549 + QPDFObjectHandle fo = pdf.copyForeignObject(
  2550 + ph2.getFormXObjectForPage());
  2551 + int min_suffix = 1;
  2552 + QPDFObjectHandle resources = ph1.getAttribute("/Resources", true);
  2553 + std::string name = resources.getUniqueResourceName(
  2554 + "/Fx", min_suffix);
  2555 + std::string content =
  2556 + ph1.placeFormXObject(
  2557 + fo, name, ph1.getTrimBox().getArrayAsRectangle(),
  2558 + false, allow_shrink, allow_expand);
  2559 + if (! content.empty())
  2560 + {
  2561 + resources.mergeResources(
  2562 + QPDFObjectHandle::parse("<< /XObject << >> >>"));
  2563 + resources.getKey("/XObject").replaceKey(name, fo);
  2564 + ph1.addPageContents(
  2565 + QPDFObjectHandle::newStream(&pdf, "q\n"), true);
  2566 + ph1.addPageContents(
  2567 + QPDFObjectHandle::newStream(&pdf, "\nQ\n" + content),
  2568 + false);
  2569 + }
  2570 + }
  2571 + QPDFWriter w(pdf, "a.pdf");
  2572 + w.setQDFMode(true);
  2573 + w.setStaticID(true);
  2574 + w.write();
  2575 +}
  2576 +
  2577 +static void test_64(QPDF& pdf, char const* arg2)
  2578 +{
  2579 + test_64_67(pdf, arg2, false, false);
  2580 +}
  2581 +
  2582 +static void test_65(QPDF& pdf, char const* arg2)
  2583 +{
  2584 + test_64_67(pdf, arg2, true, false);
  2585 +}
  2586 +
  2587 +static void test_66(QPDF& pdf, char const* arg2)
  2588 +{
  2589 + test_64_67(pdf, arg2, false, true);
  2590 +}
  2591 +
  2592 +static void test_67(QPDF& pdf, char const* arg2)
  2593 +{
  2594 + test_64_67(pdf, arg2, true, true);
  2595 +}
  2596 +
  2597 +static void test_68(QPDF& pdf, char const* arg2)
  2598 +{
  2599 + QPDFObjectHandle root = pdf.getRoot();
  2600 + QPDFObjectHandle qstream = root.getKey("/QStream");
  2601 + try
2590 2602 {
2591   - QPDFObjectHandle root = pdf.getRoot();
2592   - QPDFObjectHandle qstream = root.getKey("/QStream");
2593   - try
2594   - {
2595   - qstream.getStreamData();
2596   - std::cout << "oops -- didn't throw" << std::endl;
2597   - }
2598   - catch (std::exception& e)
2599   - {
2600   - std::cout << "get unfilterable stream: " << e.what()
2601   - << std::endl;
2602   - }
2603   - PointerHolder<Buffer> b1 = qstream.getStreamData(qpdf_dl_all);
2604   - if ((b1->getSize() > 10) &&
2605   - (memcmp(b1->getBuffer(),
2606   - "wwwwwwwww", 9) == 0))
2607   - {
2608   - std::cout << "filtered stream data okay" << std::endl;
2609   - }
2610   - PointerHolder<Buffer> b2 = qstream.getRawStreamData();
2611   - if ((b2->getSize() > 10) &&
2612   - (memcmp(b2->getBuffer(),
2613   - "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46", 10) == 0))
2614   - {
2615   - std::cout << "raw stream data okay" << std::endl;
2616   - }
  2603 + qstream.getStreamData();
  2604 + std::cout << "oops -- didn't throw" << std::endl;
2617 2605 }
2618   - else if (n == 69)
  2606 + catch (std::exception& e)
2619 2607 {
2620   - pdf.setImmediateCopyFrom(true);
2621   - auto pages = pdf.getAllPages();
2622   - for (size_t i = 0; i < pages.size(); ++i)
2623   - {
2624   - QPDF out;
2625   - out.emptyPDF();
2626   - out.addPage(pages.at(i), false);
2627   - std::string outname = std::string("auto-") +
2628   - QUtil::uint_to_string(i) + ".pdf";
2629   - QPDFWriter w(out, outname.c_str());
2630   - w.setStaticID(true);
2631   - w.write();
2632   - }
  2608 + std::cout << "get unfilterable stream: " << e.what()
  2609 + << std::endl;
2633 2610 }
2634   - else if (n == 70)
  2611 + PointerHolder<Buffer> b1 = qstream.getStreamData(qpdf_dl_all);
  2612 + if ((b1->getSize() > 10) &&
  2613 + (memcmp(b1->getBuffer(),
  2614 + "wwwwwwwww", 9) == 0))
2635 2615 {
2636   - auto trailer = pdf.getTrailer();
2637   - trailer.getKey("/S1").setFilterOnWrite(false);
2638   - trailer.getKey("/S2").setFilterOnWrite(false);
2639   - QPDFWriter w(pdf, "a.pdf");
  2616 + std::cout << "filtered stream data okay" << std::endl;
  2617 + }
  2618 + PointerHolder<Buffer> b2 = qstream.getRawStreamData();
  2619 + if ((b2->getSize() > 10) &&
  2620 + (memcmp(b2->getBuffer(),
  2621 + "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46", 10) == 0))
  2622 + {
  2623 + std::cout << "raw stream data okay" << std::endl;
  2624 + }
  2625 +}
  2626 +
  2627 +static void test_69(QPDF& pdf, char const* arg2)
  2628 +{
  2629 + pdf.setImmediateCopyFrom(true);
  2630 + auto pages = pdf.getAllPages();
  2631 + for (size_t i = 0; i < pages.size(); ++i)
  2632 + {
  2633 + QPDF out;
  2634 + out.emptyPDF();
  2635 + out.addPage(pages.at(i), false);
  2636 + std::string outname = std::string("auto-") +
  2637 + QUtil::uint_to_string(i) + ".pdf";
  2638 + QPDFWriter w(out, outname.c_str());
2640 2639 w.setStaticID(true);
2641   - w.setDecodeLevel(qpdf_dl_specialized);
2642 2640 w.write();
2643 2641 }
2644   - else if (n == 71)
2645   - {
2646   - auto show = [](QPDFObjectHandle& obj,
2647   - QPDFObjectHandle& xobj_dict,
2648   - std::string const& key) {
2649   - std::cout << xobj_dict.unparse() << " -> "
2650   - << key << " -> " << obj.unparse() << std::endl;
2651   - };
2652   - auto page = QPDFPageDocumentHelper(pdf).getAllPages().at(0);
2653   - std::cout << "--- recursive, all ---" << std::endl;
2654   - page.forEachXObject(true, show);
2655   - std::cout << "--- non-recursive, all ---" << std::endl;
2656   - page.forEachXObject(false, show);
2657   - std::cout << "--- recursive, images ---" << std::endl;
2658   - page.forEachImage(true, show);
2659   - std::cout << "--- non-recursive, images ---" << std::endl;
2660   - page.forEachImage(false, show);
2661   - std::cout << "--- recursive, form XObjects ---" << std::endl;
2662   - page.forEachFormXObject(true, show);
2663   - std::cout << "--- non-recursive, form XObjects ---" << std::endl;
2664   - page.forEachFormXObject(false, show);
2665   - auto fx1 = QPDFPageObjectHelper(
2666   - page.getObjectHandle()
2667   - .getKey("/Resources")
2668   - .getKey("/XObject")
2669   - .getKey("/Fx1"));
2670   - std::cout << "--- recursive, all, from fx1 ---" << std::endl;
2671   - fx1.forEachXObject(true, show);
2672   - std::cout << "--- non-recursive, all, from fx1 ---" << std::endl;
2673   - fx1.forEachXObject(false, show);
2674   - std::cout << "--- get images, page ---" << std::endl;
2675   - for (auto& i: page.getImages())
2676   - {
2677   - std::cout << i.first << " -> " << i.second.unparse() << std::endl;
2678   - }
2679   - std::cout << "--- get images, fx ---" << std::endl;
2680   - for (auto& i: fx1.getImages())
2681   - {
2682   - std::cout << i.first << " -> " << i.second.unparse() << std::endl;
2683   - }
2684   - std::cout << "--- get form XObjects, page ---" << std::endl;
2685   - for (auto& i: page.getFormXObjects())
  2642 +}
  2643 +
  2644 +static void test_70(QPDF& pdf, char const* arg2)
  2645 +{
  2646 + auto trailer = pdf.getTrailer();
  2647 + trailer.getKey("/S1").setFilterOnWrite(false);
  2648 + trailer.getKey("/S2").setFilterOnWrite(false);
  2649 + QPDFWriter w(pdf, "a.pdf");
  2650 + w.setStaticID(true);
  2651 + w.setDecodeLevel(qpdf_dl_specialized);
  2652 + w.write();
  2653 +}
  2654 +
  2655 +static void test_71(QPDF& pdf, char const* arg2)
  2656 +{
  2657 + auto show = [](QPDFObjectHandle& obj,
  2658 + QPDFObjectHandle& xobj_dict,
  2659 + std::string const& key) {
  2660 + std::cout << xobj_dict.unparse() << " -> "
  2661 + << key << " -> " << obj.unparse() << std::endl;
  2662 + };
  2663 + auto page = QPDFPageDocumentHelper(pdf).getAllPages().at(0);
  2664 + std::cout << "--- recursive, all ---" << std::endl;
  2665 + page.forEachXObject(true, show);
  2666 + std::cout << "--- non-recursive, all ---" << std::endl;
  2667 + page.forEachXObject(false, show);
  2668 + std::cout << "--- recursive, images ---" << std::endl;
  2669 + page.forEachImage(true, show);
  2670 + std::cout << "--- non-recursive, images ---" << std::endl;
  2671 + page.forEachImage(false, show);
  2672 + std::cout << "--- recursive, form XObjects ---" << std::endl;
  2673 + page.forEachFormXObject(true, show);
  2674 + std::cout << "--- non-recursive, form XObjects ---" << std::endl;
  2675 + page.forEachFormXObject(false, show);
  2676 + auto fx1 = QPDFPageObjectHelper(
  2677 + page.getObjectHandle()
  2678 + .getKey("/Resources")
  2679 + .getKey("/XObject")
  2680 + .getKey("/Fx1"));
  2681 + std::cout << "--- recursive, all, from fx1 ---" << std::endl;
  2682 + fx1.forEachXObject(true, show);
  2683 + std::cout << "--- non-recursive, all, from fx1 ---" << std::endl;
  2684 + fx1.forEachXObject(false, show);
  2685 + std::cout << "--- get images, page ---" << std::endl;
  2686 + for (auto& i: page.getImages())
  2687 + {
  2688 + std::cout << i.first << " -> " << i.second.unparse() << std::endl;
  2689 + }
  2690 + std::cout << "--- get images, fx ---" << std::endl;
  2691 + for (auto& i: fx1.getImages())
  2692 + {
  2693 + std::cout << i.first << " -> " << i.second.unparse() << std::endl;
  2694 + }
  2695 + std::cout << "--- get form XObjects, page ---" << std::endl;
  2696 + for (auto& i: page.getFormXObjects())
  2697 + {
  2698 + std::cout << i.first << " -> " << i.second.unparse() << std::endl;
  2699 + }
  2700 + std::cout << "--- get form XObjects, fx ---" << std::endl;
  2701 + for (auto& i: fx1.getFormXObjects())
  2702 + {
  2703 + std::cout << i.first << " -> " << i.second.unparse() << std::endl;
  2704 + }
  2705 +}
  2706 +
  2707 +static void test_72(QPDF& pdf, char const* arg2)
  2708 +{
  2709 + // Call some QPDFPageObjectHelper methods on form XObjects.
  2710 + auto page = QPDFPageDocumentHelper(pdf).getAllPages().at(0);
  2711 + auto fx1 = QPDFPageObjectHelper(
  2712 + page.getObjectHandle()
  2713 + .getKey("/Resources")
  2714 + .getKey("/XObject")
  2715 + .getKey("/Fx1"));
  2716 + std::cout << "--- parseContents ---" << std::endl;
  2717 + ParserCallbacks cb;
  2718 + fx1.parseContents(&cb);
  2719 + // Do this once with addContentTokenFilter and once with
  2720 + // addTokenFilter to show that they are the same and to ensure
  2721 + // that addTokenFilter is directly exercised in testing.
  2722 + for (int i = 0; i < 2; i++)
  2723 + {
  2724 + Pl_Buffer b("buffer");
  2725 + if (i == 0)
2686 2726 {
2687   - std::cout << i.first << " -> " << i.second.unparse() << std::endl;
  2727 + fx1.addContentTokenFilter(new TokenFilter);
2688 2728 }
2689   - std::cout << "--- get form XObjects, fx ---" << std::endl;
2690   - for (auto& i: fx1.getFormXObjects())
  2729 + else
2691 2730 {
2692   - std::cout << i.first << " -> " << i.second.unparse() << std::endl;
  2731 + fx1.getObjectHandle().addTokenFilter(new TokenFilter);
2693 2732 }
  2733 + fx1.pipeContents(&b);
  2734 + std::unique_ptr<Buffer> buf(b.getBuffer());
  2735 + std::string s(
  2736 + reinterpret_cast<char const*>(buf->getBuffer()),
  2737 + buf->getSize());
  2738 + assert(s.find("/bye") != std::string::npos);
2694 2739 }
2695   - else if (n == 72)
  2740 +}
  2741 +
  2742 +static void test_73(QPDF& pdf, char const* arg2)
  2743 +{
  2744 + try
2696 2745 {
2697   - // Call some QPDFPageObjectHelper methods on form XObjects.
2698   - auto page = QPDFPageDocumentHelper(pdf).getAllPages().at(0);
2699   - auto fx1 = QPDFPageObjectHelper(
2700   - page.getObjectHandle()
2701   - .getKey("/Resources")
2702   - .getKey("/XObject")
2703   - .getKey("/Fx1"));
2704   - std::cout << "--- parseContents ---" << std::endl;
2705   - ParserCallbacks cb;
2706   - fx1.parseContents(&cb);
2707   - // Do this once with addContentTokenFilter and once with
2708   - // addTokenFilter to show that they are the same and to ensure
2709   - // that addTokenFilter is directly exercised in testing.
2710   - for (int i = 0; i < 2; i++)
2711   - {
2712   - Pl_Buffer b("buffer");
2713   - if (i == 0)
2714   - {
2715   - fx1.addContentTokenFilter(new TokenFilter);
2716   - }
2717   - else
2718   - {
2719   - fx1.getObjectHandle().addTokenFilter(new TokenFilter);
2720   - }
2721   - fx1.pipeContents(&b);
2722   - std::unique_ptr<Buffer> buf(b.getBuffer());
2723   - std::string s(
2724   - reinterpret_cast<char const*>(buf->getBuffer()),
2725   - buf->getSize());
2726   - assert(s.find("/bye") != std::string::npos);
2727   - }
  2746 + QPDF pdf2;
  2747 + pdf2.getRoot();
2728 2748 }
2729   - else if (n == 73)
  2749 + catch (std::exception& e)
2730 2750 {
2731   - try
2732   - {
2733   - QPDF pdf2;
2734   - pdf2.getRoot();
2735   - }
2736   - catch (std::exception& e)
2737   - {
2738   - std::cerr << "getRoot: " << e.what() << std::endl;
2739   - }
  2751 + std::cerr << "getRoot: " << e.what() << std::endl;
  2752 + }
2740 2753  
2741   - pdf.closeInputSource();
2742   - pdf.getRoot().getKey("/Pages").unparseResolved();
2743   - }
2744   - else if (n == 74)
2745   - {
2746   - // This test is crafted to work with split-nntree.pdf
2747   - std::cout << "/Split1" << std::endl;
2748   - auto split1 = QPDFNumberTreeObjectHelper(
2749   - pdf.getTrailer().getKey("/Split1"), pdf);
2750   - split1.setSplitThreshold(4);
2751   - auto check_split1 = [&split1](int k) {
2752   - auto i = split1.insert(k, QPDFObjectHandle::newString(
2753   - QUtil::int_to_string(k)));
2754   - assert(i->first == k);
2755   - };
2756   - check_split1(15);
2757   - check_split1(35);
2758   - check_split1(125);
2759   - for (auto const& i: split1)
2760   - {
2761   - std::cout << i.first << std::endl;
2762   - }
  2754 + pdf.closeInputSource();
  2755 + pdf.getRoot().getKey("/Pages").unparseResolved();
  2756 +}
2763 2757  
2764   - std::cout << "/Split2" << std::endl;
2765   - auto split2 = QPDFNameTreeObjectHelper(
2766   - pdf.getTrailer().getKey("/Split2"), pdf);
2767   - split2.setSplitThreshold(4);
2768   - auto check_split2 = [](QPDFNameTreeObjectHelper& noh,
2769   - std::string const& k) {
2770   - auto i = noh.insert(k, QPDFObjectHandle::newUnicodeString(k));
2771   - assert(i->first == k);
2772   - };
2773   - check_split2(split2, "C");
2774   - for (auto const& i: split2)
2775   - {
2776   - std::cout << i.first << std::endl;
2777   - }
  2758 +static void test_74(QPDF& pdf, char const* arg2)
  2759 +{
  2760 + // This test is crafted to work with split-nntree.pdf
  2761 + std::cout << "/Split1" << std::endl;
  2762 + auto split1 = QPDFNumberTreeObjectHelper(
  2763 + pdf.getTrailer().getKey("/Split1"), pdf);
  2764 + split1.setSplitThreshold(4);
  2765 + auto check_split1 = [&split1](int k) {
  2766 + auto i = split1.insert(k, QPDFObjectHandle::newString(
  2767 + QUtil::int_to_string(k)));
  2768 + assert(i->first == k);
  2769 + };
  2770 + check_split1(15);
  2771 + check_split1(35);
  2772 + check_split1(125);
  2773 + for (auto const& i: split1)
  2774 + {
  2775 + std::cout << i.first << std::endl;
  2776 + }
  2777 +
  2778 + std::cout << "/Split2" << std::endl;
  2779 + auto split2 = QPDFNameTreeObjectHelper(
  2780 + pdf.getTrailer().getKey("/Split2"), pdf);
  2781 + split2.setSplitThreshold(4);
  2782 + auto check_split2 = [](QPDFNameTreeObjectHelper& noh,
  2783 + std::string const& k) {
  2784 + auto i = noh.insert(k, QPDFObjectHandle::newUnicodeString(k));
  2785 + assert(i->first == k);
  2786 + };
  2787 + check_split2(split2, "C");
  2788 + for (auto const& i: split2)
  2789 + {
  2790 + std::cout << i.first << std::endl;
  2791 + }
  2792 +
  2793 + std::cout << "/Split3" << std::endl;
  2794 + auto split3 = QPDFNameTreeObjectHelper(
  2795 + pdf.getTrailer().getKey("/Split3"), pdf);
  2796 + split3.setSplitThreshold(4);
  2797 + check_split2(split3, "P");
  2798 + check_split2(split3, "\xcf\x80");
  2799 + for (auto& i: split3)
  2800 + {
  2801 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  2802 + }
  2803 +
  2804 + QPDFWriter w(pdf, "a.pdf");
  2805 + w.setStaticID(true);
  2806 + w.setQDFMode(true);
  2807 + w.write();
  2808 +}
2778 2809  
2779   - std::cout << "/Split3" << std::endl;
2780   - auto split3 = QPDFNameTreeObjectHelper(
2781   - pdf.getTrailer().getKey("/Split3"), pdf);
2782   - split3.setSplitThreshold(4);
2783   - check_split2(split3, "P");
2784   - check_split2(split3, "\xcf\x80");
2785   - for (auto& i: split3)
2786   - {
2787   - std::cout << i.first << " " << i.second.unparse() << std::endl;
2788   - }
  2810 +static void test_75(QPDF& pdf, char const* arg2)
  2811 +{
  2812 + // This test is crafted to work with erase-nntree.pdf
  2813 + auto erase1 = QPDFNameTreeObjectHelper(
  2814 + pdf.getTrailer().getKey("/Erase1"), pdf);
  2815 + QPDFObjectHandle value;
  2816 + assert(! erase1.remove("1X"));
  2817 + assert(erase1.remove("1C", &value));
  2818 + assert(value.getUTF8Value() == "c");
  2819 + auto iter1 = erase1.find("1B");
  2820 + iter1.remove();
  2821 + assert(iter1->first == "1D");
  2822 + iter1.remove();
  2823 + assert(iter1 == erase1.end());
  2824 + --iter1;
  2825 + assert(iter1->first == "1A");
  2826 + iter1.remove();
  2827 + assert(iter1 == erase1.end());
  2828 +
  2829 + auto erase2_oh = pdf.getTrailer().getKey("/Erase2");
  2830 + auto erase2 = QPDFNumberTreeObjectHelper(erase2_oh, pdf);
  2831 + auto iter2 = erase2.find(250);
  2832 + iter2.remove();
  2833 + assert(iter2 == erase2.end());
  2834 + --iter2;
  2835 + assert(iter2->first == 240);
  2836 + auto k1 = erase2_oh.getKey("/Kids").getArrayItem(1);
  2837 + auto l1 = k1.getKey("/Limits");
  2838 + assert(l1.getArrayItem(0).getIntValue() == 230);
  2839 + assert(l1.getArrayItem(1).getIntValue() == 240);
  2840 + iter2 = erase2.find(210);
  2841 + iter2.remove();
  2842 + assert(iter2->first == 220);
  2843 + k1 = erase2_oh.getKey("/Kids").getArrayItem(0);
  2844 + l1 = k1.getKey("/Limits");
  2845 + assert(l1.getArrayItem(0).getIntValue() == 220);
  2846 + assert(l1.getArrayItem(1).getIntValue() == 220);
  2847 + k1 = k1.getKey("/Kids");
  2848 + assert(k1.getArrayNItems() == 1);
  2849 +
  2850 + auto erase3 = QPDFNumberTreeObjectHelper(
  2851 + pdf.getTrailer().getKey("/Erase3"), pdf);
  2852 + iter2 = erase3.find(320);
  2853 + iter2.remove();
  2854 + assert(iter2 == erase3.end());
  2855 + erase3.remove(310);
  2856 + assert(erase3.begin() == erase3.end());
  2857 +
  2858 + auto erase4 = QPDFNumberTreeObjectHelper(
  2859 + pdf.getTrailer().getKey("/Erase4"), pdf);
  2860 + iter2 = erase4.find(420);
  2861 + iter2.remove();
  2862 + assert(iter2->first == 430);
  2863 +
  2864 + QPDFWriter w(pdf, "a.pdf");
  2865 + w.setStaticID(true);
  2866 + w.setQDFMode(true);
  2867 + w.write();
  2868 +}
2789 2869  
2790   - QPDFWriter w(pdf, "a.pdf");
2791   - w.setStaticID(true);
2792   - w.setQDFMode(true);
2793   - w.write();
2794   - }
2795   - else if (n == 75)
2796   - {
2797   - // This test is crafted to work with erase-nntree.pdf
2798   - auto erase1 = QPDFNameTreeObjectHelper(
2799   - pdf.getTrailer().getKey("/Erase1"), pdf);
2800   - QPDFObjectHandle value;
2801   - assert(! erase1.remove("1X"));
2802   - assert(erase1.remove("1C", &value));
2803   - assert(value.getUTF8Value() == "c");
2804   - auto iter1 = erase1.find("1B");
2805   - iter1.remove();
2806   - assert(iter1->first == "1D");
2807   - iter1.remove();
2808   - assert(iter1 == erase1.end());
2809   - --iter1;
2810   - assert(iter1->first == "1A");
2811   - iter1.remove();
2812   - assert(iter1 == erase1.end());
2813   -
2814   - auto erase2_oh = pdf.getTrailer().getKey("/Erase2");
2815   - auto erase2 = QPDFNumberTreeObjectHelper(erase2_oh, pdf);
2816   - auto iter2 = erase2.find(250);
2817   - iter2.remove();
2818   - assert(iter2 == erase2.end());
2819   - --iter2;
2820   - assert(iter2->first == 240);
2821   - auto k1 = erase2_oh.getKey("/Kids").getArrayItem(1);
2822   - auto l1 = k1.getKey("/Limits");
2823   - assert(l1.getArrayItem(0).getIntValue() == 230);
2824   - assert(l1.getArrayItem(1).getIntValue() == 240);
2825   - iter2 = erase2.find(210);
2826   - iter2.remove();
2827   - assert(iter2->first == 220);
2828   - k1 = erase2_oh.getKey("/Kids").getArrayItem(0);
2829   - l1 = k1.getKey("/Limits");
2830   - assert(l1.getArrayItem(0).getIntValue() == 220);
2831   - assert(l1.getArrayItem(1).getIntValue() == 220);
2832   - k1 = k1.getKey("/Kids");
2833   - assert(k1.getArrayNItems() == 1);
2834   -
2835   - auto erase3 = QPDFNumberTreeObjectHelper(
2836   - pdf.getTrailer().getKey("/Erase3"), pdf);
2837   - iter2 = erase3.find(320);
2838   - iter2.remove();
2839   - assert(iter2 == erase3.end());
2840   - erase3.remove(310);
2841   - assert(erase3.begin() == erase3.end());
2842   -
2843   - auto erase4 = QPDFNumberTreeObjectHelper(
2844   - pdf.getTrailer().getKey("/Erase4"), pdf);
2845   - iter2 = erase4.find(420);
2846   - iter2.remove();
2847   - assert(iter2->first == 430);
2848   -
2849   - QPDFWriter w(pdf, "a.pdf");
2850   - w.setStaticID(true);
2851   - w.setQDFMode(true);
2852   - w.write();
2853   - }
2854   - else if (n == 76)
2855   - {
2856   - // Embedded files. arg2 is a file to attach. Hard-code the
2857   - // mime type and file name for test purposes.
2858   - QPDFEmbeddedFileDocumentHelper efdh(pdf);
2859   - auto fs1 = QPDFFileSpecObjectHelper::createFileSpec(
2860   - pdf, "att1.txt", arg2);
2861   - fs1.setDescription("some text");
2862   - auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream());
2863   - efs1.setSubtype("text/plain")
2864   - .setCreationDate("D:20210207191121-05'00'")
2865   - .setModDate("D:20210208001122Z");
2866   - efdh.replaceEmbeddedFile("att1", fs1);
2867   - auto efs2 = QPDFEFStreamObjectHelper::createEFStream(
2868   - pdf, "from string");
2869   - efs2.setSubtype("text/plain");
2870   - Pl_Buffer p("buffer");
2871   - p.write(QUtil::unsigned_char_pointer("from buffer"), 11);
2872   - p.finish();
2873   - auto efs3 = QPDFEFStreamObjectHelper::createEFStream(
2874   - pdf, p.getBuffer());
2875   - efs3.setSubtype("text/plain");
2876   - efdh.replaceEmbeddedFile(
2877   - "att2", QPDFFileSpecObjectHelper::createFileSpec(
2878   - pdf, "att2.txt", efs2));
2879   - auto fs3 = QPDFFileSpecObjectHelper::createFileSpec(
2880   - pdf, "att3.txt", efs3);
2881   - efdh.replaceEmbeddedFile("att3", fs3);
2882   - fs3.setFilename("\xcf\x80.txt", "att3.txt");
2883   -
2884   - assert(efs1.getCreationDate() == "D:20210207191121-05'00'");
2885   - assert(efs1.getModDate() == "D:20210208001122Z");
2886   - assert(efs2.getSize() == 11);
2887   - assert(efs2.getSubtype() == "text/plain");
2888   - assert(QUtil::hex_encode(efs2.getChecksum()) ==
2889   - "2fce9c8228e360ba9b04a1bd1bf63d6b");
2890   -
2891   - for (auto iter: efdh.getEmbeddedFiles())
2892   - {
2893   - std::cout << iter.first << " -> " << iter.second->getFilename()
2894   - << std::endl;
2895   - }
2896   - assert(efdh.getEmbeddedFile("att1")->getFilename() == "att1.txt");
2897   - assert(! efdh.getEmbeddedFile("potato"));
  2870 +static void test_76(QPDF& pdf, char const* arg2)
  2871 +{
  2872 + // Embedded files. arg2 is a file to attach. Hard-code the
  2873 + // mime type and file name for test purposes.
  2874 + QPDFEmbeddedFileDocumentHelper efdh(pdf);
  2875 + auto fs1 = QPDFFileSpecObjectHelper::createFileSpec(
  2876 + pdf, "att1.txt", arg2);
  2877 + fs1.setDescription("some text");
  2878 + auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream());
  2879 + efs1.setSubtype("text/plain")
  2880 + .setCreationDate("D:20210207191121-05'00'")
  2881 + .setModDate("D:20210208001122Z");
  2882 + efdh.replaceEmbeddedFile("att1", fs1);
  2883 + auto efs2 = QPDFEFStreamObjectHelper::createEFStream(
  2884 + pdf, "from string");
  2885 + efs2.setSubtype("text/plain");
  2886 + Pl_Buffer p("buffer");
  2887 + p.write(QUtil::unsigned_char_pointer("from buffer"), 11);
  2888 + p.finish();
  2889 + auto efs3 = QPDFEFStreamObjectHelper::createEFStream(
  2890 + pdf, p.getBuffer());
  2891 + efs3.setSubtype("text/plain");
  2892 + efdh.replaceEmbeddedFile(
  2893 + "att2", QPDFFileSpecObjectHelper::createFileSpec(
  2894 + pdf, "att2.txt", efs2));
  2895 + auto fs3 = QPDFFileSpecObjectHelper::createFileSpec(
  2896 + pdf, "att3.txt", efs3);
  2897 + efdh.replaceEmbeddedFile("att3", fs3);
  2898 + fs3.setFilename("\xcf\x80.txt", "att3.txt");
  2899 +
  2900 + assert(efs1.getCreationDate() == "D:20210207191121-05'00'");
  2901 + assert(efs1.getModDate() == "D:20210208001122Z");
  2902 + assert(efs2.getSize() == 11);
  2903 + assert(efs2.getSubtype() == "text/plain");
  2904 + assert(QUtil::hex_encode(efs2.getChecksum()) ==
  2905 + "2fce9c8228e360ba9b04a1bd1bf63d6b");
  2906 +
  2907 + for (auto iter: efdh.getEmbeddedFiles())
  2908 + {
  2909 + std::cout << iter.first << " -> " << iter.second->getFilename()
  2910 + << std::endl;
  2911 + }
  2912 + assert(efdh.getEmbeddedFile("att1")->getFilename() == "att1.txt");
  2913 + assert(! efdh.getEmbeddedFile("potato"));
  2914 +
  2915 + QPDFWriter w(pdf, "a.pdf");
  2916 + w.setStaticID(true);
  2917 + w.setQDFMode(true);
  2918 + w.write();
  2919 +}
2898 2920  
2899   - QPDFWriter w(pdf, "a.pdf");
2900   - w.setStaticID(true);
2901   - w.setQDFMode(true);
2902   - w.write();
  2921 +static void test_77(QPDF& pdf, char const* arg2)
  2922 +{
  2923 + QPDFEmbeddedFileDocumentHelper efdh(pdf);
  2924 + assert(efdh.removeEmbeddedFile("att2"));
  2925 + assert(! efdh.removeEmbeddedFile("att2"));
  2926 +
  2927 + QPDFWriter w(pdf, "a.pdf");
  2928 + w.setStaticID(true);
  2929 + w.setQDFMode(true);
  2930 + w.write();
  2931 +}
  2932 +
  2933 +static void test_78(QPDF& pdf, char const* arg2)
  2934 +{
  2935 + // Test functional versions of replaceStreamData()
  2936 +
  2937 + auto f1 = [](Pipeline* p) {
  2938 + p->write(QUtil::unsigned_char_pointer("potato"), 6);
  2939 + p->finish();
  2940 + };
  2941 + auto f2 = [](Pipeline* p, bool suppress_warnings, bool will_retry) {
  2942 + std::cerr << "f2" << std::endl;
  2943 + if (will_retry)
  2944 + {
  2945 + std::cerr << "failing" << std::endl;
  2946 + return false;
  2947 + }
  2948 + if (! suppress_warnings)
  2949 + {
  2950 + std::cerr << "warning" << std::endl;
  2951 + }
  2952 + p->write(QUtil::unsigned_char_pointer("salad"), 5);
  2953 + p->finish();
  2954 + std::cerr << "f2 done" << std::endl;
  2955 + return true;
  2956 + };
  2957 +
  2958 + auto null = QPDFObjectHandle::newNull();
  2959 + auto s1 = QPDFObjectHandle::newStream(&pdf);
  2960 + s1.replaceStreamData(f1, null, null);
  2961 + auto s2 = QPDFObjectHandle::newStream(&pdf);
  2962 + s2.replaceStreamData(f2, null, null);
  2963 + pdf.getTrailer().replaceKey(
  2964 + "/Streams", QPDFObjectHandle::newArray({s1, s2}));
  2965 + std::cout << "piping with warning suppression" << std::endl;
  2966 + Pl_Discard d;
  2967 + s2.pipeStreamData(&d, nullptr, 0, qpdf_dl_all, true, false);
  2968 +
  2969 + std::cout << "writing" << std::endl;
  2970 + QPDFWriter w(pdf, "a.pdf");
  2971 + w.setStaticID(true);
  2972 + w.setQDFMode(true);
  2973 + w.write();
  2974 +}
  2975 +
  2976 +static void test_79(QPDF& pdf, char const* arg2)
  2977 +{
  2978 + // Exercise stream copier
  2979 +
  2980 + // Copy streams. Modify the original and make sure the copy is
  2981 + // unaffected.
  2982 + auto copies = QPDFObjectHandle::newArray();
  2983 + pdf.getTrailer().replaceKey("/Copies", copies);
  2984 + auto null = QPDFObjectHandle::newNull();
  2985 +
  2986 + // Get a regular stream from the file
  2987 + auto p1 = pdf.getAllPages().at(0);
  2988 + auto s1 = p1.getKey("/Contents");
  2989 +
  2990 + // Create a stream from a string
  2991 + auto s2 = QPDFObjectHandle::newStream(&pdf, "from string");
  2992 + // Add direct and indirect objects to the dictionary
  2993 + s2.getDict().replaceKey(
  2994 + "/Stuff",
  2995 + QPDFObjectHandle::parse(
  2996 + &pdf,
  2997 + "<< /Direct 3 /Indirect " +
  2998 + pdf.makeIndirectObject(
  2999 + QPDFObjectHandle::newInteger(16059)).unparse() + ">>"));
  3000 + s2.getDict().replaceKey(
  3001 + "/Other", QPDFObjectHandle::newString("other stuff"));
  3002 +
  3003 + // Use a provider
  3004 + Pl_Buffer b("buffer");
  3005 + b.write(QUtil::unsigned_char_pointer("from buffer"), 11);
  3006 + b.finish();
  3007 + PointerHolder<Buffer> bp = b.getBuffer();
  3008 + auto s3 = QPDFObjectHandle::newStream(&pdf, bp);
  3009 +
  3010 + std::vector<QPDFObjectHandle> streams = {s1, s2, s3};
  3011 + pdf.getTrailer().replaceKey(
  3012 + "/Originals", QPDFObjectHandle::newArray(streams));
  3013 +
  3014 + int i = 0;
  3015 + for (auto orig: streams)
  3016 + {
  3017 + ++i;
  3018 + auto istr = QUtil::int_to_string(i);
  3019 + auto orig_data = orig.getStreamData();
  3020 + auto copy = orig.copyStream();
  3021 + copy.getDict().replaceKey(
  3022 + "/Other", QPDFObjectHandle::newString("other: " + istr));
  3023 + orig.replaceStreamData("something new " + istr, null, null);
  3024 + auto copy_data = copy.getStreamData();
  3025 + assert(orig_data->getSize() == copy_data->getSize());
  3026 + assert(memcmp(orig_data->getBuffer(),
  3027 + copy_data->getBuffer(),
  3028 + orig_data->getSize()) == 0);
  3029 + copies.appendItem(copy);
  3030 + }
  3031 +
  3032 + QPDFWriter w(pdf, "a.pdf");
  3033 + w.setStaticID(true);
  3034 + w.setQDFMode(true);
  3035 + w.write();
  3036 +}
  3037 +
  3038 +static void test_80(QPDF& pdf, char const* arg2)
  3039 +{
  3040 + // Exercise transform/copy annotations without passing in
  3041 + // QPDFAcroFormDocumentHelper pointers. The case of passing
  3042 + // them in is sufficiently exercised by testing through the
  3043 + // qpdf CLI.
  3044 +
  3045 + // The main file is a file that has lots of annotations. Arg2
  3046 + // is a file to copy annotations to.
  3047 +
  3048 + QPDFMatrix m;
  3049 + m.translate(306, 396);
  3050 + m.scale(0.4, 0.4);
  3051 + auto page1 = pdf.getAllPages().at(0);
  3052 + auto old_annots = page1.getKey("/Annots");
  3053 + // Transform annotations and copy them back to the same page.
  3054 + std::vector<QPDFObjectHandle> new_annots;
  3055 + std::vector<QPDFObjectHandle> new_fields;
  3056 + std::set<QPDFObjGen> old_fields;
  3057 + QPDFAcroFormDocumentHelper afdh(pdf);
  3058 + // Use defaults for from_qpdf and from_afdh.
  3059 + afdh.transformAnnotations(
  3060 + old_annots, new_annots, new_fields, old_fields, m);
  3061 + for (auto const& annot: new_annots)
  3062 + {
  3063 + old_annots.appendItem(annot);
  3064 + }
  3065 + afdh.addAndRenameFormFields(new_fields);
  3066 +
  3067 + m = QPDFMatrix();
  3068 + m.translate(612, 0);
  3069 + m.scale(-1, 1);
  3070 + QPDF pdf2;
  3071 + pdf2.processFile(arg2);
  3072 + auto page2 = QPDFPageDocumentHelper(pdf2).getAllPages().at(0);
  3073 + page2.copyAnnotations(page1, m);
  3074 +
  3075 + QPDFWriter w1(pdf, "a.pdf");
  3076 + w1.setStaticID(true);
  3077 + w1.setQDFMode(true);
  3078 + w1.write();
  3079 +
  3080 + QPDFWriter w2(pdf2, "b.pdf");
  3081 + w2.setStaticID(true);
  3082 + w2.setQDFMode(true);
  3083 + w2.write();
  3084 +}
  3085 +
  3086 +static void test_81(QPDF& pdf, char const* arg2)
  3087 +{
  3088 + // Exercise that type errors get their own special type
  3089 + try
  3090 + {
  3091 + QPDFObjectHandle::newNull().getIntValue();
  3092 + assert(false);
2903 3093 }
2904   - else if (n == 77)
  3094 + catch (QPDFExc& e)
2905 3095 {
2906   - QPDFEmbeddedFileDocumentHelper efdh(pdf);
2907   - assert(efdh.removeEmbeddedFile("att2"));
2908   - assert(! efdh.removeEmbeddedFile("att2"));
2909   -
2910   - QPDFWriter w(pdf, "a.pdf");
2911   - w.setStaticID(true);
2912   - w.setQDFMode(true);
2913   - w.write();
  3096 + assert(e.getErrorCode() == qpdf_e_object);
2914 3097 }
2915   - else if (n == 78)
  3098 +}
  3099 +
  3100 +void runtest(int n, char const* filename1, char const* arg2)
  3101 +{
  3102 + // Most tests here are crafted to work on specific files. Look at
  3103 + // the test suite to see how the test is invoked to find the file
  3104 + // that the test is supposed to operate on.
  3105 +
  3106 + if (n == 0)
2916 3107 {
2917   - // Test functional versions of replaceStreamData()
  3108 + // Throw in some random test cases that don't fit anywhere
  3109 + // else. This is in addition to whatever else is going on in
  3110 + // test 0.
2918 3111  
2919   - auto f1 = [](Pipeline* p) {
2920   - p->write(QUtil::unsigned_char_pointer("potato"), 6);
2921   - p->finish();
2922   - };
2923   - auto f2 = [](Pipeline* p, bool suppress_warnings, bool will_retry) {
2924   - std::cerr << "f2" << std::endl;
2925   - if (will_retry)
2926   - {
2927   - std::cerr << "failing" << std::endl;
2928   - return false;
2929   - }
2930   - if (! suppress_warnings)
2931   - {
2932   - std::cerr << "warning" << std::endl;
2933   - }
2934   - p->write(QUtil::unsigned_char_pointer("salad"), 5);
2935   - p->finish();
2936   - std::cerr << "f2 done" << std::endl;
2937   - return true;
2938   - };
2939   -
2940   - auto null = QPDFObjectHandle::newNull();
2941   - auto s1 = QPDFObjectHandle::newStream(&pdf);
2942   - s1.replaceStreamData(f1, null, null);
2943   - auto s2 = QPDFObjectHandle::newStream(&pdf);
2944   - s2.replaceStreamData(f2, null, null);
2945   - pdf.getTrailer().replaceKey(
2946   - "/Streams", QPDFObjectHandle::newArray({s1, s2}));
2947   - std::cout << "piping with warning suppression" << std::endl;
2948   - Pl_Discard d;
2949   - s2.pipeStreamData(&d, nullptr, 0, qpdf_dl_all, true, false);
  3112 + // The code to trim user passwords looks for 0x28 (which is
  3113 + // "(") since it marks the beginning of the padding. Exercise
  3114 + // the code to make sure it skips over 0x28 characters that
  3115 + // aren't part of padding.
  3116 + std::string password(
  3117 + "1234567890123456789012(45678\x28\xbf\x4e\x5e");
  3118 + assert(password.length() == 32);
  3119 + QPDF::trim_user_password(password);
  3120 + assert(password == "1234567890123456789012(45678");
2950 3121  
2951   - std::cout << "writing" << std::endl;
2952   - QPDFWriter w(pdf, "a.pdf");
2953   - w.setStaticID(true);
2954   - w.setQDFMode(true);
2955   - w.write();
  3122 + QPDFObjectHandle uninitialized;
  3123 + assert(uninitialized.getTypeCode() == QPDFObject::ot_uninitialized);
  3124 + assert(strcmp(uninitialized.getTypeName(), "uninitialized") == 0);
2956 3125 }
2957   - else if (n == 79)
2958   - {
2959   - // Exercise stream copier
2960   -
2961   - // Copy streams. Modify the original and make sure the copy is
2962   - // unaffected.
2963   - auto copies = QPDFObjectHandle::newArray();
2964   - pdf.getTrailer().replaceKey("/Copies", copies);
2965   - auto null = QPDFObjectHandle::newNull();
2966   -
2967   - // Get a regular stream from the file
2968   - auto p1 = pdf.getAllPages().at(0);
2969   - auto s1 = p1.getKey("/Contents");
2970   -
2971   - // Create a stream from a string
2972   - auto s2 = QPDFObjectHandle::newStream(&pdf, "from string");
2973   - // Add direct and indirect objects to the dictionary
2974   - s2.getDict().replaceKey(
2975   - "/Stuff",
2976   - QPDFObjectHandle::parse(
2977   - &pdf,
2978   - "<< /Direct 3 /Indirect " +
2979   - pdf.makeIndirectObject(
2980   - QPDFObjectHandle::newInteger(16059)).unparse() + ">>"));
2981   - s2.getDict().replaceKey(
2982   - "/Other", QPDFObjectHandle::newString("other stuff"));
2983   -
2984   - // Use a provider
2985   - Pl_Buffer b("buffer");
2986   - b.write(QUtil::unsigned_char_pointer("from buffer"), 11);
2987   - b.finish();
2988   - PointerHolder<Buffer> bp = b.getBuffer();
2989   - auto s3 = QPDFObjectHandle::newStream(&pdf, bp);
2990 3126  
2991   - std::vector<QPDFObjectHandle> streams = {s1, s2, s3};
2992   - pdf.getTrailer().replaceKey(
2993   - "/Originals", QPDFObjectHandle::newArray(streams));
  3127 + QPDF pdf;
  3128 + PointerHolder<char> file_buf;
  3129 + FILE* filep = 0;
  3130 + if (n == 0)
  3131 + {
  3132 + pdf.setAttemptRecovery(false);
  3133 + }
  3134 + if (((n == 35) || (n == 36)) && (arg2 != 0))
  3135 + {
  3136 + // arg2 is password
  3137 + pdf.processFile(filename1, arg2);
  3138 + }
  3139 + else if (n == 45)
  3140 + {
  3141 + // Decode obfuscated files. To obfuscated, run the input file
  3142 + // through this perl script, and save the result to
  3143 + // filename.obfuscated. This pretends that the input was
  3144 + // called filename.pdf and that that file contained the
  3145 + // deobfuscated version.
2994 3146  
2995   - int i = 0;
2996   - for (auto orig: streams)
2997   - {
2998   - ++i;
2999   - auto istr = QUtil::int_to_string(i);
3000   - auto orig_data = orig.getStreamData();
3001   - auto copy = orig.copyStream();
3002   - copy.getDict().replaceKey(
3003   - "/Other", QPDFObjectHandle::newString("other: " + istr));
3004   - orig.replaceStreamData("something new " + istr, null, null);
3005   - auto copy_data = copy.getStreamData();
3006   - assert(orig_data->getSize() == copy_data->getSize());
3007   - assert(memcmp(orig_data->getBuffer(),
3008   - copy_data->getBuffer(),
3009   - orig_data->getSize()) == 0);
3010   - copies.appendItem(copy);
3011   - }
  3147 + // undef $/;
  3148 + // my @str = split('', <STDIN>);
  3149 + // for (my $i = 0; $i < scalar(@str); ++$i)
  3150 + // {
  3151 + // $str[$i] = chr(ord($str[$i]) ^ 0xcc);
  3152 + // }
  3153 + // print(join('', @str));
3012 3154  
3013   - QPDFWriter w(pdf, "a.pdf");
3014   - w.setStaticID(true);
3015   - w.setQDFMode(true);
3016   - w.write();
3017   - }
3018   - else if (n == 80)
3019   - {
3020   - // Exercise transform/copy annotations without passing in
3021   - // QPDFAcroFormDocumentHelper pointers. The case of passing
3022   - // them in is sufficiently exercised by testing through the
3023   - // qpdf CLI.
3024   -
3025   - // The main file is a file that has lots of annotations. Arg2
3026   - // is a file to copy annotations to.
3027   -
3028   - QPDFMatrix m;
3029   - m.translate(306, 396);
3030   - m.scale(0.4, 0.4);
3031   - auto page1 = pdf.getAllPages().at(0);
3032   - auto old_annots = page1.getKey("/Annots");
3033   - // Transform annotations and copy them back to the same page.
3034   - std::vector<QPDFObjectHandle> new_annots;
3035   - std::vector<QPDFObjectHandle> new_fields;
3036   - std::set<QPDFObjGen> old_fields;
3037   - QPDFAcroFormDocumentHelper afdh(pdf);
3038   - // Use defaults for from_qpdf and from_afdh.
3039   - afdh.transformAnnotations(
3040   - old_annots, new_annots, new_fields, old_fields, m);
3041   - for (auto const& annot: new_annots)
  3155 + std::string filename(std::string(filename1) + ".obfuscated");
  3156 + size_t size = 0;
  3157 + QUtil::read_file_into_memory(filename.c_str(), file_buf, size);
  3158 + char* p = file_buf.getPointer();
  3159 + for (size_t i = 0; i < size; ++i)
3042 3160 {
3043   - old_annots.appendItem(annot);
  3161 + p[i] = static_cast<char>(p[i] ^ 0xcc);
3044 3162 }
3045   - afdh.addAndRenameFormFields(new_fields);
3046   -
3047   - m = QPDFMatrix();
3048   - m.translate(612, 0);
3049   - m.scale(-1, 1);
3050   - QPDF pdf2;
3051   - pdf2.processFile(arg2);
3052   - auto page2 = QPDFPageDocumentHelper(pdf2).getAllPages().at(0);
3053   - page2.copyAnnotations(page1, m);
3054   -
3055   - QPDFWriter w1(pdf, "a.pdf");
3056   - w1.setStaticID(true);
3057   - w1.setQDFMode(true);
3058   - w1.write();
3059   -
3060   - QPDFWriter w2(pdf2, "b.pdf");
3061   - w2.setStaticID(true);
3062   - w2.setQDFMode(true);
3063   - w2.write();
  3163 + pdf.processMemoryFile((std::string(filename1) + ".pdf").c_str(),
  3164 + p, size);
3064 3165 }
3065   - else if (n == 81)
  3166 + else if ((n == 61) || (n == 81))
  3167 + {
  3168 + // Ignore filename argument entirely
  3169 + }
  3170 + else if (n % 2 == 0)
3066 3171 {
3067   - // Exercise that type errors get their own special type
3068   - try
  3172 + if (n % 4 == 0)
3069 3173 {
3070   - QPDFObjectHandle::newNull().getIntValue();
3071   - assert(false);
  3174 + QTC::TC("qpdf", "exercise processFile(name)");
  3175 + pdf.processFile(filename1);
3072 3176 }
3073   - catch (QPDFExc& e)
  3177 + else
3074 3178 {
3075   - assert(e.getErrorCode() == qpdf_e_object);
  3179 + QTC::TC("qpdf", "exercise processFile(FILE*)");
  3180 + filep = QUtil::safe_fopen(filename1, "rb");
  3181 + pdf.processFile(filename1, filep, false);
3076 3182 }
3077 3183 }
3078 3184 else
3079 3185 {
  3186 + QTC::TC("qpdf", "exercise processMemoryFile");
  3187 + size_t size = 0;
  3188 + QUtil::read_file_into_memory(filename1, file_buf, size);
  3189 + pdf.processMemoryFile(filename1, file_buf.getPointer(), size);
  3190 + }
  3191 +
  3192 + std::map<int, void (*)(QPDF&, char const*)> test_functions = {
  3193 + {0, test_0_1}, {1, test_0_1}, {2, test_2}, {3, test_3},
  3194 + {4, test_4}, {5, test_5}, {6, test_6}, {7, test_7},
  3195 + {8, test_8}, {9, test_9}, {10, test_10}, {11, test_11},
  3196 + {12, test_12}, {13, test_13}, {14, test_14}, {15, test_15},
  3197 + {16, test_16}, {17, test_17}, {18, test_18}, {19, test_19},
  3198 + {20, test_20}, {21, test_21}, {22, test_22}, {23, test_23},
  3199 + {24, test_24}, {25, test_25}, {26, test_26}, {27, test_27},
  3200 + {28, test_28}, {29, test_29}, {30, test_30}, {31, test_31},
  3201 + {32, test_32}, {33, test_33}, {34, test_34}, {35, test_35},
  3202 + {36, test_36}, {37, test_37}, {38, test_38}, {39, test_39},
  3203 + {40, test_40}, {41, test_41}, {42, test_42}, {43, test_43},
  3204 + {44, test_44}, {45, test_45}, {46, test_46}, {47, test_47},
  3205 + {48, test_48}, {49, test_49}, {50, test_50}, {51, test_51},
  3206 + {52, test_52}, {53, test_53}, {54, test_54}, {55, test_55},
  3207 + {56, test_56}, {57, test_57}, {58, test_58}, {59, test_59},
  3208 + {60, test_60}, {61, test_61}, {62, test_62}, {63, test_63},
  3209 + {64, test_64}, {65, test_65}, {66, test_66}, {67, test_67},
  3210 + {68, test_68}, {69, test_69}, {70, test_70}, {71, test_71},
  3211 + {72, test_72}, {73, test_73}, {74, test_74}, {75, test_75},
  3212 + {76, test_76}, {77, test_77}, {78, test_78}, {79, test_79},
  3213 + {80, test_80}, {81, test_81},
  3214 + };
  3215 +
  3216 + auto fn = test_functions.find(n);
  3217 + if (fn == test_functions.end())
  3218 + {
3080 3219 throw std::runtime_error(std::string("invalid test ") +
3081 3220 QUtil::int_to_string(n));
3082 3221 }
  3222 + (fn->second)(pdf, arg2);
3083 3223  
3084 3224 if (filep)
3085 3225 {
... ...