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,8 +3,6 @@ Next
3 3
4 * High-level API/doc overhaul: #593 4 * High-level API/doc overhaul: #593
5 5
6 -* Refactor test_driver.cc so that runtest is not one huge function.  
7 -  
8 Documentation 6 Documentation
9 ============= 7 =============
10 8
qpdf/qtest/qpdf/form-minimal.out
1 no forms 1 no forms
  2 +test 43 done
qpdf/test_driver.cc
@@ -193,1697 +193,1649 @@ static void compare_numbers( @@ -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 else 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 else 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 else 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 // Local scope 1124 // Local scope
1164 Pl_Buffer pl("buffer"); 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 pl.finish(); 1129 pl.finish();
1168 PointerHolder<Buffer> b = pl.getBuffer(); 1130 PointerHolder<Buffer> b = pl.getBuffer();
1169 Provider* provider = new Provider(b); 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 << std::endl; 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 << std::endl; 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 << std::endl; 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 #ifdef _MSC_VER 1839 #ifdef _MSC_VER
1888 # pragma warning (disable: 4996) 1840 # pragma warning (disable: 4996)
1889 #endif 1841 #endif
@@ -1891,211 +1843,213 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -1891,211 +1843,213 @@ void runtest(int n, char const* filename1, char const* arg2)
1891 # pragma GCC diagnostic push 1843 # pragma GCC diagnostic push
1892 # pragma GCC diagnostic ignored "-Wdeprecated-declarations" 1844 # pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1893 #endif 1845 #endif
1894 - auto bad1 = QPDFNumberTreeObjectHelper(  
1895 - pdf.getTrailer().getKey("/Bad1")); 1846 + auto bad1 = QPDFNumberTreeObjectHelper(
  1847 + pdf.getTrailer().getKey("/Bad1"));
1896 #if (defined(__GNUC__) || defined(__clang__)) 1848 #if (defined(__GNUC__) || defined(__clang__))
1897 # pragma GCC diagnostic pop 1849 # pragma GCC diagnostic pop
1898 #endif 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 #ifdef _MSC_VER 2053 #ifdef _MSC_VER
2100 # pragma warning (disable: 4996) 2054 # pragma warning (disable: 4996)
2101 #endif 2055 #endif
@@ -2103,983 +2057,1169 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -2103,983 +2057,1169 @@ void runtest(int n, char const* filename1, char const* arg2)
2103 # pragma GCC diagnostic push 2057 # pragma GCC diagnostic push
2104 # pragma GCC diagnostic ignored "-Wdeprecated-declarations" 2058 # pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2105 #endif 2059 #endif
2106 - auto bad1 = QPDFNameTreeObjectHelper(  
2107 - pdf.getTrailer().getKey("/Bad1")); 2060 + auto bad1 = QPDFNameTreeObjectHelper(
  2061 + pdf.getTrailer().getKey("/Bad1"));
2108 #if (defined(__GNUC__) || defined(__clang__)) 2062 #if (defined(__GNUC__) || defined(__clang__))
2109 # pragma GCC diagnostic pop 2063 # pragma GCC diagnostic pop
2110 #endif 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 w.setStaticID(true); 2639 w.setStaticID(true);
2641 - w.setDecodeLevel(qpdf_dl_specialized);  
2642 w.write(); 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 else 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 throw std::runtime_error(std::string("invalid test ") + 3219 throw std::runtime_error(std::string("invalid test ") +
3081 QUtil::int_to_string(n)); 3220 QUtil::int_to_string(n));
3082 } 3221 }
  3222 + (fn->second)(pdf, arg2);
3083 3223
3084 if (filep) 3224 if (filep)
3085 { 3225 {