Commit 5a02471bb15299dec00c73f6a08a2b4e73061775

Authored by Jay Berkenbilt
1 parent a50cfefd

Command-line page merging and splitting

Implement --pages ... -- option for qpdf.  Update TODO with remaining
things to document.
ChangeLog
1 2012-07-21 Jay Berkenbilt <ejb@ql.org> 1 2012-07-21 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Add --pages command-line option to qpdf to enable page-based
  4 + merging and splitting.
  5 +
3 * Add new method QPDFObjectHandle::replaceDict to replace a 6 * Add new method QPDFObjectHandle::replaceDict to replace a
4 stream's dictionary. Use with caution; see comments in 7 stream's dictionary. Use with caution; see comments in
5 QPDFObjectHandle.hh. 8 QPDFObjectHandle.hh.
@@ -24,46 +24,40 @@ Next @@ -24,46 +24,40 @@ Next
24 can only be used by one thread at a time, but multiple threads can 24 can only be used by one thread at a time, but multiple threads can
25 simultaneously use separate objects. 25 simultaneously use separate objects.
26 26
27 - * Write some documentation about the design of copyForeignObject.  
28 -  
29 * Mention QPDFObjectHandle::parse in the documentation. 27 * Mention QPDFObjectHandle::parse in the documentation.
30 28
31 - * copyForeignObject still to do:  
32 -  
33 - - qpdf command  
34 -  
35 - Command line could be something like (but not exactly) 29 + * Manual: empty --empty as an input file name option
36 30
37 - --pages [ --new ] { file [password] numeric-range ... } ... -- 31 + * copyForeignObject, merge/split documentation:
38 32
39 - The first file referenced would be the one whose other data would  
40 - be preserved (like trailer, info, encryption, outlines, etc.).  
41 - --new as first file would just use an empty file as the starting  
42 - point. Be explicit about whether outlines, etc., are handled.  
43 - They are not handled initially. 33 + document details of --pages option in manual. Include nuances of
  34 + range parsing, such as backward ranges and "z". Discuss
  35 + implications of using --empty vs. using one of the source files as
  36 + the original file including Outlines (which basically work) and
  37 + page labels (which don't). Also mention trick of specifying two
  38 + different paths to the same file get duplication.
44 39
45 - Example: to grab pages 1-5 from file1 and 11-15 from file2 40 + Command line is
46 41
47 - --pages file1.pdf 1-5 file2.pdf 11-15 -- 42 + --pages infile [ --password=pwd ] range ... --
48 43
49 - To implement this, we would remove all pages from file1 except  
50 - pages 1 through 5. Then we would take pages 11 through 15 from  
51 - file2, copy them to the file, and add them as pages. 44 + The regular input referenced would be the one whose other data
  45 + would be preserved (like trailer, info, encryption, outlines,
  46 + etc.). It can be but doesn't have to be one of the files selected.
52 47
53 - (Look at ~/source/examples/perl/numrange.pl for numeric range  
54 - parsing code.) 48 + Example: to grab pages 1-5 from file1 and 11-15 from file2 in
  49 + reverse:
55 50
56 - - document that makeIndirectObject doesn't handle foreign objects  
57 - automatically because copying a foreign object is a big enough  
58 - deal that it should be explicit. However addPages* does handle  
59 - foreign page objects automatically. 51 + qpdf file1.pdf out.pdf --pages file1.pdf 1-5 file2.pdf 15-11 --
60 52
61 - - Test /Outlines and see whether there's any point in handling  
62 - them in the API. Maybe just copying them over works. What  
63 - about command line tool? Also think about page labels. 53 + Use comments in qpdf.cc to guide internals documentation when
  54 + discussing implementation. Also see copyForeignObject as a source
  55 + for documentation.
64 56
65 - - Tests through qpdf command line: copy pages from multiple PDFs  
66 - starting with one PDF and also starting with empty. 57 + Document that makeIndirectObject doesn't handle foreign objects
  58 + automatically because copying a foreign object is a big enough deal
  59 + that it should be explicit. However addPages* does handle foreign
  60 + page objects automatically.
67 61
68 * Document --copy-encryption and --encryption-file-password in 62 * Document --copy-encryption and --encryption-file-password in
69 manual. Mention that the first half of /ID as well as all the 63 manual. Mention that the first half of /ID as well as all the
qpdf/qpdf.cc
@@ -3,10 +3,12 @@ @@ -3,10 +3,12 @@
3 #include <stdlib.h> 3 #include <stdlib.h>
4 #include <fcntl.h> 4 #include <fcntl.h>
5 #include <stdio.h> 5 #include <stdio.h>
  6 +#include <ctype.h>
6 7
7 #include <qpdf/QUtil.hh> 8 #include <qpdf/QUtil.hh>
8 #include <qpdf/QTC.hh> 9 #include <qpdf/QTC.hh>
9 #include <qpdf/Pl_StdioFile.hh> 10 #include <qpdf/Pl_StdioFile.hh>
  11 +#include <qpdf/PointerHolder.hh>
10 12
11 #include <qpdf/QPDF.hh> 13 #include <qpdf/QPDF.hh>
12 #include <qpdf/QPDFExc.hh> 14 #include <qpdf/QPDFExc.hh>
@@ -18,6 +20,31 @@ static int const EXIT_WARNING = 3; @@ -18,6 +20,31 @@ static int const EXIT_WARNING = 3;
18 20
19 static char const* whoami = 0; 21 static char const* whoami = 0;
20 22
  23 +struct PageSpec
  24 +{
  25 + PageSpec(std::string const& filename,
  26 + char const* password,
  27 + char const* range) :
  28 + filename(filename),
  29 + password(password),
  30 + range(range)
  31 + {
  32 + }
  33 +
  34 + std::string filename;
  35 + char const* password;
  36 + char const* range;
  37 +};
  38 +
  39 +struct QPDFPageData
  40 +{
  41 + QPDFPageData(QPDF* qpdf, char const* range);
  42 +
  43 + QPDF* qpdf;
  44 + std::vector<QPDFObjectHandle> orig_pages;
  45 + std::vector<int> selected_pages;
  46 +};
  47 +
21 // Note: let's not be too noisy about documenting the fact that this 48 // Note: let's not be too noisy about documenting the fact that this
22 // software purposely fails to enforce the distinction between user 49 // software purposely fails to enforce the distinction between user
23 // and owner passwords. A user password is sufficient to gain full 50 // and owner passwords. A user password is sufficient to gain full
@@ -29,7 +56,7 @@ static char const* whoami = 0; @@ -29,7 +56,7 @@ static char const* whoami = 0;
29 56
30 static char const* help = "\ 57 static char const* help = "\
31 \n\ 58 \n\
32 -Usage: qpdf [ options ] infilename [ outfilename ]\n\ 59 +Usage: qpdf [ options ] { infilename | --empty } [ outfilename ]\n\
33 \n\ 60 \n\
34 An option summary appears below. Please see the documentation for details.\n\ 61 An option summary appears below. Please see the documentation for details.\n\
35 \n\ 62 \n\
@@ -57,6 +84,7 @@ parameters will be copied, including both user and owner passwords, even\n\ @@ -57,6 +84,7 @@ parameters will be copied, including both user and owner passwords, even\n\
57 if the user password is used to open the other file. This works even if\n\ 84 if the user password is used to open the other file. This works even if\n\
58 the owner password is not known.\n\ 85 the owner password is not known.\n\
59 \n\ 86 \n\
  87 +\n\
60 Encryption Options\n\ 88 Encryption Options\n\
61 ------------------\n\ 89 ------------------\n\
62 \n\ 90 \n\
@@ -113,6 +141,40 @@ to be used even if not otherwise needed. This option is primarily useful\n\ @@ -113,6 +141,40 @@ to be used even if not otherwise needed. This option is primarily useful\n\
113 for testing qpdf and has no other practical use.\n\ 141 for testing qpdf and has no other practical use.\n\
114 \n\ 142 \n\
115 \n\ 143 \n\
  144 +Page Selection Options\n\
  145 +----------------------\n\
  146 +\n\
  147 +These options allow pages to be selected from one or more PDF files.\n\
  148 +Whatever file is given as the primary input file is used as the\n\
  149 +starting point, but its pages are replaced with pages as specified.\n\
  150 +\n\
  151 +--pages file [ --password=password ] page-range ... --\n\
  152 +\n\
  153 +For each file that pages should be taken from, specify the file, a\n\
  154 +password needed to open the file (if needed), and a page range. The\n\
  155 +password needs to be given only once per file. If the input file file\n\
  156 +requires a password, that password must be specified outside the\n\
  157 +--pages option and does not need to be repeated. The same file can be\n\
  158 +repeated multiple times. All non-page data (info, outlines, page numbers,\n\
  159 +etc. are taken from the primary input file. To discard this, use --empty\n\
  160 +as the primary input.\n\
  161 +\n\
  162 +It is not presently possible to specify the same page from the same\n\
  163 +file directly more than once, but you can make this work by specifying\n\
  164 +two different paths to the same file (such as by putting ./ somewhere\n\
  165 +in the path).\n\
  166 +\n\
  167 +The page range is a set of numbers separated by commas, ranges of\n\
  168 +numbers separated dashes, or combinations of those. The character\n\
  169 +\"z\" represents the last page. Pages can appear in any order. Ranges\n\
  170 +can appear with a high number followed by a low number, which causes the\n\
  171 +pages to appear in reverse. Repeating a number will cause an error, but\n\
  172 +the manual discusses a workaround should you really want to include the\n\
  173 +same page twice.\n\
  174 +\n\
  175 +See the manual for examples and a discussion of additional subtleties.\n\
  176 +\n\
  177 +\n\
116 Advanced Transformation Options\n\ 178 Advanced Transformation Options\n\
117 -------------------------------\n\ 179 -------------------------------\n\
118 \n\ 180 \n\
@@ -275,6 +337,146 @@ static void show_encryption(QPDF&amp; pdf) @@ -275,6 +337,146 @@ static void show_encryption(QPDF&amp; pdf)
275 } 337 }
276 } 338 }
277 339
  340 +static std::vector<int> parse_numrange(char const* range, int max)
  341 +{
  342 + std::vector<int> result;
  343 + char const* p = range;
  344 + try
  345 + {
  346 + std::vector<int> work;
  347 + static int const comma = -1;
  348 + static int const dash = -2;
  349 +
  350 + enum { st_top,
  351 + st_in_number,
  352 + st_after_number } state = st_top;
  353 + bool last_separator_was_dash = false;
  354 + int cur_number = 0;
  355 + while (*p)
  356 + {
  357 + char ch = *p;
  358 + if (isdigit(ch))
  359 + {
  360 + if (! ((state == st_top) || (state == st_in_number)))
  361 + {
  362 + throw std::runtime_error("digit not expected");
  363 + }
  364 + state = st_in_number;
  365 + cur_number *= 10;
  366 + cur_number += (ch - '0');
  367 + }
  368 + else if (ch == 'z')
  369 + {
  370 + // z represents max
  371 + if (! (state == st_top))
  372 + {
  373 + throw std::runtime_error("z not expected");
  374 + }
  375 + state = st_after_number;
  376 + cur_number = max;
  377 + }
  378 + else if ((ch == ',') || (ch == '-'))
  379 + {
  380 + if (! ((state == st_in_number) || (state == st_after_number)))
  381 + {
  382 + throw std::runtime_error("unexpected separator");
  383 + }
  384 + work.push_back(cur_number);
  385 + cur_number = 0;
  386 + if (ch == ',')
  387 + {
  388 + state = st_top;
  389 + last_separator_was_dash = false;
  390 + work.push_back(comma);
  391 + }
  392 + else if (ch == '-')
  393 + {
  394 + if (last_separator_was_dash)
  395 + {
  396 + throw std::runtime_error("unexpected dash");
  397 + }
  398 + state = st_top;
  399 + last_separator_was_dash = true;
  400 + work.push_back(dash);
  401 + }
  402 + }
  403 + else
  404 + {
  405 + throw std::runtime_error("unexpected character");
  406 + }
  407 + ++p;
  408 + }
  409 + if ((state == st_in_number) || (state == st_after_number))
  410 + {
  411 + work.push_back(cur_number);
  412 + }
  413 + else
  414 + {
  415 + throw std::runtime_error("number expected");
  416 + }
  417 +
  418 + p = 0;
  419 + for (size_t i = 0; i < work.size(); i += 2)
  420 + {
  421 + int num = work[i];
  422 + if ((num < 1) || (num > max))
  423 + {
  424 + throw std::runtime_error(
  425 + "number " + QUtil::int_to_string(num) + " out of range");
  426 + }
  427 + if (i == 0)
  428 + {
  429 + result.push_back(work[i]);
  430 + }
  431 + else
  432 + {
  433 + int separator = work[i-1];
  434 + if (separator == comma)
  435 + {
  436 + result.push_back(num);
  437 + }
  438 + else if (separator == dash)
  439 + {
  440 + int lastnum = result.back();
  441 + if (num > lastnum)
  442 + {
  443 + for (int j = lastnum + 1; j <= num; ++j)
  444 + {
  445 + result.push_back(j);
  446 + }
  447 + }
  448 + else
  449 + {
  450 + for (int j = lastnum - 1; j >= num; --j)
  451 + {
  452 + result.push_back(j);
  453 + }
  454 + }
  455 + }
  456 + else
  457 + {
  458 + throw std::logic_error(
  459 + "INTERNAL ERROR parsing numeric range");
  460 + }
  461 + }
  462 + }
  463 + }
  464 + catch (std::runtime_error e)
  465 + {
  466 + if (p)
  467 + {
  468 + usage("error at * in numeric range " +
  469 + std::string(range, p - range) + "*" + p + ": " + e.what());
  470 + }
  471 + else
  472 + {
  473 + usage("error in numeric range " +
  474 + std::string(range) + ": " + e.what());
  475 + }
  476 + }
  477 + return result;
  478 +}
  479 +
