From 5a02471bb15299dec00c73f6a08a2b4e73061775 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 21 Jul 2012 20:24:02 -0400 Subject: [PATCH] Command-line page merging and splitting Implement --pages ... -- option for qpdf. Update TODO with remaining things to document. --- ChangeLog | 3 + TODO | 52 +- qpdf/qpdf.cc | 398 ++++- qpdf/qtest/qpdf.test | 84 + qpdf/qtest/qpdf/20-pages.pdf | Bin 0 -> 7343 bytes qpdf/qtest/qpdf/merge-three-files-1.pdf | Bin 0 -> 8495 bytes qpdf/qtest/qpdf/merge-three-files-2.pdf | Bin 0 -> 6036 bytes qpdf/qtest/qpdf/page-labels-and-outlines.pdf | 1503 ++++++++++++++++++ 8 files changed, 2008 insertions(+), 32 deletions(-) create mode 100644 qpdf/qtest/qpdf/20-pages.pdf create mode 100644 qpdf/qtest/qpdf/merge-three-files-1.pdf create mode 100644 qpdf/qtest/qpdf/merge-three-files-2.pdf create mode 100644 qpdf/qtest/qpdf/page-labels-and-outlines.pdf diff --git a/ChangeLog b/ChangeLog index 2bb4564a..24265a54 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2012-07-21 Jay Berkenbilt + * Add --pages command-line option to qpdf to enable page-based + merging and splitting. + * Add new method QPDFObjectHandle::replaceDict to replace a stream's dictionary. Use with caution; see comments in QPDFObjectHandle.hh. diff --git a/TODO b/TODO index 42d5e7a6..de99b5cd 100644 --- a/TODO +++ b/TODO @@ -24,46 +24,40 @@ Next can only be used by one thread at a time, but multiple threads can simultaneously use separate objects. - * Write some documentation about the design of copyForeignObject. - * Mention QPDFObjectHandle::parse in the documentation. - * copyForeignObject still to do: + * Manual: empty --empty as an input file name option - - qpdf command + * copyForeignObject, merge/split documentation: - Command line could be something like (but not exactly) + document details of --pages option in manual. Include nuances of + range parsing, such as backward ranges and "z". Discuss + implications of using --empty vs. using one of the source files as + the original file including Outlines (which basically work) and + page labels (which don't). Also mention trick of specifying two + different paths to the same file get duplication. - --pages [ --new ] { file [password] numeric-range ... } ... -- + Command line is - The first file referenced would be the one whose other data would - be preserved (like trailer, info, encryption, outlines, etc.). - --new as first file would just use an empty file as the starting - point. Be explicit about whether outlines, etc., are handled. - They are not handled initially. + --pages infile [ --password=pwd ] range ... -- - Example: to grab pages 1-5 from file1 and 11-15 from file2 + The regular input referenced would be the one whose other data + would be preserved (like trailer, info, encryption, outlines, + etc.). It can be but doesn't have to be one of the files selected. - --pages file1.pdf 1-5 file2.pdf 11-15 -- + Example: to grab pages 1-5 from file1 and 11-15 from file2 in + reverse: - To implement this, we would remove all pages from file1 except - pages 1 through 5. Then we would take pages 11 through 15 from - file2, copy them to the file, and add them as pages. + qpdf file1.pdf out.pdf --pages file1.pdf 1-5 file2.pdf 15-11 -- - (Look at ~/source/examples/perl/numrange.pl for numeric range - parsing code.) + Use comments in qpdf.cc to guide internals documentation when + discussing implementation. Also see copyForeignObject as a source + for documentation. - - document that makeIndirectObject doesn't handle foreign objects - automatically because copying a foreign object is a big enough - deal that it should be explicit. However addPages* does handle - foreign page objects automatically. - - - Test /Outlines and see whether there's any point in handling - them in the API. Maybe just copying them over works. What - about command line tool? Also think about page labels. - - - Tests through qpdf command line: copy pages from multiple PDFs - starting with one PDF and also starting with empty. + Document that makeIndirectObject doesn't handle foreign objects + automatically because copying a foreign object is a big enough deal + that it should be explicit. However addPages* does handle foreign + page objects automatically. * Document --copy-encryption and --encryption-file-password in manual. Mention that the first half of /ID as well as all the diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index cf99ca44..73f4cbd0 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -3,10 +3,12 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -18,6 +20,31 @@ static int const EXIT_WARNING = 3; static char const* whoami = 0; +struct PageSpec +{ + PageSpec(std::string const& filename, + char const* password, + char const* range) : + filename(filename), + password(password), + range(range) + { + } + + std::string filename; + char const* password; + char const* range; +}; + +struct QPDFPageData +{ + QPDFPageData(QPDF* qpdf, char const* range); + + QPDF* qpdf; + std::vector orig_pages; + std::vector selected_pages; +}; + // Note: let's not be too noisy about documenting the fact that this // software purposely fails to enforce the distinction between user // and owner passwords. A user password is sufficient to gain full @@ -29,7 +56,7 @@ static char const* whoami = 0; static char const* help = "\ \n\ -Usage: qpdf [ options ] infilename [ outfilename ]\n\ +Usage: qpdf [ options ] { infilename | --empty } [ outfilename ]\n\ \n\ An option summary appears below. Please see the documentation for details.\n\ \n\ @@ -57,6 +84,7 @@ parameters will be copied, including both user and owner passwords, even\n\ if the user password is used to open the other file. This works even if\n\ the owner password is not known.\n\ \n\ +\n\ Encryption Options\n\ ------------------\n\ \n\ @@ -113,6 +141,40 @@ to be used even if not otherwise needed. This option is primarily useful\n\ for testing qpdf and has no other practical use.\n\ \n\ \n\ +Page Selection Options\n\ +----------------------\n\ +\n\ +These options allow pages to be selected from one or more PDF files.\n\ +Whatever file is given as the primary input file is used as the\n\ +starting point, but its pages are replaced with pages as specified.\n\ +\n\ +--pages file [ --password=password ] page-range ... --\n\ +\n\ +For each file that pages should be taken from, specify the file, a\n\ +password needed to open the file (if needed), and a page range. The\n\ +password needs to be given only once per file. If the input file file\n\ +requires a password, that password must be specified outside the\n\ +--pages option and does not need to be repeated. The same file can be\n\ +repeated multiple times. All non-page data (info, outlines, page numbers,\n\ +etc. are taken from the primary input file. To discard this, use --empty\n\ +as the primary input.\n\ +\n\ +It is not presently possible to specify the same page from the same\n\ +file directly more than once, but you can make this work by specifying\n\ +two different paths to the same file (such as by putting ./ somewhere\n\ +in the path).\n\ +\n\ +The page range is a set of numbers separated by commas, ranges of\n\ +numbers separated dashes, or combinations of those. The character\n\ +\"z\" represents the last page. Pages can appear in any order. Ranges\n\ +can appear with a high number followed by a low number, which causes the\n\ +pages to appear in reverse. Repeating a number will cause an error, but\n\ +the manual discusses a workaround should you really want to include the\n\ +same page twice.\n\ +\n\ +See the manual for examples and a discussion of additional subtleties.\n\ +\n\ +\n\ Advanced Transformation Options\n\ -------------------------------\n\ \n\ @@ -275,6 +337,146 @@ static void show_encryption(QPDF& pdf) } } +static std::vector parse_numrange(char const* range, int max) +{ + std::vector result; + char const* p = range; + try + { + std::vector work; + static int const comma = -1; + static int const dash = -2; + + enum { st_top, + st_in_number, + st_after_number } state = st_top; + bool last_separator_was_dash = false; + int cur_number = 0; + while (*p) + { + char ch = *p; + if (isdigit(ch)) + { + if (! ((state == st_top) || (state == st_in_number))) + { + throw std::runtime_error("digit not expected"); + } + state = st_in_number; + cur_number *= 10; + cur_number += (ch - '0'); + } + else if (ch == 'z') + { + // z represents max + if (! (state == st_top)) + { + throw std::runtime_error("z not expected"); + } + state = st_after_number; + cur_number = max; + } + else if ((ch == ',') || (ch == '-')) + { + if (! ((state == st_in_number) || (state == st_after_number))) + { + throw std::runtime_error("unexpected separator"); + } + work.push_back(cur_number); + cur_number = 0; + if (ch == ',') + { + state = st_top; + last_separator_was_dash = false; + work.push_back(comma); + } + else if (ch == '-') + { + if (last_separator_was_dash) + { + throw std::runtime_error("unexpected dash"); + } + state = st_top; + last_separator_was_dash = true; + work.push_back(dash); + } + } + else + { + throw std::runtime_error("unexpected character"); + } + ++p; + } + if ((state == st_in_number) || (state == st_after_number)) + { + work.push_back(cur_number); + } + else + { + throw std::runtime_error("number expected"); + } + + p = 0; + for (size_t i = 0; i < work.size(); i += 2) + { + int num = work[i]; + if ((num < 1) || (num > max)) + { + throw std::runtime_error( + "number " + QUtil::int_to_string(num) + " out of range"); + } + if (i == 0) + { + result.push_back(work[i]); + } + else + { + int separator = work[i-1]; + if (separator == comma) + { + result.push_back(num); + } + else if (separator == dash) + { + int lastnum = result.back(); + if (num > lastnum) + { + for (int j = lastnum + 1; j <= num; ++j) + { + result.push_back(j); + } + } + else + { + for (int j = lastnum - 1; j >= num; --j) + { + result.push_back(j); + } + } + } + else + { + throw std::logic_error( + "INTERNAL ERROR parsing numeric range"); + } + } + } + } + catch (std::runtime_error e) + { + if (p) + { + usage("error at * in numeric range " + + std::string(range, p - range) + "*" + p + ": " + e.what()); + } + else + { + usage("error in numeric range " + + std::string(range) + ": " + e.what()); + } + } + return result; +} + static void parse_encrypt_options( int argc, char* argv[], int& cur_arg, @@ -578,6 +780,66 @@ parse_encrypt_options( } } +static std::vector +parse_pages_options( + int argc, char* argv[], int& cur_arg) +{ + std::vector result; + while (1) + { + if ((cur_arg < argc) && (strcmp(argv[cur_arg], "--") == 0)) + { + break; + } + if (cur_arg + 2 >= argc) + { + usage("insufficient arguments to --pages"); + } + char* file = argv[cur_arg++]; + char* password = 0; + char* range = argv[cur_arg++]; + if (strncmp(range, "--password=", 11) == 0) + { + // Oh, that's the password, not the range + if (cur_arg + 1 >= argc) + { + usage("insufficient arguments to --pages"); + } + password = range + 11; + range = argv[cur_arg++]; + } + + result.push_back(PageSpec(file, password, range)); + } + return result; +} + +static void test_numrange(char const* range) +{ + if (range == 0) + { + std::cout << "null" << std::endl; + } + else + { + std::vector result = parse_numrange(range, 15); + std::cout << "numeric range " << range << " ->"; + for (std::vector::iterator iter = result.begin(); + iter != result.end(); ++iter) + { + std::cout << " " << *iter; + } + std::cout << std::endl; + } +} + +QPDFPageData::QPDFPageData(QPDF* qpdf, char const* range) : + qpdf(qpdf), + orig_pages(qpdf->getAllPages()) +{ + this->selected_pages = parse_numrange(range, this->orig_pages.size()); +} + int main(int argc, char* argv[]) { whoami = QUtil::getWhoami(argv[0]); @@ -673,6 +935,8 @@ int main(int argc, char* argv[]) bool show_page_images = false; bool check = false; + std::vector page_specs; + bool require_outfile = true; char const* infilename = 0; char const* outfilename = 0; @@ -694,7 +958,14 @@ int main(int argc, char* argv[]) *parameter++ = 0; } - if (strcmp(arg, "password") == 0) + // Arguments that start with space are undocumented and + // are for use by the test suite. + if (strcmp(arg, " test-numrange") == 0) + { + test_numrange(parameter); + exit(0); + } + else if (strcmp(arg, "password") == 0) { if (parameter == 0) { @@ -702,6 +973,10 @@ int main(int argc, char* argv[]) } password = parameter; } + else if (strcmp(arg, "empty") == 0) + { + infilename = ""; + } else if (strcmp(arg, "linearize") == 0) { linearize = true; @@ -745,6 +1020,14 @@ int main(int argc, char* argv[]) } encryption_file_password = parameter; } + else if (strcmp(arg, "pages") == 0) + { + page_specs = parse_pages_options(argc, argv, ++i); + if (page_specs.empty()) + { + usage("--pages: no page specifications given"); + } + } else if (strcmp(arg, "stream-data") == 0) { if (parameter == 0) @@ -950,7 +1233,14 @@ int main(int argc, char* argv[]) { pdf.setAttemptRecovery(false); } - pdf.processFile(infilename, password); + if (strcmp(infilename, "") == 0) + { + pdf.emptyPDF(); + } + else + { + pdf.processFile(infilename, password); + } if (outfilename == 0) { if (show_encryption) @@ -1126,6 +1416,108 @@ int main(int argc, char* argv[]) } else { + std::vector > page_heap; + if (! page_specs.empty()) + { + // Parse all page specifications and translate them + // into lists of actual pages. + + // Create a QPDF object for each file that we may take + // pages from. + std::map page_spec_qpdfs; + page_spec_qpdfs[infilename] = &pdf; + std::vector parsed_specs; + for (std::vector::iterator iter = page_specs.begin(); + iter != page_specs.end(); ++iter) + { + PageSpec& page_spec = *iter; + if (page_spec_qpdfs.count(page_spec.filename) == 0) + { + // Open the PDF file and store the QPDF + // object. Throw a PointerHolder to the qpdf + // into a heap so that it survives through + // writing the output but gets cleaned up + // automatically at the end. Do not + // canonicalize the file name. Using two + // different paths to refer to the same file + // is a document workaround for duplicating a + // page. If you are using this an example of + // how to do this with the API, you can just + // create two different QPDF objects to the + // same underlying file with the same path to + // achieve the same affect. + PointerHolder qpdf_ph = new QPDF(); + page_heap.push_back(qpdf_ph); + QPDF* qpdf = qpdf_ph.getPointer(); + qpdf->processFile( + page_spec.filename.c_str(), page_spec.password); + page_spec_qpdfs[page_spec.filename] = qpdf; + } + + // Read original pages from the PDF, and parse the + // page range associated with this occurrence of + // the file. + parsed_specs.push_back( + QPDFPageData(page_spec_qpdfs[page_spec.filename], + page_spec.range)); + } + + // Clear all pages out of the primary QPDF's pages + // tree but leave the objects in place in the file so + // they can be re-added without changing their object + // numbers. This enables other things in the original + // file, such as outlines, to continue to work. + std::vector orig_pages = pdf.getAllPages(); + for (std::vector::iterator iter = + orig_pages.begin(); + iter != orig_pages.end(); ++iter) + { + pdf.removePage(*iter); + } + + // Add all the pages from all the files in the order + // specified. Keep track of any pages from the + // original file that we are selecting. + std::set selected_from_orig; + for (std::vector::iterator iter = + parsed_specs.begin(); + iter != parsed_specs.end(); ++iter) + { + QPDFPageData& page_data = *iter; + for (std::vector::iterator pageno_iter = + page_data.selected_pages.begin(); + pageno_iter != page_data.selected_pages.end(); + ++pageno_iter) + { + // Pages are specified from 1 but numbered + // from 0 in the vector + int pageno = *pageno_iter - 1; + pdf.addPage(page_data.orig_pages[pageno], false); + if (page_data.qpdf == &pdf) + { + // This is a page from the original file. + // Keep track of the fact that we are + // using it. + selected_from_orig.insert(pageno); + } + } + } + + // Delete page objects for unused page in primary. + // This prevents those objects from being preserved by + // being referred to from other places, such as the + // outlines dictionary. + for (int pageno = 0; pageno < (int)orig_pages.size(); ++pageno) + { + if (selected_from_orig.count(pageno) == 0) + { + pdf.replaceObject(orig_pages[pageno].getObjectID(), + orig_pages[pageno].getGeneration(), + QPDFObjectHandle::newNull()); + } + } + } + if (strcmp(outfilename, "-") == 0) { outfilename = 0; diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 02a90736..d9cd6f06 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -377,6 +377,90 @@ $td->runtest("parse objects from string", show_ntests(); # ---------- +$td->notify("--- Numeric range parsing tests ---"); +my @nrange_tests = ( + [",5", + "qpdf: error at * in numeric range *,5: unexpected separator", + 2], + ["4,,5", + "qpdf: error at * in numeric range 4,*,5: unexpected separator", + 2], + ["4,5,", + "qpdf: error at * in numeric range 4,5,*: number expected", + 2], + ["z1,", + "qpdf: error at * in numeric range z*1,: digit not expected", + 2], + ["1z,", + "qpdf: error at * in numeric range 1*z,: z not expected", + 2], + ["1-5?", + "qpdf: error at * in numeric range 1-5*?: unexpected character", + 2], + ["1-30", + "qpdf: error in numeric range 1-30: number 30 out of range", + 2], + ["1-10,0,5", + "qpdf: error in numeric range 1-10,0,5: number 0 out of range", + 2], + ["1-10,1234,5", + "qpdf: error in numeric range 1-10,1234,5: number 1234 out of range", + 2], + ["1,3,5-10,z-13,13,9,z,2", + "numeric range 1,3,5-10,z-13,13,9,z,2" . + " -> 1 3 5 6 7 8 9 10 15 14 13 13 9 15 2", + 0], + ); +$n_tests += scalar(@nrange_tests); +foreach my $d (@nrange_tests) +{ + my ($range, $output, $status) = @$d; + $td->runtest("numeric range $range", + {$td->COMMAND => ['qpdf', '-- test-numrange=' . $range], + $td->FILTER => "grep 'numeric range'"}, + {$td->STRING => $output . "\n", $td->EXIT_STATUS => $status}, + $td->NORMALIZE_NEWLINES); +} + +# ---------- +$td->notify("--- Merging and Splitting ---"); +$n_tests += 4; + +# Select pages from the same file multiple times including selecting +# twice from an encrypted file and specifying the password only the +# first time. The file 20-pages.pdf is specified with two different +# paths to duplicate a page. +my $pages_options = "--pages page-labels-and-outlines.pdf 1,3,5-7,z" . + " 20-pages.pdf --password=user z-15" . + " page-labels-and-outlines.pdf 12" . + " 20-pages.pdf 10" . + " ./20-pages.pdf --password=owner 10" . + " minimal.pdf 1 --"; + +$td->runtest("merge three files", + {$td->COMMAND => "qpdf page-labels-and-outlines.pdf a.pdf" . + " $pages_options --static-id"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}); +# Manually verified about this file: make sure that outline entries +# that pointed to pages that were preserved still work in the copy, +# and verify that all pages are as expected. page-labels-and-outlines +# as well as 20-pages have text on page n (from 1) that shows its page +# position from 0, so page 1 says it's page 0. +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "merge-three-files-1.pdf"}); +# Select the same pages but add them to an empty file +$td->runtest("merge three files", + {$td->COMMAND => "qpdf --empty a.pdf" . + " $pages_options --static-id"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}); +# Manually verified about this file: it has the same pages but does +# not contain outlines, page labels, or other things from the original +# file. +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "merge-three-files-2.pdf"}); +# ---------- $td->notify("--- PDF From Scratch ---"); $n_tests += 2; diff --git a/qpdf/qtest/qpdf/20-pages.pdf b/qpdf/qtest/qpdf/20-pages.pdf new file mode 100644 index 0000000000000000000000000000000000000000..616ac93049c7a805cc8b0de0d01bca606eb5c710 GIT binary patch literal 7343 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 literal 0 HcmV?d00001 diff --git a/qpdf/qtest/qpdf/merge-three-files-1.pdf b/qpdf/qtest/qpdf/merge-three-files-1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..709f21eddac3ddabec4b24df17c871db89f2f882 GIT binary patch literal 8495 zcmc&)&2QX96fdnJ1(QlhAih-4P!XvWBs=37f1wbyO}c96SDK_P5LMu0opkADx2&BK zIH5{CB2^qXaX>xr2UHx9xb%>UB9+hHkjjY*w@TcAHy^gg9(QTFTM}u#yW^Sne)Hzd zZ-4Wg(b?(x7#XM5=(m4e``aQ2qt4R%*2Dy=om@{^&2|(c8ye?OZ8ltuj)hB6D@OeG ziS@M@Ey6ASWftw*k0vLfEB`i+YF)S{i10SA&?6}JaCr|x3SCHMWm;IVMIR>}C>!NK zX)W_E!GL?&2MlpcC3D2ZP9Sd3)TBh~V9{Da=rG=Tj$cFwj&~Xns-2FbEIy(+MpTAr z;iHRie=1DER%aDPSy8*eL&~F=hIXp6-cFEgZ(F_4Q zN1F}47_J!XiK{QJfw&SZ-}v&*Aqb0jlYoizPNZWnChkcaL>eK|iY=|!(#knhlT+n+ z5o;6W_KFA@EJy@%TSU@O?X9=pLGATcOS^zM84Or1k!~UywhHQrXd?^FY}kz;daBz( zGigO=-%R{x4CyvDiJaF_h5h|0S`|_aFQ0gdSHTq6gca9Cp5wyW0=}Z-*=}?xT{#6} zp&MNcF90%tK~fy4g&qia%u@tYopu7#gI%Yx;&{|(hKD*EVy|K3!s>Z}t>IB2nv3Gj zdUqL?f^We(n2{d{vgOi>1nzd0=OYN7TxQ;sFQOI`zWgR4nH05cLBuoBL?rWbdl2zl zHW8@|-A)DZ zA83_iPq&p?E7_h6j9UAMmXKuzX|RD&ER)RmFnlJYq6tkYmi|eDNoLfcv0Us_Qml}d zIV7&b$7rTcmEZ*_%6U4K=ZZ;$B4ujt3PqaAQ>6?QPb)Qea4l>%LPW-eEF}bVDVXgc z1BJCBG&@5kB`FvT?hh({>ZR8~uH6D!fm3dRn_*d_3a;KeZZ0*q~p z@rs9ONHJX^@M^$|!3*v|ySPtbj16Nbbmsl-27HF^{4>8t!7dHeJ@CU^un*%s@WFi5 z4{T9rqjn}jO>Hhja2k?hQF}ExkKmXFfovraoZ9NGFo~w4Wqu}uGg6#%qj1gI_#(FM z&duKbkS<{R)bPx%>)(I+$>xEVUm4o`b$GKj^V-d``1Zo^&ELDb@9y03+w<0Qa7I&O z)6<&%n{p_OBO$13N21oHC}}Q-sCKvwUYqSzR6E;jA8g0Xyx~N+21md;fz#{yQX%v3E9C>C+dO5;Kw`$uUapdG0S4-jWI*7i* zPKo`+P7lw_V{hl)U01F?J+%9uYq##*`sT)-E0=HFTNs);di=`O*M{yqIlg1>2P2W` zT-Uq#E?a1>Me*2N2MDz1K6{|uMRnmi@CiPVPw@Om!6Y{A%dfvWbma5P7lxnRvG@2$X!_QPtEcaW$?2!^iBFHbXF9JU zuYqi3rfuZ|)|-ZBp+-~o>N)MLhkPjC$tzs!d zSvJ`Vn$BtpnhJn9MXflUqLCp}XO$u}vB>tgh#B5$$V7gnt6d0LH7xlcLFHk3OzN?c zY4Z772Bs6@0{|;w5%EelPgRP+T8hUE!IpvPY`Fzs>fBp}MP-$}<%HkpMk^LZ4z=Lm zsQ!@aIMhKaC~M=7ruZLfXKmDlS(Mrsuc4IMoWLCGd9XC4egQVx2_qtuTEWV3zxN*zD)SbFNZ9ih2FM z=z|jAp>iALmFrSJ=E8O_wR!w^wAALi20j{iMy#N8V2ovi7YK^>n9YC4N^R7{%$RSi zFVrcFxG>LL59U|21@Kh4BG0pv4Pp3Ej`cx1mHI(B$QbJp-AB?5n=PpN@aLOzoeux^0--JD zn;%EOA*ePpjTR>;VZ;e+7f8epynx!&XRhr6JNVT00%!8Sd+;YTaMBICi6AJ?4wyAM JdiZ4B`WKhcKSKZj literal 0 HcmV?d00001 diff --git a/qpdf/qtest/qpdf/merge-three-files-2.pdf b/qpdf/qtest/qpdf/merge-three-files-2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c8b717e6b2561de3070102b71b3ed35d26d0f8d7 GIT binary patch literal 6036 zcmc&&OK%%h6b`K{nZcEiK)h7YB_b=@&fLemq+pfAwHiujYMXWuA(+I|)Zom3J%PXq zRmu{nV!?_9>Vh9ou|#6iMJkF^p50X{D}F#Fb}jdj@m$ZD23k+C7vtpm-t&Fu%>BMO zbLUnX&7=4jHRgW!`|@1_BMZfA?;G>;$Xp3F!XYAjq=n4RryqvMTnv(+A8(*~-3X(u zm?(Zu7c9nGQG&38%u~JY5UnCA*D`sv<<*f_S6)4N_2m^?>Pz6a5LTdAt;Z?=RuOo3 zIYq_5D#$x2LB%tzaSE!n!{4%j1T2!A}d%mm1_ygq{B#N45N+Y z092~lRVCL5I#&f*w+*_MApn_NmybugS*`Jvw!~Vx%(pwLZ zc_QMdy=ViO=X%lW(Xf{dycKNnuC|Gh+1^@9WQ!}Z5Y@#>vUW%1PV&ZQ%jn@=8_nf<61LU7%2GL?0Do8j4A zmf(e27bGYXLyKwTWiyu^< zota&(1rS>|u6?l|W=}trCElpL2f>TXs}w0SP0GiOHz2a^Ui+r~;3cnQN`B^SjX`YP z!2WM+-NGJ`i%{LE_8gIUlN((y zgB;uDUG;f18WYE9;)9}W%;Ad2jS<2nnj51IPc}DZ7sPWM?rXVup2hb-Zp^m<2DW^F zfl0wUX7T$y9uM0NjOU7z!rVO11@nkSz&wIoP%A>k17kcMVIG|4h?mmbJQwCcJXkB@ z@x#d6T55?y(eZc`i&wbZ7_k8tN?oq~+&t!ke5nKPIdu!hJg%#JJRjDSSrCH}apXD9 zg;56f24irqG1~_9&?DpDD^0DeY^heGsJln@6v)nA_Z$(u5HZCojBbz F{sBeBwnYE{ literal 0 HcmV?d00001 diff --git a/qpdf/qtest/qpdf/page-labels-and-outlines.pdf b/qpdf/qtest/qpdf/page-labels-and-outlines.pdf new file mode 100644 index 00000000..a0b01037 --- /dev/null +++ b/qpdf/qtest/qpdf/page-labels-and-outlines.pdf @@ -0,0 +1,1503 @@ +%PDF-1.3 +%¿÷¢þ +%QDF-1.0 + +1 0 obj +<< + /PageLabels << /Nums [ + 0 << /P () >> + 2 << /S /r /St 1 >> + 7 << /P () >> + 9 << /S /r /St 6 >> + 11 << /P () >> + 12 << /S /D /St 2 >> + 15 << /S /D /St 6 >> + 19 << /P () >> + 20 << /S /D /St 12 >> + 22 << /S /D /St 16059 >> + 23 << /S /r /St 50 >> + 29 << /S /r /St 54 >> + ] >> + /Pages 2 0 R + /Type /Catalog + /PageMode /UseOutlines + /Outlines 95 0 R +>> +endobj + +2 0 obj +<< + /Count 30 + /Kids [ + 3 0 R + 4 0 R + 5 0 R + 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 + 19 0 R + 20 0 R + 21 0 R + 22 0 R + 23 0 R + 24 0 R + 25 0 R + 26 0 R + 27 0 R + 28 0 R + 29 0 R + 30 0 R + 31 0 R + 32 0 R + ] + /Type /Pages +>> +endobj + +%% Page 1 +3 0 obj +<< + /Contents 33 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 2 +4 0 obj +<< + /Contents 37 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 3 +5 0 obj +<< + /Contents 39 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 4 +6 0 obj +<< + /Contents 41 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 5 +7 0 obj +<< + /Contents 43 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 6 +8 0 obj +<< + /Contents 45 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 7 +9 0 obj +<< + /Contents 47 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 8 +10 0 obj +<< + /Contents 49 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 9 +11 0 obj +<< + /Contents 51 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 10 +12 0 obj +<< + /Contents 53 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 11 +13 0 obj +<< + /Contents 55 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 12 +14 0 obj +<< + /Contents 57 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 13 +15 0 obj +<< + /Contents 59 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 14 +16 0 obj +<< + /Contents 61 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 15 +17 0 obj +<< + /Contents 63 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 16 +18 0 obj +<< + /Contents 65 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 17 +19 0 obj +<< + /Contents 67 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 18 +20 0 obj +<< + /Contents 69 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 19 +21 0 obj +<< + /Contents 71 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 20 +22 0 obj +<< + /Contents 73 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 21 +23 0 obj +<< + /Contents 75 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 22 +24 0 obj +<< + /Contents 77 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 23 +25 0 obj +<< + /Contents 79 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 24 +26 0 obj +<< + /Contents 81 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 25 +27 0 obj +<< + /Contents 83 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 26 +28 0 obj +<< + /Contents 85 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 27 +29 0 obj +<< + /Contents 87 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 28 +30 0 obj +<< + /Contents 89 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 29 +31 0 obj +<< + /Contents 91 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Page 30 +32 0 obj +<< + /Contents 93 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 35 0 R + >> + /ProcSet 36 0 R + >> + /Type /Page +>> +endobj + +%% Contents for page 1 +33 0 obj +<< + /Length 34 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 0) Tj +ET +endstream +endobj + +34 0 obj +46 +endobj + +35 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +36 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 2 +37 0 obj +<< + /Length 38 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 1) Tj +ET +endstream +endobj + +38 0 obj +46 +endobj + +%% Contents for page 3 +39 0 obj +<< + /Length 40 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 2) Tj +ET +endstream +endobj + +40 0 obj +46 +endobj + +%% Contents for page 4 +41 0 obj +<< + /Length 42 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 3) Tj +ET +endstream +endobj + +42 0 obj +46 +endobj + +%% Contents for page 5 +43 0 obj +<< + /Length 44 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 4) Tj +ET +endstream +endobj + +44 0 obj +46 +endobj + +%% Contents for page 6 +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 7 +47 0 obj +<< + /Length 48 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 6) Tj +ET +endstream +endobj + +48 0 obj +46 +endobj + +%% Contents for page 8 +49 0 obj +<< + /Length 50 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 7) Tj +ET +endstream +endobj + +50 0 obj +46 +endobj + +%% Contents for page 9 +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 + +%% Contents for page 10 +53 0 obj +<< + /Length 54 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 9) Tj +ET +endstream +endobj + +54 0 obj +46 +endobj + +%% Contents for page 11 +55 0 obj +<< + /Length 56 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 10) Tj +ET +endstream +endobj + +56 0 obj +47 +endobj + +%% Contents for page 12 +57 0 obj +<< + /Length 58 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 11) Tj +ET +endstream +endobj + +58 0 obj +47 +endobj + +%% Contents for page 13 +59 0 obj +<< + /Length 60 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 12) Tj +ET +endstream +endobj + +60 0 obj +47 +endobj + +%% Contents for page 14 +61 0 obj +<< + /Length 62 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 13) Tj +ET +endstream +endobj + +62 0 obj +47 +endobj + +%% Contents for page 15 +63 0 obj +<< + /Length 64 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 14) Tj +ET +endstream +endobj + +64 0 obj +47 +endobj + +%% Contents for page 16 +65 0 obj +<< + /Length 66 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 15) Tj +ET +endstream +endobj + +66 0 obj +47 +endobj + +%% Contents for page 17 +67 0 obj +<< + /Length 68 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 16) Tj +ET +endstream +endobj + +68 0 obj +47 +endobj + +%% Contents for page 18 +69 0 obj +<< + /Length 70 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 17) Tj +ET +endstream +endobj + +70 0 obj +47 +endobj + +%% Contents for page 19 +71 0 obj +<< + /Length 72 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 18) Tj +ET +endstream +endobj + +72 0 obj +47 +endobj + +%% Contents for page 20 +73 0 obj +<< + /Length 74 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 19) Tj +ET +endstream +endobj + +74 0 obj +47 +endobj + +%% Contents for page 21 +75 0 obj +<< + /Length 76 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 20) Tj +ET +endstream +endobj + +76 0 obj +47 +endobj + +%% Contents for page 22 +77 0 obj +<< + /Length 78 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 21) Tj +ET +endstream +endobj + +78 0 obj +47 +endobj + +%% Contents for page 23 +79 0 obj +<< + /Length 80 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 22) Tj +ET +endstream +endobj + +80 0 obj +47 +endobj + +%% Contents for page 24 +81 0 obj +<< + /Length 82 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 23) Tj +ET +endstream +endobj + +82 0 obj +47 +endobj + +%% Contents for page 25 +83 0 obj +<< + /Length 84 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 24) Tj +ET +endstream +endobj + +84 0 obj +47 +endobj + +%% Contents for page 26 +85 0 obj +<< + /Length 86 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 25) Tj +ET +endstream +endobj + +86 0 obj +47 +endobj + +%% Contents for page 27 +87 0 obj +<< + /Length 88 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 26) Tj +ET +endstream +endobj + +88 0 obj +47 +endobj + +%% Contents for page 28 +89 0 obj +<< + /Length 90 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 27) Tj +ET +endstream +endobj + +90 0 obj +47 +endobj + +%% Contents for page 29 +91 0 obj +<< + /Length 92 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 28) Tj +ET +endstream +endobj + +92 0 obj +47 +endobj + +%% Contents for page 30 +93 0 obj +<< + /Length 94 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 29) Tj +ET +endstream +endobj + +94 0 obj +47 +endobj + +95 0 obj +<< + /Type /Outlines + /First 96 0 R + /Last 97 0 R + /Count 6 +>> +endobj + +96 0 obj +<< + /Type /Outline + /Title (Isís 1 -> 5: /XYZ null null null) + /Parent 95 0 R + /Count 4 + /Next 97 0 R + /First 98 0 R + /Last 99 0 R + /Dest [ 8 0 R /XYZ null null null ] +>> +endobj + +97 0 obj +<< + /Type /Outline + /Title (Trepak 2 -> 15: /XYZ 66 756 3) + /Parent 95 0 R + /Prev 96 0 R + /Dest [ 18 0 R /XYZ 66 756 3 ] +>> +endobj + +98 0 obj +<< + /Type /Outline + /Title (Amanda 1.1 -> 11: /Fit) + /Parent 96 0 R + /Next 99 0 R + /First 100 0 R + /Last 101 0 R + /Count -3 + /Dest [ 14 0 R /Fit ] +>> +endobj + +99 0 obj +<< + /Type /Outline + % /Title (Sandy (Sandy [Greek]) 1.2 -> 13: /FitH 792) + /Title + /Parent 96 0 R + /Prev 98 0 R + /First 105 0 R + /Last 106 0 R + /Count 2 + /Dest [ 16 0 R /FitH 792 ] +>> +endobj + +100 0 obj +<< + /Type /Outline + /Title (Isosicle 1.1.1 -> 12: /FitV 100) + /Parent 98 0 R + /Next 101 0 R + /First 102 0 R + /Last 103 0 R + /Count -2 + /Dest [ 15 0 R /FitV 100 ] +>> +endobj + +101 0 obj +<< + /Type /Outline + /Title (Isosicle 1.1.2 -> 12: /XYZ null null null) + /Parent 98 0 R + /Prev 100 0 R + /First 104 0 R + /Last 104 0 R + /Count 1 + /Dest [ 15 0 R /XYZ null null null ] +>> +endobj + +102 0 obj +<< + /Type /Outline + /Title (Isosicle 1.1.1.1 -> 18: /XYZ null null null) + /Parent 100 0 R + /Next 103 0 R + /Dest [ 21 0 R /XYZ null null null ] +>> +endobj + +103 0 obj +<< + /Type /Outline + /Title (Isosicle 1.1.1.2 -> 19: /XYZ null null null) + /Parent 100 0 R + /Prev 102 0 R + /Dest [ 22 0 R /XYZ null null null ] +>> +endobj + +104 0 obj +<< + /Type /Outline + /Title (Isosicle 1.1.2.1 -> 22: /XYZ null null null) + /Parent 101 0 R + /Dest [ 25 0 R /XYZ null null null ] +>> +endobj + +105 0 obj +<< + /Type /Outline + /Title (Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770) + /Parent 99 0 R + /Next 106 0 R + /Dest [ 4 0 R /FitR 66 714 180 770 ] +>> +endobj + +106 0 obj +<< + /Type /Outline + /Title (Trepsicle 1.2.2 -> 0: /XYZ null null null) + /Parent 99 0 R + /Prev 105 0 R + /Dest [ 3 0 R /XYZ null null null ] +>> +endobj + +xref +0 107 +0000000000 65535 f +0000000025 00000 n +0000000434 00000 n +0000000830 00000 n +0000001035 00000 n +0000001240 00000 n +0000001445 00000 n +0000001650 00000 n +0000001855 00000 n +0000002060 00000 n +0000002265 00000 n +0000002471 00000 n +0000002678 00000 n +0000002885 00000 n +0000003092 00000 n +0000003299 00000 n +0000003506 00000 n +0000003713 00000 n +0000003920 00000 n +0000004127 00000 n +0000004334 00000 n +0000004541 00000 n +0000004748 00000 n +0000004955 00000 n +0000005162 00000 n +0000005369 00000 n +0000005576 00000 n +0000005783 00000 n +0000005990 00000 n +0000006197 00000 n +0000006404 00000 n +0000006611 00000 n +0000006818 00000 n +0000007037 00000 n +0000007140 00000 n +0000007160 00000 n +0000007279 00000 n +0000007338 00000 n +0000007441 00000 n +0000007484 00000 n +0000007587 00000 n +0000007630 00000 n +0000007733 00000 n +0000007776 00000 n +0000007879 00000 n +0000007922 00000 n +0000008025 00000 n +0000008068 00000 n +0000008171 00000 n +0000008214 00000 n +0000008317 00000 n +0000008360 00000 n +0000008463 00000 n +0000008507 00000 n +0000008610 00000 n +0000008654 00000 n +0000008758 00000 n +0000008802 00000 n +0000008906 00000 n +0000008950 00000 n +0000009054 00000 n +0000009098 00000 n +0000009202 00000 n +0000009246 00000 n +0000009350 00000 n +0000009394 00000 n +0000009498 00000 n +0000009542 00000 n +0000009646 00000 n +0000009690 00000 n +0000009794 00000 n +0000009838 00000 n +0000009942 00000 n +0000009986 00000 n +0000010090 00000 n +0000010134 00000 n +0000010238 00000 n +0000010282 00000 n +0000010386 00000 n +0000010430 00000 n +0000010534 00000 n +0000010578 00000 n +0000010682 00000 n +0000010726 00000 n +0000010830 00000 n +0000010874 00000 n +0000010978 00000 n +0000011022 00000 n +0000011126 00000 n +0000011170 00000 n +0000011274 00000 n +0000011318 00000 n +0000011422 00000 n +0000011466 00000 n +0000011570 00000 n +0000011590 00000 n +0000011677 00000 n +0000011873 00000 n +0000012019 00000 n +0000012194 00000 n +0000012547 00000 n +0000012738 00000 n +0000012948 00000 n +0000013117 00000 n +0000013286 00000 n +0000013439 00000 n +0000013608 00000 n +trailer << + /Root 1 0 R + /Size 107 +>> +startxref +13773 +%%EOF