From d8d70ecca264fa5c681dca992a03cfa4c46b3f43 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Mon, 1 Jan 2024 18:55:45 -0500 Subject: [PATCH] Support comma-separated numeric values with --collate (fixes #505) --- ChangeLog | 6 +- include/qpdf/QPDFJob.hh | 2 +- job.sums | 6 +- libqpdf/QPDFJob.cc | 35 +- libqpdf/QPDFJob_config.cc | 31 +- libqpdf/qpdf/auto_job_help.hh | 8 +- manual/cli.rst | 49 +- manual/qpdf.1 | 8 +- manual/release-notes.rst | 4 + qpdf/qtest/arg-parsing.test | 10 +- qpdf/qtest/collate.test | 8 +- .../qpdf/three-files-2,3,4-collate-out.pdf | 988 ++++++++++++++++++ 12 files changed, 1116 insertions(+), 39 deletions(-) create mode 100644 qpdf/qtest/qpdf/three-files-2,3,4-collate-out.pdf diff --git a/ChangeLog b/ChangeLog index a5bc330a..c5336c11 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,11 @@ 2024-01-01 Jay Berkenbilt + * Support comma-separated numeric values with --collate to select + different group sizes from different files. Fixes #505. + * Support "x" before a group in a numeric range to exclude a group - from the previous group. Details are in the manual. + from the previous group. Details are in the manual. Fixes #564, + #790. 2023-12-29 Jay Berkenbilt diff --git a/include/qpdf/QPDFJob.hh b/include/qpdf/QPDFJob.hh index d7f14957..3cfc550e 100644 --- a/include/qpdf/QPDFJob.hh +++ b/include/qpdf/QPDFJob.hh @@ -637,7 +637,7 @@ class QPDFJob bool show_filtered_stream_data{false}; bool show_pages{false}; bool show_page_images{false}; - size_t collate{0}; + std::vector collate; bool flatten_rotation{false}; bool list_attachments{false}; std::string attachment_to_show; diff --git a/job.sums b/job.sums index adde95f6..bf044dcb 100644 --- a/job.sums +++ b/job.sums @@ -9,12 +9,12 @@ include/qpdf/auto_job_c_pages.hh b3cc0f21029f6d89efa043dcdbfa183cb59325b6506001c include/qpdf/auto_job_c_uo.hh ae21b69a1efa9333050f4833d465f6daff87e5b38e5106e49bbef5d4132e4ed1 job.yml 4f89fc7b622df897d30d403d8035aa36fc7de8d8c43042c736e0300d904cb05c libqpdf/qpdf/auto_job_decl.hh 9c6f701c29f3f764d620186bed92685a2edf2e4d11e4f4532862c05470cfc4d2 -libqpdf/qpdf/auto_job_help.hh 838f4065f64dc3fbd493510fd21d8ab4e16ee2434592776f44f80cbe3045cb50 +libqpdf/qpdf/auto_job_help.hh ea0d0cdebeb190d305bd5f9bca85a4430dbcfa0881ac9be839216b878765b379 libqpdf/qpdf/auto_job_init.hh b4c2b3724fba61f1206fd3bae81951636852592f67a63ef9539839c2c5995065 libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a9474319fb7c86d92634cc8297 libqpdf/qpdf/auto_job_json_init.hh f5acb9aa103131cb68dec0e12c4d237a6459bdb49b24773c24f0c2724a462b8f libqpdf/qpdf/auto_job_schema.hh b53c006fec2e75b1b73588d242d49a32f7d3db820b1541de106c5d4c27fbb4d9 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 -manual/cli.rst d6d1ca82c936ffeaf137c586f988f80043db4c3b226d26fdf94f19a6005d012e -manual/qpdf.1 10dc52d32a6d8885ce4e4292875ee7fe8e7a826ef3fc28db5671be413bcaacc7 +manual/cli.rst fc8488129c479b6cde9dffbbddb150bc1ffb45bc38b3bff2c5dba4378f0edb67 +manual/qpdf.1 738dc9b732ad4c880d034b99f957077628fde1d0006943aaf813e98f8e2f9635 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index b2f905a1..a7cb3e3a 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -1,10 +1,6 @@ #include -#include -#include -#include #include -#include #include #include @@ -14,13 +10,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -2419,26 +2413,32 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector>& page_hea dh.removePage(page); } - if (m->collate && (parsed_specs.size() > 1)) { + auto n_collate = m->collate.size(); + auto n_specs = parsed_specs.size(); + if (n_collate > 0 && n_specs > 1) { // Collate the pages by selecting one page from each spec in order. When a spec runs out of // pages, stop selecting from it. std::vector new_parsed_specs; - size_t nspecs = parsed_specs.size(); - size_t cur_page = 0; + // Make sure we have a collate value for each spec. We have already checked that a non-empty + // collate has either one value or one value per spec. + for (auto i = n_collate; i < n_specs; ++i) { + m->collate.push_back(m->collate.at(0)); + } + std::vector cur_page(n_specs, 0); bool got_pages = true; while (got_pages) { got_pages = false; - for (size_t i = 0; i < nspecs; ++i) { + for (size_t i = 0; i < n_specs; ++i) { QPDFPageData& page_data = parsed_specs.at(i); - for (size_t j = 0; j < m->collate; ++j) { - if (cur_page + j < page_data.selected_pages.size()) { + for (size_t j = 0; j < m->collate.at(i); ++j) { + if (cur_page.at(i) + j < page_data.selected_pages.size()) { got_pages = true; new_parsed_specs.emplace_back( - page_data, page_data.selected_pages.at(cur_page + j)); + page_data, page_data.selected_pages.at(cur_page.at(i) + j)); } } + cur_page.at(i) += m->collate.at(i); } - cur_page += m->collate; } parsed_specs = new_parsed_specs; } @@ -3019,9 +3019,10 @@ QPDFJob::writeOutfile(QPDF& pdf) try { QUtil::remove_file(backup.c_str()); } catch (QPDFSystemError& e) { - *m->log->getError() << m->message_prefix << ": unable to delete original file (" - << e.what() << ");" << " original file left in " << backup - << ", but the input was successfully replaced\n"; + *m->log->getError() + << m->message_prefix << ": unable to delete original file (" << e.what() << ");" + << " original file left in " << backup + << ", but the input was successfully replaced\n"; } } } diff --git a/libqpdf/QPDFJob_config.cc b/libqpdf/QPDFJob_config.cc index 4798ce9b..a0dc10f6 100644 --- a/libqpdf/QPDFJob_config.cc +++ b/libqpdf/QPDFJob_config.cc @@ -98,8 +98,27 @@ QPDFJob::Config::collate() QPDFJob::Config* QPDFJob::Config::collate(std::string const& parameter) { - auto n = (parameter.empty() ? 1 : QUtil::string_to_uint(parameter.c_str())); - o.m->collate = QIntC::to_size(n); + if (parameter.empty()) { + o.m->collate.push_back(1); + return this; + } + size_t pos = 0; + // Parse a,b,c + while (true) { + auto end = parameter.find(',', pos); + auto n = parameter.substr(pos, end); + if (n.empty()) { + usage("--collate: trailing comma"); + } + o.m->collate.push_back(QIntC::to_size(QUtil::string_to_uint(n.c_str()))); + if (end == std::string::npos) { + break; + } + pos = end + 1; + } + if (o.m->collate.empty()) { + o.m->collate.push_back(1); + } return this; } @@ -932,9 +951,15 @@ QPDFJob::Config::pages() QPDFJob::Config* QPDFJob::PagesConfig::endPages() { - if (this->config->o.m->page_specs.empty()) { + auto n_specs = config->o.m->page_specs.size(); + if (n_specs == 0) { usage("--pages: no page specifications given"); } + auto n_collate = config->o.m->collate.size(); + if (!(n_collate == 0 || n_collate == 1 || n_collate == n_specs)) { + usage("--pages: if --collate has more than one value, it must have one value per page " + "specification"); + } return this->config; } diff --git a/libqpdf/qpdf/auto_job_help.hh b/libqpdf/qpdf/auto_job_help.hh index 4ee0f56e..c83f94b4 100644 --- a/libqpdf/qpdf/auto_job_help.hh +++ b/libqpdf/qpdf/auto_job_help.hh @@ -315,11 +315,13 @@ ap.addOptionHelp("--pages", "modification", "begin page selection", R"(--pages f Run qpdf --help=page-selection for details. )"); -ap.addOptionHelp("--collate", "modification", "collate with --pages", R"(--collate[=n] +ap.addOptionHelp("--collate", "modification", "collate with --pages", R"(--collate[=n[,m,...]] Collate rather than concatenate pages specified with --pages. With a numeric parameter, collate in groups of n. The default -is 1. Run qpdf --help=page-selection for additional details. +is 1. With comma-separated numeric parameters, take n from the +first file, m from the second, etc. Run +qpdf --help=page-selection for additional details. )"); ap.addOptionHelp("--split-pages", "modification", "write pages to separate files", R"(--split-pages[=n] @@ -607,6 +609,8 @@ Run qpdf --help=page-ranges for help with page ranges. Use --collate=n to cause pages to be collated in groups of n pages (default 1) instead of concatenating the input. +Use --collate=i,j,k,... to take i from the first, then j from the +second, then k from the third, then i from the first, etc. Examples: diff --git a/manual/cli.rst b/manual/cli.rst index 592ba6ef..4927205a 100644 --- a/manual/cli.rst +++ b/manual/cli.rst @@ -1403,18 +1403,21 @@ Related Options See also :qpdf:ref:`--split-pages`, :qpdf:ref:`--collate`, :ref:`page-ranges`. -.. qpdf:option:: --collate[=n] +.. qpdf:option:: --collate[=n[,m,...]] .. help: collate with --pages Collate rather than concatenate pages specified with --pages. With a numeric parameter, collate in groups of n. The default - is 1. Run qpdf --help=page-selection for additional details. + is 1. With comma-separated numeric parameters, take n from the + first file, m from the second, etc. Run + qpdf --help=page-selection for additional details. This option causes :command:`qpdf` to collate rather than concatenate pages specified with :qpdf:ref:`--pages`. With a numeric parameter, collate in groups of :samp:`{n}`. The default - is 1. + is 1. With comma-separated numeric parameters, take :samp:`{n}` + from the first file, :samp:`{m}` from the second, etc. Please see :ref:`page-selection` for additional details. @@ -2335,6 +2338,8 @@ Page Selection Use --collate=n to cause pages to be collated in groups of n pages (default 1) instead of concatenating the input. + Use --collate=i,j,k,... to take i from the first, then j from the + second, then k from the third, then i from the first, etc. Examples: @@ -2383,9 +2388,13 @@ Notes: See :ref:`page-ranges` for help on specifying a page range. Use :samp:`--collate={n}` to cause pages to be collated in groups of -:samp:`{n}` pages (default 1) instead of concatenating the input. Note -that the :qpdf:ref:`--collate` appears outside of ``--pages ... --`` -(before ``--pages`` or after ``--``). Pages are pulled from each +:samp:`{n}` pages (default 1) instead of concatenating the input. Use +:samp:`--collate={i},{j},{k},...` to take :samp:`{i}` from the first, +then :samp:`{j}` from the second, then :samp:`{k}` from the third, +then :samp:`{i}` from the first, etc. + +Note that the :qpdf:ref:`--collate` appears outside of ``--pages ... +--`` (before ``--pages`` or after ``--``). Pages are pulled from each document in turn. When a document is out of pages, it is skipped. See examples below. @@ -2481,6 +2490,34 @@ Examples - a.pdf page 5 +- You can specify a multiple numeric parameters to :qpdf:ref:`--collate`. With + :samp:`--collate={i,j,k}`, pull groups of :samp:`{i}` pages from the + first file, then :samp:`{j}` from the second, thenm :samp:`{k}` from + the third, repeating. The number of parameters must equal the number + of groups. For example, if you ran + + :: + + qpdf --collate=2,1,3 --empty --pages a.pdf 1-5 b.pdf 6-4 c.pdf r1-r4 -- out.pdf + + you would get the following pages in this order: + + - a.pdf pages 1 and 2 + + - b.pdf page 6 + + - c.pdf last three pages in reverse order + + - a.pdf pages 3 and 4 + + - b.pdf page 5 + + - c.pdf fourth to last page + + - a.pdf page 5 + + - b.pdf page 4 + - Take pages 1 through 5 from :file:`file1.pdf` and pages 11 through 15 in reverse from :file:`file2.pdf`, taking document-level metadata from :file:`file2.pdf`. diff --git a/manual/qpdf.1 b/manual/qpdf.1 index d758dca3..bf5e9792 100644 --- a/manual/qpdf.1 +++ b/manual/qpdf.1 @@ -414,11 +414,13 @@ Related Options: Run qpdf --help=page-selection for details. .TP .B --collate \-\- collate with --pages ---collate[=n] +--collate[=n[,m,...]] Collate rather than concatenate pages specified with --pages. With a numeric parameter, collate in groups of n. The default -is 1. Run qpdf --help=page-selection for additional details. +is 1. With comma-separated numeric parameters, take n from the +first file, m from the second, etc. Run +qpdf --help=page-selection for additional details. .TP .B --split-pages \-\- write pages to separate files --split-pages[=n] @@ -737,6 +739,8 @@ Run qpdf --help=page-ranges for help with page ranges. Use --collate=n to cause pages to be collated in groups of n pages (default 1) instead of concatenating the input. +Use --collate=i,j,k,... to take i from the first, then j from the +second, then k from the third, then i from the first, etc. Examples: diff --git a/manual/release-notes.rst b/manual/release-notes.rst index 17dc116d..72b1aad6 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -50,6 +50,10 @@ Planned changes for future 12.x (subject to change): of pages within a page range. See :ref:`page-ranges` for details. + - Support comma-separated numeric values with + :qpdf:ref:`--collate` to select different numbers of pages from + different groups. + 11.7.0: December 24, 2023 - Bug fixes: diff --git a/qpdf/qtest/arg-parsing.test b/qpdf/qtest/arg-parsing.test index aca8c7ef..82594674 100644 --- a/qpdf/qtest/arg-parsing.test +++ b/qpdf/qtest/arg-parsing.test @@ -15,7 +15,7 @@ cleanup(); my $td = new TestDriver('arg-parsing'); -my $n_tests = 22; +my $n_tests = 23; $td->runtest("required argument", {$td->COMMAND => "qpdf --password minimal.pdf"}, @@ -94,6 +94,14 @@ $td->runtest("v2-only qpdf json-key", {$td->REGEXP => ".*\"qpdf\" is only valid for json version > 1.*", $td->EXIT_STATUS => 2}, $td->NORMALIZE_NEWLINES); +$td->runtest("wrong number of collate args", + {$td->COMMAND => + "qpdf --collate=2,3 collate-odd.pdf" . + " --pages . minimal.pdf collate-even.pdf -- a.pdf"}, + {$td->REGEXP => ".*--collate has more than one value.*", + $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); + # Ignoring -- at the top level was never intended but turned out to # have been there for a long time so that people relied on it. It is # intentionally not documented. diff --git a/qpdf/qtest/collate.test b/qpdf/qtest/collate.test index fbbea641..fb296e29 100644 --- a/qpdf/qtest/collate.test +++ b/qpdf/qtest/collate.test @@ -17,9 +17,11 @@ my $td = new TestDriver('collate'); my @collate = ( ["", "three-files", "collate-odd", "collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"], - [1, "three-files", "collate-odd", + ["1", "three-files", "collate-odd", "collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"], - [2, "three-files-2", "collate-odd", + ["2", "three-files-2", "collate-odd", + "collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"], + ["2,3,4", "three-files-2,3,4", "collate-odd", "collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"], ); my $n_tests = 2 * scalar(@collate); @@ -28,7 +30,7 @@ foreach my $d (@collate) { my ($n, $description, $first, $args) = @$d; my $collate = '--collate'; - if ($n) + if ($n ne "") { $collate .= "=$n"; } diff --git a/qpdf/qtest/qpdf/three-files-2,3,4-collate-out.pdf b/qpdf/qtest/qpdf/three-files-2,3,4-collate-out.pdf new file mode 100644 index 00000000..8be9c056 --- /dev/null +++ b/qpdf/qtest/qpdf/three-files-2,3,4-collate-out.pdf @@ -0,0 +1,988 @@ +%PDF-1.3 +%¿÷¢þ +%QDF-1.0 + +%% Original object ID: 1 0 +1 0 obj +<< + /Outlines 2 0 R + /PageLabels << + /Nums [ + 0 + << + /P () + /St 1 + >> + 1 + << + /S /r + /St 1 + >> + 2 + << + /St 3 + >> + 3 + << + /S /D + /St 3 + >> + 4 + << + /P () + /St 1 + >> + 5 + << + /S /r + /St 6 + >> + 6 + << + /P () + /St 1 + >> + 7 + << + /S /r + /St 3 + >> + 8 + << + /S /r + /St 5 + >> + 9 + << + /S /r + /St 4 + >> + 10 + << + /S /r + /St 2 + >> + 11 + << + /P () + /St 2 + >> + 12 + << + /P () + /St 2 + >> + ] + >> + /PageMode /UseOutlines + /Pages 3 0 R + /Type /Catalog +>> +endobj + +%% Original object ID: 2 0 +2 0 obj +<< + /Count 6 + /First 4 0 R + /Last 5 0 R + /Type /Outlines +>> +endobj + +%% Original object ID: 3 0 +3 0 obj +<< + /Count 13 + /Kids [ + 6 0 R + 7 0 R + 8 0 R + 9 0 R + 10 0 R + 11 0 R + 12 0 R + 13 0 R + 14 0 R + 15 0 R + 16 0 R + 17 0 R + 18 0 R + ] + /Type /Pages +>> +endobj + +%% Original object ID: 4 0 +4 0 obj +<< + /Count 4 + /Dest [ + null + /XYZ + null + null + null + ] + /First 19 0 R + /Last 20 0 R + /Next 5 0 R + /Parent 2 0 R + /Title (Isís 1 -> 5: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 5 0 +5 0 obj +<< + /Dest [ + null + /XYZ + 66 + 756 + 3 + ] + /Parent 2 0 R + /Prev 4 0 R + /Title (Trepak 2 -> 15: /XYZ 66 756 3) + /Type /Outline +>> +endobj + +%% Page 1 +%% Original object ID: 6 0 +6 0 obj +<< + /Contents 21 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 2 +%% Original object ID: 7 0 +7 0 obj +<< + /Contents 25 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 3 +%% Original object ID: 47 0 +8 0 obj +<< + /Contents 27 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 29 0 R + >> + /ProcSet 30 0 R + >> + /Type /Page +>> +endobj + +%% Page 4 +%% Original object ID: 51 0 +9 0 obj +<< + /Contents 31 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 5 +%% Original object ID: 55 0 +10 0 obj +<< + /Contents 35 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 6 +%% Original object ID: 57 0 +11 0 obj +<< + /Contents 37 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 7 +%% Original object ID: 59 0 +12 0 obj +<< + /Contents 39 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 8 +%% Original object ID: 8 0 +13 0 obj +<< + /Contents 41 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 9 +%% Original object ID: 9 0 +14 0 obj +<< + /Contents 43 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 10 +%% Original object ID: 61 0 +15 0 obj +<< + /Contents 45 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 11 +%% Original object ID: 63 0 +16 0 obj +<< + /Contents 47 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 12 +%% Original object ID: 65 0 +17 0 obj +<< + /Contents 49 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 13 +%% Original object ID: 10 0 +18 0 obj +<< + /Contents 51 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Original object ID: 21 0 +19 0 obj +<< + /Count -3 + /Dest [ + null + /Fit + ] + /First 53 0 R + /Last 54 0 R + /Next 20 0 R + /Parent 4 0 R + /Title (Amanda 1.1 -> 11: /Fit) + /Type /Outline +>> +endobj + +%% Original object ID: 22 0 +20 0 obj +<< + /Count 2 + /Dest [ + null + /FitH + 792 + ] + /First 55 0 R + /Last 56 0 R + /Parent 4 0 R + /Prev 19 0 R + /Title + /Type /Outline +>> +endobj + +%% Contents for page 1 +%% Original object ID: 23 0 +21 0 obj +<< + /Length 22 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 0) Tj +ET +endstream +endobj + +22 0 obj +46 +endobj + +%% Original object ID: 24 0 +23 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +%% Original object ID: 25 0 +24 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 2 +%% Original object ID: 26 0 +25 0 obj +<< + /Length 26 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 2) Tj +ET +endstream +endobj + +26 0 obj +46 +endobj + +%% Contents for page 3 +%% Original object ID: 48 0 +27 0 obj +<< + /Length 28 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj + +28 0 obj +44 +endobj + +%% Original object ID: 49 0 +29 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +%% Original object ID: 50 0 +30 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 4 +%% Original object ID: 52 0 +31 0 obj +<< + /Length 32 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 13) Tj +ET +endstream +endobj + +32 0 obj +47 +endobj + +%% Original object ID: 53 0 +33 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +%% Original object ID: 54 0 +34 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 5 +%% Original object ID: 56 0 +35 0 obj +<< + /Length 36 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 11) Tj +ET +endstream +endobj + +36 0 obj +47 +endobj + +%% Contents for page 6 +%% Original object ID: 58 0 +37 0 obj +<< + /Length 38 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 9) Tj +ET +endstream +endobj + +38 0 obj +46 +endobj + +%% Contents for page 7 +%% Original object ID: 60 0 +39 0 obj +<< + /Length 40 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 7) Tj +ET +endstream +endobj + +40 0 obj +46 +endobj + +%% Contents for page 8 +%% Original object ID: 27 0 +41 0 obj +<< + /Length 42 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 4) Tj +ET +endstream +endobj + +42 0 obj +46 +endobj + +%% Contents for page 9 +%% Original object ID: 28 0 +43 0 obj +<< + /Length 44 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 6) Tj +ET +endstream +endobj + +44 0 obj +46 +endobj + +%% Contents for page 10 +%% Original object ID: 62 0 +45 0 obj +<< + /Length 46 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 5) Tj +ET +endstream +endobj + +46 0 obj +46 +endobj + +%% Contents for page 11 +%% Original object ID: 64 0 +47 0 obj +<< + /Length 48 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 3) Tj +ET +endstream +endobj + +48 0 obj +46 +endobj + +%% Contents for page 12 +%% Original object ID: 66 0 +49 0 obj +<< + /Length 50 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 1) Tj +ET +endstream +endobj + +50 0 obj +46 +endobj + +%% Contents for page 13 +%% Original object ID: 29 0 +51 0 obj +<< + /Length 52 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 8) Tj +ET +endstream +endobj + +52 0 obj +46 +endobj + +%% Original object ID: 40 0 +53 0 obj +<< + /Count -2 + /Dest [ + 57 0 R + /FitV + 100 + ] + /First 58 0 R + /Last 59 0 R + /Next 54 0 R + /Parent 19 0 R + /Title (Isosicle 1.1.1 -> 12: /FitV 100) + /Type /Outline +>> +endobj + +%% Original object ID: 41 0 +54 0 obj +<< + /Count 1 + /Dest [ + 57 0 R + /XYZ + null + null + null + ] + /First 60 0 R + /Last 60 0 R + /Parent 19 0 R + /Prev 53 0 R + /Title (Isosicle 1.1.2 -> 12: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 42 0 +55 0 obj +<< + /Dest [ + null + /FitR + 66 + 714 + 180 + 770 + ] + /Next 56 0 R + /Parent 20 0 R + /Title (Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770) + /Type /Outline +>> +endobj + +%% Original object ID: 43 0 +56 0 obj +<< + /Dest [ + 6 0 R + /XYZ + null + null + null + ] + /Parent 20 0 R + /Prev 55 0 R + /Title (Trepsicle 1.2.2 -> 0: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 12 0 +57 0 obj +null +endobj + +%% Original object ID: 44 0 +58 0 obj +<< + /Dest [ + 61 0 R + /XYZ + null + null + null + ] + /Next 59 0 R + /Parent 53 0 R + /Title (Isosicle 1.1.1.1 -> 18: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 45 0 +59 0 obj +<< + /Dest [ + null + /XYZ + null + null + null + ] + /Parent 53 0 R + /Prev 58 0 R + /Title (Isosicle 1.1.1.2 -> 19: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 46 0 +60 0 obj +<< + /Dest [ + 62 0 R + /XYZ + null + null + null + ] + /Parent 54 0 R + /Title (Isosicle 1.1.2.1 -> 22: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 15 0 +61 0 obj +null +endobj + +%% Original object ID: 17 0 +62 0 obj +null +endobj + +xref +0 63 +0000000000 65535 f +0000000052 00000 n +0000000907 00000 n +0000001014 00000 n +0000001243 00000 n +0000001484 00000 n +0000001684 00000 n +0000001916 00000 n +0000002149 00000 n +0000002382 00000 n +0000002615 00000 n +0000002849 00000 n +0000003083 00000 n +0000003316 00000 n +0000003549 00000 n +0000003784 00000 n +0000004019 00000 n +0000004254 00000 n +0000004489 00000 n +0000004713 00000 n +0000004921 00000 n +0000005278 00000 n +0000005381 00000 n +0000005429 00000 n +0000005576 00000 n +0000005663 00000 n +0000005766 00000 n +0000005837 00000 n +0000005938 00000 n +0000005986 00000 n +0000006133 00000 n +0000006220 00000 n +0000006324 00000 n +0000006372 00000 n +0000006519 00000 n +0000006606 00000 n +0000006710 00000 n +0000006781 00000 n +0000006884 00000 n +0000006955 00000 n +0000007058 00000 n +0000007129 00000 n +0000007232 00000 n +0000007303 00000 n +0000007406 00000 n +0000007478 00000 n +0000007581 00000 n +0000007653 00000 n +0000007756 00000 n +0000007828 00000 n +0000007931 00000 n +0000008003 00000 n +0000008106 00000 n +0000008154 00000 n +0000008383 00000 n +0000008639 00000 n +0000008859 00000 n +0000009072 00000 n +0000009122 00000 n +0000009338 00000 n +0000009552 00000 n +0000009753 00000 n +0000009803 00000 n +trailer << + /Root 1 0 R + /Size 63 + /ID [<31415926535897932384626433832795>] +>> +startxref +9825 +%%EOF