278 static void 480 static void
279 parse_encrypt_options( 481 parse_encrypt_options(
280 int argc, char* argv[], int& cur_arg, 482 int argc, char* argv[], int& cur_arg,
@@ -578,6 +780,66 @@ parse_encrypt_options( @@ -578,6 +780,66 @@ parse_encrypt_options(
578 } 780 }
579 } 781 }
580 782
  783 +static std::vector<PageSpec>
  784 +parse_pages_options(
  785 + int argc, char* argv[], int& cur_arg)
  786 +{
  787 + std::vector<PageSpec> result;
  788 + while (1)
  789 + {
  790 + if ((cur_arg < argc) && (strcmp(argv[cur_arg], "--") == 0))
  791 + {
  792 + break;
  793 + }
  794 + if (cur_arg + 2 >= argc)
  795 + {
  796 + usage("insufficient arguments to --pages");
  797 + }
  798 + char* file = argv[cur_arg++];
  799 + char* password = 0;
  800 + char* range = argv[cur_arg++];
  801 + if (strncmp(range, "--password=", 11) == 0)
  802 + {
  803 + // Oh, that's the password, not the range
  804 + if (cur_arg + 1 >= argc)
  805 + {
  806 + usage("insufficient arguments to --pages");
  807 + }
  808 + password = range + 11;
  809 + range = argv[cur_arg++];
  810 + }
  811 +
  812 + result.push_back(PageSpec(file, password, range));
  813 + }
  814 + return result;
  815 +}
  816 +
  817 +static void test_numrange(char const* range)
  818 +{
  819 + if (range == 0)
  820 + {
  821 + std::cout << "null" << std::endl;
  822 + }
  823 + else
  824 + {
  825 + std::vector<int> result = parse_numrange(range, 15);
  826 + std::cout << "numeric range " << range << " ->";
  827 + for (std::vector<int>::iterator iter = result.begin();
  828 + iter != result.end(); ++iter)
  829 + {
  830 + std::cout << " " << *iter;
  831 + }
  832 + std::cout << std::endl;
  833 + }
  834 +}
  835 +
  836 +QPDFPageData::QPDFPageData(QPDF* qpdf, char const* range) :
  837 + qpdf(qpdf),
  838 + orig_pages(qpdf->getAllPages())
  839 +{
  840 + this->selected_pages = parse_numrange(range, this->orig_pages.size());
  841 +}
  842 +
581 int main(int argc, char* argv[]) 843 int main(int argc, char* argv[])
582 { 844 {
583 whoami = QUtil::getWhoami(argv[0]); 845 whoami = QUtil::getWhoami(argv[0]);
@@ -673,6 +935,8 @@ int main(int argc, char* argv[]) @@ -673,6 +935,8 @@ int main(int argc, char* argv[])
673 bool show_page_images = false; 935 bool show_page_images = false;
674 bool check = false; 936 bool check = false;
675 937
  938 + std::vector<PageSpec> page_specs;
  939 +
676 bool require_outfile = true; 940 bool require_outfile = true;
677 char const* infilename = 0; 941 char const* infilename = 0;
678 char const* outfilename = 0; 942 char const* outfilename = 0;
@@ -694,7 +958,14 @@ int main(int argc, char* argv[]) @@ -694,7 +958,14 @@ int main(int argc, char* argv[])
694 *parameter++ = 0; 958 *parameter++ = 0;
695 } 959 }
696 960
697 - if (strcmp(arg, "password") == 0) 961 + // Arguments that start with space are undocumented and
  962 + // are for use by the test suite.
  963 + if (strcmp(arg, " test-numrange") == 0)
  964 + {
  965 + test_numrange(parameter);
  966 + exit(0);
  967 + }
  968 + else if (strcmp(arg, "password") == 0)
698 { 969 {
699 if (parameter == 0) 970 if (parameter == 0)
700 { 971 {
@@ -702,6 +973,10 @@ int main(int argc, char* argv[]) @@ -702,6 +973,10 @@ int main(int argc, char* argv[])
702 } 973 }
703 password = parameter; 974 password = parameter;
704 } 975 }
  976 + else if (strcmp(arg, "empty") == 0)
  977 + {
  978 + infilename = "";
  979 + }
705 else if (strcmp(arg, "linearize") == 0) 980 else if (strcmp(arg, "linearize") == 0)
706 { 981 {
707 linearize = true; 982 linearize = true;
@@ -745,6 +1020,14 @@ int main(int argc, char* argv[]) @@ -745,6 +1020,14 @@ int main(int argc, char* argv[])
745 } 1020 }
746 encryption_file_password = parameter; 1021 encryption_file_password = parameter;
747 } 1022 }
  1023 + else if (strcmp(arg, "pages") == 0)
  1024 + {
  1025 + page_specs = parse_pages_options(argc, argv, ++i);
  1026 + if (page_specs.empty())
  1027 + {
  1028 + usage("--pages: no page specifications given");
  1029 + }
  1030 + }
748 else if (strcmp(arg, "stream-data") == 0) 1031 else if (strcmp(arg, "stream-data") == 0)
749 { 1032 {
750 if (parameter == 0) 1033 if (parameter == 0)
@@ -950,7 +1233,14 @@ int main(int argc, char* argv[]) @@ -950,7 +1233,14 @@ int main(int argc, char* argv[])
950 { 1233 {
951 pdf.setAttemptRecovery(false); 1234 pdf.setAttemptRecovery(false);
952 } 1235 }
953 - pdf.processFile(infilename, password); 1236 + if (strcmp(infilename, "") == 0)
  1237 + {
  1238 + pdf.emptyPDF();
  1239 + }
  1240 + else
  1241 + {
  1242 + pdf.processFile(infilename, password);
  1243 + }
954 if (outfilename == 0) 1244 if (outfilename == 0)
955 { 1245 {
956 if (show_encryption) 1246 if (show_encryption)
@@ -1126,6 +1416,108 @@ int main(int argc, char* argv[]) @@ -1126,6 +1416,108 @@ int main(int argc, char* argv[])
1126 } 1416 }
1127 else 1417 else
1128 { 1418 {
  1419 + std::vector<PointerHolder<QPDF> > page_heap;
  1420 + if (! page_specs.empty())
  1421 + {
  1422 + // Parse all page specifications and translate them
  1423 + // into lists of actual pages.
  1424 +
  1425 + // Create a QPDF object for each file that we may take
  1426 + // pages from.
  1427 + std::map<std::string, QPDF*> page_spec_qpdfs;
  1428 + page_spec_qpdfs[infilename] = &pdf;
  1429 + std::vector<QPDFPageData> parsed_specs;
  1430 + for (std::vector<PageSpec>::iterator iter = page_specs.begin();
  1431 + iter != page_specs.end(); ++iter)
  1432 + {
  1433 + PageSpec& page_spec = *iter;
  1434 + if (page_spec_qpdfs.count(page_spec.filename) == 0)
  1435 + {
  1436 + // Open the PDF file and store the QPDF
  1437 + // object. Throw a PointerHolder to the qpdf
  1438 + // into a heap so that it survives through
  1439 + // writing the output but gets cleaned up
  1440 + // automatically at the end. Do not
  1441 + // canonicalize the file name. Using two
  1442 + // different paths to refer to the same file
  1443 + // is a document workaround for duplicating a
  1444 + // page. If you are using this an example of
  1445 + // how to do this with the API, you can just
  1446 + // create two different QPDF objects to the
  1447 + // same underlying file with the same path to
  1448 + // achieve the same affect.
  1449 + PointerHolder<QPDF> qpdf_ph = new QPDF();
  1450 + page_heap.push_back(qpdf_ph);
  1451 + QPDF* qpdf = qpdf_ph.getPointer();
  1452 + qpdf->processFile(
  1453 + page_spec.filename.c_str(), page_spec.password);
  1454 + page_spec_qpdfs[page_spec.filename] = qpdf;
  1455 + }
  1456 +
  1457 + // Read original pages from the PDF, and parse the
  1458 + // page range associated with this occurrence of
  1459 + // the file.
  1460 + parsed_specs.push_back(
  1461 + QPDFPageData(page_spec_qpdfs[page_spec.filename],
  1462 + page_spec.range));
  1463 + }
  1464 +
  1465 + // Clear all pages out of the primary QPDF's pages
  1466 + // tree but leave the objects in place in the file so
  1467 + // they can be re-added without changing their object
  1468 + // numbers. This enables other things in the original
  1469 + // file, such as outlines, to continue to work.
  1470 + std::vector<QPDFObjectHandle> orig_pages = pdf.getAllPages();
  1471 + for (std::vector<QPDFObjectHandle>::iterator iter =
  1472 + orig_pages.begin();
  1473 + iter != orig_pages.end(); ++iter)
  1474 + {
  1475 + pdf.removePage(*iter);
  1476 + }
  1477 +
  1478 + // Add all the pages from all the files in the order
  1479 + // specified. Keep track of any pages from the
  1480 + // original file that we are selecting.
  1481 + std::set<int> selected_from_orig;
  1482 + for (std::vector<QPDFPageData>::iterator iter =
  1483 + parsed_specs.begin();
  1484 + iter != parsed_specs.end(); ++iter)
  1485 + {
  1486 + QPDFPageData& page_data = *iter;
  1487 + for (std::vector<int>::iterator pageno_iter =
  1488 + page_data.selected_pages.begin();
  1489 + pageno_iter != page_data.selected_pages.end();
  1490 + ++pageno_iter)
  1491 + {
  1492 + // Pages are specified from 1 but numbered
  1493 + // from 0 in the vector
  1494 + int pageno = *pageno_iter - 1;
  1495 + pdf.addPage(page_data.orig_pages[pageno], false);
  1496 + if (page_data.qpdf == &pdf)
  1497 + {
  1498 + // This is a page from the original file.
  1499 + // Keep track of the fact that we are
  1500 + // using it.
  1501 + selected_from_orig.insert(pageno);
  1502 + }
  1503 + }
  1504 + }
  1505 +
  1506 + // Delete page objects for unused page in primary.
  1507 + // This prevents those objects from being preserved by
  1508 + // being referred to from other places, such as the
  1509 + // outlines dictionary.
  1510 + for (int pageno = 0; pageno < (int)orig_pages.size(); ++pageno)
  1511 + {
  1512 + if (selected_from_orig.count(pageno) == 0)
  1513 + {
  1514 + pdf.replaceObject(orig_pages[pageno].getObjectID(),
  1515 + orig_pages[pageno].getGeneration(),
  1516 + QPDFObjectHandle::newNull());
  1517 + }
  1518 + }
  1519 + }
  1520 +
1129 if (strcmp(outfilename, "-") == 0) 1521 if (strcmp(outfilename, "-") == 0)
1130 { 1522 {
1131 outfilename = 0; 1523 outfilename = 0;
qpdf/qtest/qpdf.test
@@ -377,6 +377,90 @@ $td-&gt;runtest(&quot;parse objects from string&quot;, @@ -377,6 +377,90 @@ $td-&gt;runtest(&quot;parse objects from string&quot;,
377 377
378 show_ntests(); 378 show_ntests();
379 # ---------- 379 # ----------
  380 +$td->notify("--- Numeric range parsing tests ---");
  381 +my @nrange_tests = (
  382 + [",5",
  383 + "qpdf: error at * in numeric range *,5: unexpected separator",
  384 + 2],
  385 + ["4,,5",
  386 + "qpdf: error at * in numeric range 4,*,5: unexpected separator",
  387 + 2],
  388 + ["4,5,",
  389 + "qpdf: error at * in numeric range 4,5,*: number expected",
  390 + 2],
  391 + ["z1,",
  392 + "qpdf: error at * in numeric range z*1,: digit not expected",
  393 + 2],
  394 + ["1z,",
  395 + "qpdf: error at * in numeric range 1*z,: z not expected",
  396 + 2],
  397 + ["1-5?",
  398 + "qpdf: error at * in numeric range 1-5*?: unexpected character",
  399 + 2],
  400 + ["1-30",
  401 + "qpdf: error in numeric range 1-30: number 30 out of range",
  402 + 2],
  403 + ["1-10,0,5",
  404 + "qpdf: error in numeric range 1-10,0,5: number 0 out of range",
  405 + 2],
  406 + ["1-10,1234,5",
  407 + "qpdf: error in numeric range 1-10,1234,5: number 1234 out of range",
  408 + 2],
  409 + ["1,3,5-10,z-13,13,9,z,2",
  410 + "numeric range 1,3,5-10,z-13,13,9,z,2" .
  411 + " -> 1 3 5 6 7 8 9 10 15 14 13 13 9 15 2",
  412 + 0],
  413 + );
  414 +$n_tests += scalar(@nrange_tests);
  415 +foreach my $d (@nrange_tests)
  416 +{
  417 + my ($range, $output, $status) = @$d;
  418 + $td->runtest("numeric range $range",
  419 + {$td->COMMAND => ['qpdf', '-- test-numrange=' . $range],
  420 + $td->FILTER => "grep 'numeric range'"},
  421 + {$td->STRING => $output . "\n", $td->EXIT_STATUS => $status},
  422 + $td->NORMALIZE_NEWLINES);
  423 +}
  424 +
  425 +# ----------
  426 +$td->notify("--- Merging and Splitting ---");
  427 +$n_tests += 4;
  428 +
  429 +# Select pages from the same file multiple times including selecting
  430 +# twice from an encrypted file and specifying the password only the
  431 +# first time. The file 20-pages.pdf is specified with two different
  432 +# paths to duplicate a page.
  433 +my $pages_options = "--pages page-labels-and-outlines.pdf 1,3,5-7,z" .
  434 + " 20-pages.pdf --password=user z-15" .
  435 + " page-labels-and-outlines.pdf 12" .
  436 + " 20-pages.pdf 10" .
  437 + " ./20-pages.pdf --password=owner 10" .
  438 + " minimal.pdf 1 --";
  439 +
  440 +$td->runtest("merge three files",
  441 + {$td->COMMAND => "qpdf page-labels-and-outlines.pdf a.pdf" .
  442 + " $pages_options --static-id"},
  443 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  444 +# Manually verified about this file: make sure that outline entries
  445 +# that pointed to pages that were preserved still work in the copy,
  446 +# and verify that all pages are as expected. page-labels-and-outlines
  447 +# as well as 20-pages have text on page n (from 1) that shows its page
  448 +# position from 0, so page 1 says it's page 0.
  449 +$td->runtest("check output",
  450 + {$td->FILE => "a.pdf"},
  451 + {$td->FILE => "merge-three-files-1.pdf"});
  452 +# Select the same pages but add them to an empty file
  453 +$td->runtest("merge three files",
  454 + {$td->COMMAND => "qpdf --empty a.pdf" .
  455 + " $pages_options --static-id"},
  456 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  457 +# Manually verified about this file: it has the same pages but does
  458 +# not contain outlines, page labels, or other things from the original
  459 +# file.
  460 +$td->runtest("check output",
  461 + {$td->FILE => "a.pdf"},
  462 + {$td->FILE => "merge-three-files-2.pdf"});
  463 +# ----------
380 $td->notify("--- PDF From Scratch ---"); 464 $td->notify("--- PDF From Scratch ---");
381 $n_tests += 2; 465 $n_tests += 2;
382 466
qpdf/qtest/qpdf/20-pages.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/merge-three-files-1.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/merge-three-files-2.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/page-labels-and-outlines.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +1 0 obj
  6 +<<
  7 + /PageLabels << /Nums [
  8 + 0 << /P () >>
  9 + 2 << /S /r /St 1 >>
  10 + 7 << /P () >>
  11 + 9 << /S /r /St 6 >>
  12 + 11 << /P () >>
  13 + 12 << /S /D /St 2 >>
  14 + 15 << /S /D /St 6 >>
  15 + 19 << /P () >>
  16 + 20 << /S /D /St 12 >>
  17 + 22 << /S /D /St 16059 >>
  18 + 23 << /S /r /St 50 >>
  19 + 29 << /S /r /St 54 >>
  20 + ] >>
  21 + /Pages 2 0 R
  22 + /Type /Catalog
  23 + /PageMode /UseOutlines
  24 + /Outlines 95 0 R
  25 +>>
  26 +endobj
  27 +
  28 +2 0 obj
  29 +<<
  30 + /Count 30
  31 + /Kids [
  32 + 3 0 R
  33 + 4 0 R
  34 + 5 0 R
  35 + 6 0 R
  36 + 7 0 R
  37 + 8 0 R
  38 + 9 0 R
  39 + 10 0 R
  40 + 11 0 R
  41 + 12 0 R
  42 + 13 0 R
  43 + 14 0 R
  44 + 15 0 R
  45 + 16 0 R
  46 + 17 0 R
  47 + 18 0 R
  48 + 19 0 R
  49 + 20 0 R
  50 + 21 0 R
  51 + 22 0 R
  52 + 23 0 R
  53 + 24 0 R
  54 + 25 0 R
  55 + 26 0 R
  56 + 27 0 R
  57 + 28 0 R
  58 + 29 0 R
  59 + 30 0 R
  60 + 31 0 R
  61 + 32 0 R
  62 + ]
  63 + /Type /Pages
  64 +>>
  65 +endobj
  66 +
  67 +%% Page 1
  68 +3 0 obj
  69 +<<
  70 + /Contents 33 0 R
  71 + /MediaBox [
  72 + 0
  73 + 0
  74 + 612
  75 + 792
  76 + ]
  77 + /Parent 2 0 R
  78 + /Resources <<
  79 + /Font <<
  80 + /F1 35 0 R
  81 + >>
  82 + /ProcSet 36 0 R
  83 + >>
  84 + /Type /Page
  85 +>>
  86 +endobj
  87 +
  88 +%% Page 2
  89 +4 0 obj
  90 +<<
  91 + /Contents 37 0 R
  92 + /MediaBox [
  93 + 0
  94 + 0
  95 + 612
  96 + 792
  97 + ]
  98 + /Parent 2 0 R
  99 + /Resources <<
  100 + /Font <<
  101 + /F1 35 0 R
  102 + >>
  103 + /ProcSet 36 0 R
  104 + >>
  105 + /Type /Page
  106 +>>
  107 +endobj
  108 +
  109 +%% Page 3
  110 +5 0 obj
  111 +<<
  112 + /Contents 39 0 R
  113 + /MediaBox [
  114 + 0
  115 + 0
  116 + 612
  117 + 792
  118 + ]
  119 + /Parent 2 0 R
  120 + /Resources <<
  121 + /Font <<
  122 + /F1 35 0 R
  123 + >>
  124 + /ProcSet 36 0 R
  125 + >>
  126 + /Type /Page
  127 +>>
  128 +endobj
  129 +
  130 +%% Page 4
  131 +6 0 obj
  132 +<<
  133 + /Contents 41 0 R
  134 + /MediaBox [
  135 + 0
  136 + 0
  137 + 612
  138 + 792
  139 + ]
  140 + /Parent 2 0 R
  141 + /Resources <<
  142 + /Font <<
  143 + /F1 35 0 R
  144 + >>
  145 + /ProcSet 36 0 R
  146 + >>
  147 + /Type /Page
  148 +>>
  149 +endobj
  150 +
  151 +%% Page 5
  152 +7 0 obj
  153 +<<
  154 + /Contents 43 0 R
  155 + /MediaBox [
  156 + 0
  157 + 0
  158 + 612
  159 + 792
  160 + ]
  161 + /Parent 2 0 R
  162 + /Resources <<
  163 + /Font <<
  164 + /F1 35 0 R
  165 + >>
  166 + /ProcSet 36 0 R
  167 + >>
  168 + /Type /Page
  169 +>>
  170 +endobj
  171 +
  172 +%% Page 6
  173 +8 0 obj
  174 +<<
  175 + /Contents 45 0 R
  176 + /MediaBox [
  177 + 0
  178 + 0
  179 + 612
  180 + 792
  181 + ]
  182 + /Parent 2 0 R
  183 + /Resources <<
  184 + /Font <<
  185 + /F1 35 0 R
  186 + >>
  187 + /ProcSet 36 0 R
  188 + >>
  189 + /Type /Page
  190 +>>
  191 +endobj
  192 +
  193 +%% Page 7
  194 +9 0 obj
  195 +<<
  196 + /Contents 47 0 R
  197 + /MediaBox [
  198 + 0
  199 + 0
  200 + 612
  201 + 792
  202 + ]
  203 + /Parent 2 0 R
  204 + /Resources <<
  205 + /Font <<
  206 + /F1 35 0 R
  207 + >>
  208 + /ProcSet 36 0 R
  209 + >>
  210 + /Type /Page
  211 +>>
  212 +endobj
  213 +
  214 +%% Page 8
  215 +10 0 obj
  216 +<<
  217 + /Contents 49 0 R
  218 + /MediaBox [
  219 + 0
  220 + 0
  221 + 612
  222 + 792
  223 + ]
  224 + /Parent 2 0 R
  225 + /Resources <<
  226 + /Font <<
  227 + /F1 35 0 R
  228 + >>
  229 + /ProcSet 36 0 R
  230 + >>
  231 + /Type /Page
  232 +>>
  233 +endobj
  234 +
  235 +%% Page 9
  236 +11 0 obj
  237 +<<
  238 + /Contents 51 0 R
  239 + /MediaBox [
  240 + 0
  241 + 0
  242 + 612
  243 + 792
  244 + ]
  245 + /Parent 2 0 R
  246 + /Resources <<
  247 + /Font <<
  248 + /F1 35 0 R
  249 + >>
  250 + /ProcSet 36 0 R
  251 + >>
  252 + /Type /Page
  253 +>>
  254 +endobj
  255 +
  256 +%% Page 10
  257 +12 0 obj
  258 +<<
  259 + /Contents 53 0 R
  260 + /MediaBox [
  261 + 0
  262 + 0
  263 + 612
  264 + 792
  265 + ]
  266 + /Parent 2 0 R
  267 + /Resources <<
  268 + /Font <<
  269 + /F1 35 0 R
  270 + >>
  271 + /ProcSet 36 0 R
  272 + >>
  273 + /Type /Page
  274 +>>
  275 +endobj
  276 +
  277 +%% Page 11
  278 +13 0 obj
  279 +<<
  280 + /Contents 55 0 R
  281 + /MediaBox [
  282 + 0
  283 + 0
  284 + 612
  285 + 792
  286 + ]
  287 + /Parent 2 0 R
  288 + /Resources <<
  289 + /Font <<
  290 + /F1 35 0 R
  291 + >>
  292 + /ProcSet 36 0 R
  293 + >>
  294 + /Type /Page
  295 +>>
  296 +endobj
  297 +
  298 +%% Page 12
  299 +14 0 obj
  300 +<<
  301 + /Contents 57 0 R
  302 + /MediaBox [
  303 + 0
  304 + 0
  305 + 612
  306 + 792
  307 + ]
  308 + /Parent 2 0 R
  309 + /Resources <<
  310 + /Font <<
  311 + /F1 35 0 R
  312 + >>
  313 + /ProcSet 36 0 R
  314 + >>
  315 + /Type /Page
  316 +>>
  317 +endobj
  318 +
  319 +%% Page 13
  320 +15 0 obj
  321 +<<
  322 + /Contents 59 0 R
  323 + /MediaBox [
  324 + 0
  325 + 0
  326 + 612
  327 + 792
  328 + ]
  329 + /Parent 2 0 R
  330 + /Resources <<
  331 + /Font <<
  332 + /F1 35 0 R
  333 + >>
  334 + /ProcSet 36 0 R
  335 + >>
  336 + /Type /Page
  337 +>>
  338 +endobj
  339 +
  340 +%% Page 14
  341 +16 0 obj
  342 +<<
  343 + /Contents 61 0 R
  344 + /MediaBox [
  345 + 0
  346 + 0
  347 + 612
  348 + 792
  349 + ]
  350 + /Parent 2 0 R
  351 + /Resources <<
  352 + /Font <<
  353 + /F1 35 0 R
  354 + >>
  355 + /ProcSet 36 0 R
  356 + >>
  357 + /Type /Page
  358 +>>
  359 +endobj
  360 +
  361 +%% Page 15
  362 +17 0 obj
  363 +<<
  364 + /Contents 63 0 R
  365 + /MediaBox [
  366 + 0
  367 + 0
  368 + 612
  369 + 792
  370 + ]
  371 + /Parent 2 0 R
  372 + /Resources <<
  373 + /Font <<
  374 + /F1 35 0 R
  375 + >>
  376 + /ProcSet 36 0 R
  377 + >>
  378 + /Type /Page
  379 +>>
  380 +endobj
  381 +
  382 +%% Page 16
  383 +18 0 obj
  384 +<<
  385 + /Contents 65 0 R
  386 + /MediaBox [
  387 + 0
  388 + 0
  389 + 612
  390 + 792
  391 + ]
  392 + /Parent 2 0 R
  393 + /Resources <<
  394 + /Font <<
  395 + /F1 35 0 R
  396 + >>
  397 + /ProcSet 36 0 R
  398 + >>
  399 + /Type /Page
  400 +>>
  401 +endobj
  402 +
  403 +%% Page 17
  404 +19 0 obj
  405 +<<
  406 + /Contents 67 0 R
  407 + /MediaBox [
  408 + 0
  409 + 0
  410 + 612
  411 + 792
  412 + ]
  413 + /Parent 2 0 R
  414 + /Resources <<
  415 + /Font <<
  416 + /F1 35 0 R
  417 + >>
  418 + /ProcSet 36 0 R
  419 + >>
  420 + /Type /Page
  421 +>>
  422 +endobj
  423 +
  424 +%% Page 18
  425 +20 0 obj
  426 +<<
  427 + /Contents 69 0 R
  428 + /MediaBox [
  429 + 0
  430 + 0
  431 + 612
  432 + 792
  433 + ]
  434 + /Parent 2 0 R
  435 + /Resources <<
  436 + /Font <<
  437 + /F1 35 0 R
  438 + >>
  439 + /ProcSet 36 0 R
  440 + >>
  441 + /Type /Page
  442 +>>
  443 +endobj
  444 +
  445 +%% Page 19
  446 +21 0 obj
  447 +<<
  448 + /Contents 71 0 R
  449 + /MediaBox [
  450 + 0
  451 + 0
  452 + 612
  453 + 792
  454 + ]
  455 + /Parent 2 0 R
  456 + /Resources <<
  457 + /Font <<
  458 + /F1 35 0 R
  459 + >>
  460 + /ProcSet 36 0 R
  461 + >>
  462 + /Type /Page
  463 +>>
  464 +endobj
  465 +
  466 +%% Page 20
  467 +22 0 obj
  468 +<<
  469 + /Contents 73 0 R
  470 + /MediaBox [
  471 + 0
  472 + 0
  473 + 612
  474 + 792
  475 + ]
  476 + /Parent 2 0 R
  477 + /Resources <<
  478 + /Font <<
  479 + /F1 35 0 R
  480 + >>
  481 + /ProcSet 36 0 R
  482 + >>
  483 + /Type /Page
  484 +>>
  485 +endobj
  486 +
  487 +%% Page 21
  488 +23 0 obj
  489 +<<
  490 + /Contents 75 0 R
  491 + /MediaBox [
  492 + 0
  493 + 0
  494 + 612
  495 + 792
  496 + ]
  497 + /Parent 2 0 R
  498 + /Resources <<
  499 + /Font <<
  500 + /F1 35 0 R
  501 + >>
  502 + /ProcSet 36 0 R
  503 + >>
  504 + /Type /Page
  505 +>>
  506 +endobj
  507 +
  508 +%% Page 22
  509 +24 0 obj
  510 +<<
  511 + /Contents 77 0 R
  512 + /MediaBox [
  513 + 0
  514 + 0
  515 + 612
  516 + 792
  517 + ]
  518 + /Parent 2 0 R
  519 + /Resources <<
  520 + /Font <<
  521 + /F1 35 0 R
  522 + >>
  523 + /ProcSet 36 0 R
  524 + >>
  525 + /Type /Page
  526 +>>
  527 +endobj
  528 +
  529 +%% Page 23
  530 +25 0 obj
  531 +<<
  532 + /Contents 79 0 R
  533 + /MediaBox [
  534 + 0
  535 + 0
  536 + 612
  537 + 792
  538 + ]
  539 + /Parent 2 0 R
  540 + /Resources <<
  541 + /Font <<
  542 + /F1 35 0 R
  543 + >>
  544 + /ProcSet 36 0 R
  545 + >>
  546 + /Type /Page
  547 +>>
  548 +endobj
  549 +
  550 +%% Page 24
  551 +26 0 obj
  552 +<<
  553 + /Contents 81 0 R
  554 + /MediaBox [
  555 + 0
  556 + 0
  557 + 612
  558 + 792
  559 + ]
  560 + /Parent 2 0 R
  561 + /Resources <<
  562 + /Font <<
  563 + /F1 35 0 R
  564 + >>
  565 + /ProcSet 36 0 R
  566 + >>
  567 + /Type /Page
  568 +>>
  569 +endobj
  570 +
  571 +%% Page 25
  572 +27 0 obj
  573 +<<
  574 + /Contents 83 0 R
  575 + /MediaBox [
  576 + 0
  577 + 0
  578 + 612
  579 + 792
  580 + ]
  581 + /Parent 2 0 R
  582 + /Resources <<
  583 + /Font <<
  584 + /F1 35 0 R
  585 + >>
  586 + /ProcSet 36 0 R
  587 + >>
  588 + /Type /Page
  589 +>>
  590 +endobj
  591 +
  592 +%% Page 26
  593 +28 0 obj
  594 +<<
  595 + /Contents 85 0 R
  596 + /MediaBox [
  597 + 0
  598 + 0
  599 + 612
  600 + 792
  601 + ]
  602 + /Parent 2 0 R
  603 + /Resources <<
  604 + /Font <<
  605 + /F1 35 0 R
  606 + >>
  607 + /ProcSet 36 0 R
  608 + >>
  609 + /Type /Page
  610 +>>
  611 +endobj
  612 +
  613 +%% Page 27
  614 +29 0 obj
  615 +<<
  616 + /Contents 87 0 R
  617 + /MediaBox [
  618 + 0
  619 + 0
  620 + 612
  621 + 792
  622 + ]
  623 + /Parent 2 0 R
  624 + /Resources <<
  625 + /Font <<
  626 + /F1 35 0 R
  627 + >>
  628 + /ProcSet 36 0 R
  629 + >>
  630 + /Type /Page
  631 +>>
  632 +endobj
  633 +
  634 +%% Page 28
  635 +30 0 obj
  636 +<<
  637 + /Contents 89 0 R
  638 + /MediaBox [
  639 + 0
  640 + 0
  641 + 612
  642 + 792
  643 + ]
  644 + /Parent 2 0 R
  645 + /Resources <<
  646 + /Font <<
  647 + /F1 35 0 R
  648 + >>
  649 + /ProcSet 36 0 R
  650 + >>
  651 + /Type /Page
  652 +>>
  653 +endobj
  654 +
  655 +%% Page 29
  656 +31 0 obj
  657 +<<
  658 + /Contents 91 0 R
  659 + /MediaBox [
  660 + 0
  661 + 0
  662 + 612
  663 + 792
  664 + ]
  665 + /Parent 2 0 R
  666 + /Resources <<
  667 + /Font <<
  668 + /F1 35 0 R
  669 + >>
  670 + /ProcSet 36 0 R
  671 + >>
  672 + /Type /Page
  673 +>>
  674 +endobj
  675 +
  676 +%% Page 30
  677 +32 0 obj
  678 +<<
  679 + /Contents 93 0 R
  680 + /MediaBox [
  681 + 0
  682 + 0
  683 + 612
  684 + 792
  685 + ]
  686 + /Parent 2 0 R
  687 + /Resources <<
  688 + /Font <<
  689 + /F1 35 0 R
  690 + >>
  691 + /ProcSet 36 0 R
  692 + >>
  693 + /Type /Page
  694 +>>
  695 +endobj
  696 +
  697 +%% Contents for page 1
  698 +33 0 obj
  699 +<<
  700 + /Length 34 0 R
  701 +>>
  702 +stream
  703 +BT
  704 + /F1 24 Tf
  705 + 72 720 Td
  706 + (Potato 0) Tj
  707 +ET
  708 +endstream
  709 +endobj
  710 +
  711 +34 0 obj
  712 +46
  713 +endobj
  714 +
  715 +35 0 obj
  716 +<<
  717 + /BaseFont /Helvetica
  718 + /Encoding /WinAnsiEncoding
  719 + /Name /F1
  720 + /Subtype /Type1
  721 + /Type /Font
  722 +>>
  723 +endobj
  724 +
  725 +36 0 obj
  726 +[
  727 + /PDF
  728 + /Text
  729 +]
  730 +endobj
  731 +
  732 +%% Contents for page 2
  733 +37 0 obj
  734 +<<
  735 + /Length 38 0 R
  736 +>>
  737 +stream
  738 +BT
  739 + /F1 24 Tf
  740 + 72 720 Td
  741 + (Potato 1) Tj
  742 +ET
  743 +endstream
  744 +endobj
  745 +
  746 +38 0 obj
  747 +46
  748 +endobj
  749 +
  750 +%% Contents for page 3
  751 +39 0 obj
  752 +<<
  753 + /Length 40 0 R
  754 +>>
  755 +stream
  756 +BT
  757 + /F1 24 Tf
  758 + 72 720 Td
  759 + (Potato 2) Tj
  760 +ET
  761 +endstream
  762 +endobj
  763 +
  764 +40 0 obj
  765 +46
  766 +endobj
  767 +
  768 +%% Contents for page 4
  769 +41 0 obj
  770 +<<
  771 + /Length 42 0 R
  772 +>>
  773 +stream
  774 +BT
  775 + /F1 24 Tf
  776 + 72 720 Td
  777 + (Potato 3) Tj
  778 +ET
  779 +endstream
  780 +endobj
  781 +
  782 +42 0 obj
  783 +46
  784 +endobj
  785 +
  786 +%% Contents for page 5
  787 +43 0 obj
  788 +<<
  789 + /Length 44 0 R
  790 +>>
  791 +stream
  792 +BT
  793 + /F1 24 Tf
  794 + 72 720 Td
  795 + (Potato 4) Tj
  796 +ET
  797 +endstream
  798 +endobj
  799 +
  800 +44 0 obj
  801 +46
  802 +endobj
  803 +
  804 +%% Contents for page 6
  805 +45 0 obj
  806 +<<
  807 + /Length 46 0 R
  808 +>>
  809 +stream
  810 +BT
  811 + /F1 24 Tf
  812 + 72 720 Td
  813 + (Potato 5) Tj
  814 +ET
  815 +endstream
  816 +endobj
  817 +
  818 +46 0 obj
  819 +46
  820 +endobj
  821 +
  822 +%% Contents for page 7
  823 +47 0 obj
  824 +<<
  825 + /Length 48 0 R
  826 +>>
  827 +stream
  828 +BT
  829 + /F1 24 Tf
  830 + 72 720 Td
  831 + (Potato 6) Tj
  832 +ET
  833 +endstream
  834 +endobj
  835 +
  836 +48 0 obj
  837 +46
  838 +endobj
  839 +
  840 +%% Contents for page 8
  841 +49 0 obj
  842 +<<
  843 + /Length 50 0 R
  844 +>>
  845 +stream
  846 +BT
  847 + /F1 24 Tf
  848 + 72 720 Td
  849 + (Potato 7) Tj
  850 +ET
  851 +endstream
  852 +endobj
  853 +
  854 +50 0 obj
  855 +46
  856 +endobj
  857 +
  858 +%% Contents for page 9
  859 +51 0 obj
  860 +<<
  861 + /Length 52 0 R
  862 +>>
  863 +stream
  864 +BT
  865 + /F1 24 Tf
  866 + 72 720 Td
  867 + (Potato 8) Tj
  868 +ET
  869 +endstream
  870 +endobj
  871 +
  872 +52 0 obj
  873 +46
  874 +endobj
  875 +
  876 +%% Contents for page 10
  877 +53 0 obj
  878 +<<
  879 + /Length 54 0 R
  880 +>>
  881 +stream
  882 +BT
  883 + /F1 24 Tf
  884 + 72 720 Td
  885 + (Potato 9) Tj
  886 +ET
  887 +endstream
  888 +endobj
  889 +
  890 +54 0 obj
  891 +46
  892 +endobj
  893 +
  894 +%% Contents for page 11
  895 +55 0 obj
  896 +<<
  897 + /Length 56 0 R
  898 +>>
  899 +stream
  900 +BT
  901 + /F1 24 Tf
  902 + 72 720 Td
  903 + (Potato 10) Tj
  904 +ET
  905 +endstream
  906 +endobj
  907 +
  908 +56 0 obj
  909 +47
  910 +endobj
  911 +
  912 +%% Contents for page 12
  913 +57 0 obj
  914 +<<
  915 + /Length 58 0 R
  916 +>>
  917 +stream
  918 +BT
  919 + /F1 24 Tf
  920 + 72 720 Td
  921 + (Potato 11) Tj
  922 +ET
  923 +endstream
  924 +endobj
  925 +
  926 +58 0 obj
  927 +47
  928 +endobj
  929 +
  930 +%% Contents for page 13
  931 +59 0 obj
  932 +<<
  933 + /Length 60 0 R
  934 +>>
  935 +stream
  936 +BT
  937 + /F1 24 Tf
  938 + 72 720 Td
  939 + (Potato 12) Tj
  940 +ET
  941 +endstream
  942 +endobj
  943 +
  944 +60 0 obj
  945 +47
  946 +endobj
  947 +
  948 +%% Contents for page 14
  949 +61 0 obj
  950 +<<
  951 + /Length 62 0 R
  952 +>>
  953 +stream
  954 +BT
  955 + /F1 24 Tf
  956 + 72 720 Td
  957 + (Potato 13) Tj
  958 +ET
  959 +endstream
  960 +endobj
  961 +
  962 +62 0 obj
  963 +47
  964 +endobj
  965 +
  966 +%% Contents for page 15
  967 +63 0 obj
  968 +<<
  969 + /Length 64 0 R
  970 +>>
  971 +stream
  972 +BT
  973 + /F1 24 Tf
  974 + 72 720 Td
  975 + (Potato 14) Tj
  976 +ET
  977 +endstream
  978 +endobj
  979 +
  980 +64 0 obj
  981 +47
  982 +endobj
  983 +
  984 +%% Contents for page 16
  985 +65 0 obj
  986 +<<
  987 + /Length 66 0 R
  988 +>>
  989 +stream
  990 +BT
  991 + /F1 24 Tf
  992 + 72 720 Td
  993 + (Potato 15) Tj
  994 +ET
  995 +endstream
  996 +endobj
  997 +
  998 +66 0 obj
  999 +47
  1000 +endobj
  1001 +
  1002 +%% Contents for page 17
  1003 +67 0 obj
  1004 +<<
  1005 + /Length 68 0 R
  1006 +>>
  1007 +stream
  1008 +BT
  1009 + /F1 24 Tf
  1010 + 72 720 Td
  1011 + (Potato 16) Tj
  1012 +ET
  1013 +endstream
  1014 +endobj
  1015 +
  1016 +68 0 obj
  1017 +47
  1018 +endobj
  1019 +
  1020 +%% Contents for page 18
  1021 +69 0 obj
  1022 +<<
  1023 + /Length 70 0 R
  1024 +>>
  1025 +stream
  1026 +BT
  1027 + /F1 24 Tf
  1028 + 72 720 Td
  1029 + (Potato 17) Tj
  1030 +ET
  1031 +endstream
  1032 +endobj
  1033 +
  1034 +70 0 obj
  1035 +47
  1036 +endobj
  1037 +
  1038 +%% Contents for page 19
  1039 +71 0 obj
  1040 +<<
  1041 + /Length 72 0 R
  1042 +>>
  1043 +stream
  1044 +BT
  1045 + /F1 24 Tf
  1046 + 72 720 Td
  1047 + (Potato 18) Tj
  1048 +ET
  1049 +endstream
  1050 +endobj
  1051 +
  1052 +72 0 obj
  1053 +47
  1054 +endobj
  1055 +
  1056 +%% Contents for page 20
  1057 +73 0 obj
  1058 +<<
  1059 + /Length 74 0 R
  1060 +>>
  1061 +stream
  1062 +BT
  1063 + /F1 24 Tf
  1064 + 72 720 Td
  1065 + (Potato 19) Tj
  1066 +ET
  1067 +endstream
  1068 +endobj
  1069 +
  1070 +74 0 obj
  1071 +47
  1072 +endobj
  1073 +
  1074 +%% Contents for page 21
  1075 +75 0 obj
  1076 +<<
  1077 + /Length 76 0 R
  1078 +>>
  1079 +stream
  1080 +BT
  1081 + /F1 24 Tf
  1082 + 72 720 Td
  1083 + (Potato 20) Tj
  1084 +ET
  1085 +endstream
  1086 +endobj
  1087 +
  1088 +76 0 obj
  1089 +47
  1090 +endobj
  1091 +
  1092 +%% Contents for page 22
  1093 +77 0 obj
  1094 +<<
  1095 + /Length 78 0 R
  1096 +>>
  1097 +stream
  1098 +BT
  1099 + /F1 24 Tf
  1100 + 72 720 Td
  1101 + (Potato 21) Tj
  1102 +ET
  1103 +endstream
  1104 +endobj
  1105 +
  1106 +78 0 obj
  1107 +47
  1108 +endobj
  1109 +
  1110 +%% Contents for page 23
  1111 +79 0 obj
  1112 +<<
  1113 + /Length 80 0 R
  1114 +>>
  1115 +stream
  1116 +BT
  1117 + /F1 24 Tf
  1118 + 72 720 Td
  1119 + (Potato 22) Tj
  1120 +ET
  1121 +endstream
  1122 +endobj
  1123 +
  1124 +80 0 obj
  1125 +47
  1126 +endobj
  1127 +
  1128 +%% Contents for page 24
  1129 +81 0 obj
  1130 +<<
  1131 + /Length 82 0 R
  1132 +>>
  1133 +stream
  1134 +BT
  1135 + /F1 24 Tf
  1136 + 72 720 Td
  1137 + (Potato 23) Tj
  1138 +ET
  1139 +endstream
  1140 +endobj
  1141 +
  1142 +82 0 obj
  1143 +47
  1144 +endobj
  1145 +
  1146 +%% Contents for page 25
  1147 +83 0 obj
  1148 +<<
  1149 + /Length 84 0 R
  1150 +>>
  1151 +stream
  1152 +BT
  1153 + /F1 24 Tf
  1154 + 72 720 Td
  1155 + (Potato 24) Tj
  1156 +ET
  1157 +endstream
  1158 +endobj
  1159 +
  1160 +84 0 obj
  1161 +47
  1162 +endobj
  1163 +
  1164 +%% Contents for page 26
  1165 +85 0 obj
  1166 +<<
  1167 + /Length 86 0 R
  1168 +>>
  1169 +stream
  1170 +BT
  1171 + /F1 24 Tf
  1172 + 72 720 Td
  1173 + (Potato 25) Tj
  1174 +ET
  1175 +endstream
  1176 +endobj
  1177 +
  1178 +86 0 obj
  1179 +47
  1180 +endobj
  1181 +
  1182 +%% Contents for page 27
  1183 +87 0 obj
  1184 +<<
  1185 + /Length 88 0 R
  1186 +>>
  1187 +stream
  1188 +BT
  1189 + /F1 24 Tf
  1190 + 72 720 Td
  1191 + (Potato 26) Tj
  1192 +ET
  1193 +endstream
  1194 +endobj
  1195 +
  1196 +88 0 obj
  1197 +47
  1198 +endobj
  1199 +
  1200 +%% Contents for page 28
  1201 +89 0 obj
  1202 +<<
  1203 + /Length 90 0 R
  1204 +>>
  1205 +stream
  1206 +BT
  1207 + /F1 24 Tf
  1208 + 72 720 Td
  1209 + (Potato 27) Tj
  1210 +ET
  1211 +endstream
  1212 +endobj
  1213 +
  1214 +90 0 obj
  1215 +47
  1216 +endobj
  1217 +
  1218 +%% Contents for page 29
  1219 +91 0 obj
  1220 +<<
  1221 + /Length 92 0 R
  1222 +>>
  1223 +stream
  1224 +BT
  1225 + /F1 24 Tf
  1226 + 72 720 Td
  1227 + (Potato 28) Tj
  1228 +ET
  1229 +endstream
  1230 +endobj
  1231 +
  1232 +92 0 obj
  1233 +47
  1234 +endobj
  1235 +
  1236 +%% Contents for page 30
  1237 +93 0 obj
  1238 +<<
  1239 + /Length 94 0 R
  1240 +>>
  1241 +stream
  1242 +BT
  1243 + /F1 24 Tf
  1244 + 72 720 Td
  1245 + (Potato 29) Tj
  1246 +ET
  1247 +endstream
  1248 +endobj
  1249 +
  1250 +94 0 obj
  1251 +47
  1252 +endobj
  1253 +
  1254 +95 0 obj
  1255 +<<
  1256 + /Type /Outlines
  1257 + /First 96 0 R
  1258 + /Last 97 0 R
  1259 + /Count 6
  1260 +>>
  1261 +endobj
  1262 +
  1263 +96 0 obj
  1264 +<<
  1265 + /Type /Outline
  1266 + /Title (Isรญs 1 -> 5: /XYZ null null null)
  1267 + /Parent 95 0 R
  1268 + /Count 4
  1269 + /Next 97 0 R
  1270 + /First 98 0 R
  1271 + /Last 99 0 R
  1272 + /Dest [ 8 0 R /XYZ null null null ]
  1273 +>>
  1274 +endobj
  1275 +
  1276 +97 0 obj
  1277 +<<
  1278 + /Type /Outline
  1279 + /Title (Trepak 2 -> 15: /XYZ 66 756 3)
  1280 + /Parent 95 0 R
  1281 + /Prev 96 0 R
  1282 + /Dest [ 18 0 R /XYZ 66 756 3 ]
  1283 +>>
  1284 +endobj
  1285 +
  1286 +98 0 obj
  1287 +<<
  1288 + /Type /Outline
  1289 + /Title (Amanda 1.1 -> 11: /Fit)
  1290 + /Parent 96 0 R
  1291 + /Next 99 0 R
  1292 + /First 100 0 R
  1293 + /Last 101 0 R
  1294 + /Count -3
  1295 + /Dest [ 14 0 R /Fit ]
  1296 +>>
  1297 +endobj
  1298 +
  1299 +99 0 obj
  1300 +<<
  1301 + /Type /Outline
  1302 + % /Title (Sandy (Sandy [Greek]) 1.2 -> 13: /FitH 792)
  1303 + /Title <feff00530061006e00640079002000f703a303b103bd03b403b900f700200031002e00320020002d003e002000310033003a0020002f00460069007400480020003700390032>
  1304 + /Parent 96 0 R
  1305 + /Prev 98 0 R
  1306 + /First 105 0 R
  1307 + /Last 106 0 R
  1308 + /Count 2
  1309 + /Dest [ 16 0 R /FitH 792 ]
  1310 +>>
  1311 +endobj
  1312 +
  1313 +100 0 obj
  1314 +<<
  1315 + /Type /Outline
  1316 + /Title (Isosicle 1.1.1 -> 12: /FitV 100)
  1317 + /Parent 98 0 R
  1318 + /Next 101 0 R
  1319 + /First 102 0 R
  1320 + /Last 103 0 R
  1321 + /Count -2
  1322 + /Dest [ 15 0 R /FitV 100 ]
  1323 +>>
  1324 +endobj
  1325 +
  1326 +101 0 obj
  1327 +<<
  1328 + /Type /Outline
  1329 + /Title (Isosicle 1.1.2 -> 12: /XYZ null null null)
  1330 + /Parent 98 0 R
  1331 + /Prev 100 0 R
  1332 + /First 104 0 R
  1333 + /Last 104 0 R
  1334 + /Count 1
  1335 + /Dest [ 15 0 R /XYZ null null null ]
  1336 +>>
  1337 +endobj
  1338 +
  1339 +102 0 obj
  1340 +<<
  1341 + /Type /Outline
  1342 + /Title (Isosicle 1.1.1.1 -> 18: /XYZ null null null)
  1343 + /Parent 100 0 R
  1344 + /Next 103 0 R
  1345 + /Dest [ 21 0 R /XYZ null null null ]
  1346 +>>
  1347 +endobj
  1348 +
  1349 +103 0 obj
  1350 +<<
  1351 + /Type /Outline
  1352 + /Title (Isosicle 1.1.1.2 -> 19: /XYZ null null null)
  1353 + /Parent 100 0 R
  1354 + /Prev 102 0 R
  1355 + /Dest [ 22 0 R /XYZ null null null ]
  1356 +>>
  1357 +endobj
  1358 +
  1359 +104 0 obj
  1360 +<<
  1361 + /Type /Outline
  1362 + /Title (Isosicle 1.1.2.1 -> 22: /XYZ null null null)
  1363 + /Parent 101 0 R
  1364 + /Dest [ 25 0 R /XYZ null null null ]
  1365 +>>
  1366 +endobj
  1367 +
  1368 +105 0 obj
  1369 +<<
  1370 + /Type /Outline
  1371 + /Title (Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770)
  1372 + /Parent 99 0 R
  1373 + /Next 106 0 R
  1374 + /Dest [ 4 0 R /FitR 66 714 180 770 ]
  1375 +>>
  1376 +endobj
  1377 +
  1378 +106 0 obj
  1379 +<<
  1380 + /Type /Outline
  1381 + /Title (Trepsicle 1.2.2 -> 0: /XYZ null null null)
  1382 + /Parent 99 0 R
  1383 + /Prev 105 0 R
  1384 + /Dest [ 3 0 R /XYZ null null null ]
  1385 +>>
  1386 +endobj
  1387 +
  1388 +xref
  1389 +0 107
  1390 +0000000000 65535 f
  1391 +0000000025 00000 n
  1392 +0000000434 00000 n
  1393 +0000000830 00000 n
  1394 +0000001035 00000 n
  1395 +0000001240 00000 n
  1396 +0000001445 00000 n
  1397 +0000001650 00000 n
  1398 +0000001855 00000 n
  1399 +0000002060 00000 n
  1400 +0000002265 00000 n
  1401 +0000002471 00000 n
  1402 +0000002678 00000 n
  1403 +0000002885 00000 n
  1404 +0000003092 00000 n
  1405 +0000003299 00000 n
  1406 +0000003506 00000 n
  1407 +0000003713 00000 n
  1408 +0000003920 00000 n
  1409 +0000004127 00000 n
  1410 +0000004334 00000 n
  1411 +0000004541 00000 n
  1412 +0000004748 00000 n
  1413 +0000004955 00000 n
  1414 +0000005162 00000 n
  1415 +0000005369 00000 n
  1416 +0000005576 00000 n
  1417 +0000005783 00000 n
  1418 +0000005990 00000 n
  1419 +0000006197 00000 n
  1420 +0000006404 00000 n
  1421 +0000006611 00000 n
  1422 +0000006818 00000 n
  1423 +0000007037 00000 n
  1424 +0000007140 00000 n
  1425 +0000007160 00000 n
  1426 +0000007279 00000 n
  1427 +0000007338 00000 n
  1428 +0000007441 00000 n
  1429 +0000007484 00000 n
  1430 +0000007587 00000 n
  1431 +0000007630 00000 n
  1432 +0000007733 00000 n
  1433 +0000007776 00000 n
  1434 +0000007879 00000 n
  1435 +0000007922 00000 n
  1436 +0000008025 00000 n
  1437 +0000008068 00000 n
  1438 +0000008171 00000 n
  1439 +0000008214 00000 n
  1440 +0000008317 00000 n
  1441 +0000008360 00000 n
  1442 +0000008463 00000 n
  1443 +0000008507 00000 n
  1444 +0000008610 00000 n
  1445 +0000008654 00000 n
  1446 +0000008758 00000 n
  1447 +0000008802 00000 n
  1448 +0000008906 00000 n
  1449 +0000008950 00000 n
  1450 +0000009054 00000 n
  1451 +0000009098 00000 n
  1452 +0000009202 00000 n
  1453 +0000009246 00000 n
  1454 +0000009350 00000 n
  1455 +0000009394 00000 n
  1456 +0000009498 00000 n
  1457 +0000009542 00000 n
  1458 +0000009646 00000 n
  1459 +0000009690 00000 n
  1460 +0000009794 00000 n
  1461 +0000009838 00000 n
  1462 +0000009942 00000 n
  1463 +0000009986 00000 n
  1464 +0000010090 00000 n
  1465 +0000010134 00000 n
  1466 +0000010238 00000 n
  1467 +0000010282 00000 n
  1468 +0000010386 00000 n
  1469 +0000010430 00000 n
  1470 +0000010534 00000 n
  1471 +0000010578 00000 n
  1472 +0000010682 00000 n
  1473 +0000010726 00000 n
  1474 +0000010830 00000 n
  1475 +0000010874 00000 n
  1476 +0000010978 00000 n
  1477 +0000011022 00000 n
  1478 +0000011126 00000 n
  1479 +0000011170 00000 n
  1480 +0000011274 00000 n
  1481 +0000011318 00000 n
  1482 +0000011422 00000 n
  1483 +0000011466 00000 n
  1484 +0000011570 00000 n
  1485 +0000011590 00000 n
  1486 +0000011677 00000 n
  1487 +0000011873 00000 n
  1488 +0000012019 00000 n
  1489 +0000012194 00000 n
  1490 +0000012547 00000 n
  1491 +0000012738 00000 n
  1492 +0000012948 00000 n
  1493 +0000013117 00000 n
  1494 +0000013286 00000 n
  1495 +0000013439 00000 n
  1496 +0000013608 00000 n
  1497 +trailer <<
  1498 + /Root 1 0 R
  1499 + /Size 107
  1500 +>>
  1501 +startxref
  1502 +13773
  1503 +%%EOF