Commit 1fec40454ef72c6e2f079b599e9c807ce69a4bec

Authored by Jay Berkenbilt
1 parent ce19ec5c

Add example of name/number trees and dictionary/array iteration

ChangeLog
  1 +2021-01-30 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add examples/pdf-name-number-tree.cc to illustrate new
  4 + name/number tree API and new array/dictionary iterator API.
  5 +
1 6 2021-01-29 Jay Berkenbilt <ejb@ql.org>
2 7  
3 8 * Add wrappers QPDFDictItems and QPDFArrayItems around
... ...
examples/build.mk
... ... @@ -7,6 +7,7 @@ BINS_examples = \
7 7 pdf-filter-tokens \
8 8 pdf-invert-images \
9 9 pdf-mod-info \
  10 + pdf-name-number-tree \
10 11 pdf-npages \
11 12 pdf-overlay-page \
12 13 pdf-parse-content \
... ...
examples/pdf-name-number-tree.cc 0 → 100644
  1 +#include <qpdf/QPDF.hh>
  2 +#include <qpdf/QPDFNameTreeObjectHelper.hh>
  3 +#include <qpdf/QPDFNumberTreeObjectHelper.hh>
  4 +#include <qpdf/QPDFWriter.hh>
  5 +#include <qpdf/QUtil.hh>
  6 +#include <iostream>
  7 +#include <cstring>
  8 +
  9 +static char const* whoami = 0;
  10 +
  11 +void usage()
  12 +{
  13 + std::cerr << "Usage: " << whoami << " outfile.pdf"
  14 + << std::endl
  15 + << "Create some name/number trees and write to a file"
  16 + << std::endl;
  17 + exit(2);
  18 +}
  19 +
  20 +int main(int argc, char* argv[])
  21 +{
  22 + whoami = QUtil::getWhoami(argv[0]);
  23 +
  24 + // For libtool's sake....
  25 + if (strncmp(whoami, "lt-", 3) == 0)
  26 + {
  27 + whoami += 3;
  28 + }
  29 +
  30 + if (argc != 2)
  31 + {
  32 + usage();
  33 + }
  34 +
  35 + char const* outfilename = argv[1];
  36 +
  37 + QPDF qpdf;
  38 + qpdf.emptyPDF();
  39 +
  40 + // This example doesn't do anything particularly useful other than
  41 + // just illustrate how to use the APIs for name and number trees.
  42 + // It also demonstrates use of the iterators for dictionaries and
  43 + // arrays introduced at the same time with qpdf 10.2.
  44 +
  45 + // To use this example, compile it and run it. Study the output
  46 + // and compare it to what you expect. When done, look at the
  47 + // generated output file in a text editor to inspect the structure
  48 + // of the trees as left in the file.
  49 +
  50 + // We're just going to create some name and number trees, hang
  51 + // them off the document catalog (root), and write an empty PDF to
  52 + // a file. The PDF will have no pages and won't be viewable, but
  53 + // you can look at it in a text editor to see the resulting
  54 + // structure of the PDF.
  55 +
  56 + // Create a dictionary off the root where we will hang our name
  57 + // and number tree.
  58 + auto root = qpdf.getRoot();
  59 + auto example = QPDFObjectHandle::newDictionary();
  60 + root.replaceKey("/Example", example);
  61 +
  62 + // Create a name tree, attach it to the file, and add some items.
  63 + auto name_tree = QPDFNameTreeObjectHelper::newEmpty(qpdf);
  64 + auto name_tree_oh = name_tree.getObjectHandle();
  65 + example.replaceKey("/NameTree", name_tree_oh);
  66 + name_tree.insert("K", QPDFObjectHandle::newUnicodeString("king"));
  67 + name_tree.insert("Q", QPDFObjectHandle::newUnicodeString("queen"));
  68 + name_tree.insert("R", QPDFObjectHandle::newUnicodeString("rook"));
  69 + name_tree.insert("B", QPDFObjectHandle::newUnicodeString("bishop"));
  70 + name_tree.insert("N", QPDFObjectHandle::newUnicodeString("knight"));
  71 + auto iter = name_tree.insert(
  72 + "P", QPDFObjectHandle::newUnicodeString("pawn"));
  73 + // Look at the iterator
  74 + std::cout << "just inserted " << iter->first << " -> "
  75 + << iter->second.unparse() << std::endl;
  76 + --iter;
  77 + std::cout << "predecessor: " << iter->first << " -> "
  78 + << iter->second.unparse() << std::endl;
  79 + ++iter;
  80 + ++iter;
  81 + std::cout << "successor: " << iter->first << " -> "
  82 + << iter->second.unparse() << std::endl;
  83 +
  84 + // Use range-for iteration
  85 + std::cout << "Name tree items:" << std::endl;
  86 + for (auto i: name_tree)
  87 + {
  88 + std::cout << " " << i.first << " -> "
  89 + << i.second.unparse() << std::endl;
  90 + }
  91 +
  92 + // This is a small tree, so everything will be at the root. We can
  93 + // look at it using dictionary and array iterators.
  94 + std::cout << "Keys in name tree object:" << std::endl;
  95 + QPDFObjectHandle names;
  96 + for (auto const& i: QPDFDictItems(name_tree_oh))
  97 + {
  98 + std::cout << i.first << std::endl;
  99 + if (i.first == "/Names")
  100 + {
  101 + names = i.second;
  102 + }
  103 + }
  104 + // Values in names array:
  105 + std::cout << "Values in names:" << std::endl;
  106 + for (auto& i: QPDFArrayItems(names))
  107 + {
  108 + std::cout << " " << i.unparse() << std::endl;
  109 + }
  110 +
  111 + // pre 10.2 API
  112 + std::cout << "Has Q?: " << name_tree.hasName("Q") << std::endl;
  113 + std::cout << "Has W?: " << name_tree.hasName("W") << std::endl;
  114 + QPDFObjectHandle obj;
  115 + std::cout << "Found W?: " << name_tree.findObject("W", obj) << std::endl;
  116 + std::cout << "Found Q?: " << name_tree.findObject("Q", obj) << std::endl;
  117 + std::cout << "Q: " << obj.unparse() << std::endl;
  118 +
  119 + // 10.2 API
  120 + iter = name_tree.find("Q");
  121 + std::cout << "Q: " << iter->first << " -> "
  122 + << iter->second.unparse() << std::endl;
  123 + iter = name_tree.find("W");
  124 + std::cout << "W found: " << (iter != name_tree.end()) << std::endl;
  125 + // Allow find to return predecessor
  126 + iter = name_tree.find("W", true);
  127 + std::cout << "W's predecessor: " << iter->first << " -> "
  128 + << iter->second.unparse() << std::endl;
  129 +
  130 + // We can also remove items
  131 + std::cout << "Remove P: " << name_tree.remove("P", &obj) << std::endl;
  132 + std::cout << "Value removed: " << obj.unparse() << std::endl;
  133 + std::cout << "Has P?: " << name_tree.hasName("P") << std::endl;
  134 + // Or we can remove using an iterator
  135 + iter = name_tree.find("K");
  136 + std::cout << "Find K: " << iter->second.unparse() << std::endl;
  137 + iter.remove();
  138 + std::cout << "Iter after removing K: " << iter->first << " -> "
  139 + << iter->second.unparse() << std::endl;
  140 + std::cout << "Has K?: " << name_tree.hasName("K") << std::endl;
  141 +
  142 + // Illustrate some more advanced usage using number trees. These
  143 + // calls work for name trees too.
  144 +
  145 + // The safe way to populate a tree is to call insert repeatedly as
  146 + // above, but if you know you are definitely inserting items in
  147 + // order, it is more efficient to insert them using insertAfter,
  148 + // which avoids doing a binary search through the tree for each
  149 + // insertion. Note that if you don't insert items in order using
  150 + // this method, you will create an invalid tree.
  151 + auto number_tree = QPDFNumberTreeObjectHelper::newEmpty(qpdf);
  152 + auto number_tree_oh = number_tree.getObjectHandle();
  153 + example.replaceKey("/NumberTree", number_tree_oh);
  154 + auto iter2 = number_tree.begin();
  155 + for (int i = 7; i <= 350; i += 7)
  156 + {
  157 + iter2.insertAfter(i, QPDFObjectHandle::newString(
  158 + "-" + QUtil::int_to_string(i) + "-"));
  159 + }
  160 + std::cout << "Numbers:" << std::endl;
  161 + int n = 1;
  162 + for (auto& i: number_tree)
  163 + {
  164 + std::cout << i.first << " -> " << i.second.getUTF8Value();
  165 + if (n % 5)
  166 + {
  167 + std::cout << ", ";
  168 + }
  169 + else
  170 + {
  171 + std::cout << std::endl;
  172 + }
  173 + ++n;
  174 + }
  175 +
  176 + // When you remove an item with an iterator, the iterator
  177 + // advances. This makes it possible to filter while iterating.
  178 + // Remove all items that are multiples of 5.
  179 + iter2 = number_tree.begin();
  180 + while (iter2 != number_tree.end())
  181 + {
  182 + if (iter2->first % 5 == 0)
  183 + {
  184 + iter2.remove(); // also advances
  185 + }
  186 + else
  187 + {
  188 + ++iter2;
  189 + }
  190 + }
  191 + std::cout << "Numbers after filtering:" << std::endl;
  192 + n = 1;
  193 + for (auto& i: number_tree)
  194 + {
  195 + std::cout << i.first << " -> " << i.second.getUTF8Value();
  196 + if (n % 5)
  197 + {
  198 + std::cout << ", ";
  199 + }
  200 + else
  201 + {
  202 + std::cout << std::endl;
  203 + }
  204 + ++n;
  205 + }
  206 +
  207 + // Write to an output file
  208 + QPDFWriter w(qpdf, outfilename);
  209 + w.setQDFMode(true);
  210 + w.setStaticID(true); // for testing only
  211 + w.write();
  212 +
  213 + return 0;
  214 +}
... ...
examples/qtest/name-number-tree.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +BEGIN { $^W = 1; }
  4 +use strict;
  5 +
  6 +chdir("name-number-tree") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +my $td = new TestDriver('name-number-tree');
  11 +
  12 +cleanup();
  13 +
  14 +$td->runtest("name/number tree",
  15 + {$td->COMMAND => 'pdf-name-number-tree a.pdf'},
  16 + {$td->FILE => 'nn.out', $td->EXIT_STATUS => 0},
  17 + $td->NORMALIZE_NEWLINES);
  18 +
  19 +$td->runtest("check output",
  20 + {$td->FILE => "a.pdf"},
  21 + {$td->FILE => "out.pdf"});
  22 +
  23 +cleanup();
  24 +
  25 +$td->report(2);
  26 +
  27 +sub cleanup
  28 +{
  29 + unlink 'a.pdf';
  30 +}
... ...
examples/qtest/name-number-tree/nn.out 0 → 100644
  1 +just inserted P -> (pawn)
  2 +predecessor: N -> (knight)
  3 +successor: Q -> (queen)
  4 +Name tree items:
  5 + B -> (bishop)
  6 + K -> (king)
  7 + N -> (knight)
  8 + P -> (pawn)
  9 + Q -> (queen)
  10 + R -> (rook)
  11 +Keys in name tree object:
  12 +/Names
  13 +Values in names:
  14 + (B)
  15 + (bishop)
  16 + (K)
  17 + (king)
  18 + (N)
  19 + (knight)
  20 + (P)
  21 + (pawn)
  22 + (Q)
  23 + (queen)
  24 + (R)
  25 + (rook)
  26 +Has Q?: 1
  27 +Has W?: 0
  28 +Found W?: 0
  29 +Found Q?: 1
  30 +Q: (queen)
  31 +Q: Q -> (queen)
  32 +W found: 0
  33 +W's predecessor: R -> (rook)
  34 +Remove P: 1
  35 +Value removed: (pawn)
  36 +Has P?: 0
  37 +Find K: (king)
  38 +Iter after removing K: N -> (knight)
  39 +Has K?: 0
  40 +Numbers:
  41 +7 -> -7-, 14 -> -14-, 21 -> -21-, 28 -> -28-, 35 -> -35-
  42 +42 -> -42-, 49 -> -49-, 56 -> -56-, 63 -> -63-, 70 -> -70-
  43 +77 -> -77-, 84 -> -84-, 91 -> -91-, 98 -> -98-, 105 -> -105-
  44 +112 -> -112-, 119 -> -119-, 126 -> -126-, 133 -> -133-, 140 -> -140-
  45 +147 -> -147-, 154 -> -154-, 161 -> -161-, 168 -> -168-, 175 -> -175-
  46 +182 -> -182-, 189 -> -189-, 196 -> -196-, 203 -> -203-, 210 -> -210-
  47 +217 -> -217-, 224 -> -224-, 231 -> -231-, 238 -> -238-, 245 -> -245-
  48 +252 -> -252-, 259 -> -259-, 266 -> -266-, 273 -> -273-, 280 -> -280-
  49 +287 -> -287-, 294 -> -294-, 301 -> -301-, 308 -> -308-, 315 -> -315-
  50 +322 -> -322-, 329 -> -329-, 336 -> -336-, 343 -> -343-, 350 -> -350-
  51 +Numbers after filtering:
  52 +7 -> -7-, 14 -> -14-, 21 -> -21-, 28 -> -28-, 42 -> -42-
  53 +49 -> -49-, 56 -> -56-, 63 -> -63-, 77 -> -77-, 84 -> -84-
  54 +91 -> -91-, 98 -> -98-, 112 -> -112-, 119 -> -119-, 126 -> -126-
  55 +133 -> -133-, 147 -> -147-, 154 -> -154-, 161 -> -161-, 168 -> -168-
  56 +182 -> -182-, 189 -> -189-, 196 -> -196-, 203 -> -203-, 217 -> -217-
  57 +224 -> -224-, 231 -> -231-, 238 -> -238-, 252 -> -252-, 259 -> -259-
  58 +266 -> -266-, 273 -> -273-, 287 -> -287-, 294 -> -294-, 301 -> -301-
  59 +308 -> -308-, 322 -> -322-, 329 -> -329-, 336 -> -336-, 343 -> -343-
... ...
examples/qtest/name-number-tree/out.pdf 0 → 100644
  1 +%PDF-1.3
  2 +%¿÷¢þ
  3 +%QDF-1.0
  4 +
  5 +%% Original object ID: 1 0
  6 +1 0 obj
  7 +<<
  8 + /Example <<
  9 + /NameTree <<
  10 + /Names [
  11 + (B)
  12 + (bishop)
  13 + (N)
  14 + (knight)
  15 + (Q)
  16 + (queen)
  17 + (R)
  18 + (rook)
  19 + ]
  20 + >>
  21 + /NumberTree <<
  22 + /Kids [
  23 + 2 0 R
  24 + 3 0 R
  25 + 4 0 R
  26 + ]
  27 + /Limits [
  28 + 7
  29 + 343
  30 + ]
  31 + >>
  32 + >>
  33 + /Pages 5 0 R
  34 + /Type /Catalog
  35 +>>
  36 +endobj
  37 +
  38 +%% Original object ID: 3 0
  39 +2 0 obj
  40 +<<
  41 + /Limits [
  42 + 7
  43 + 112
  44 + ]
  45 + /Nums [
  46 + 7
  47 + (-7-)
  48 + 14
  49 + (-14-)
  50 + 21
  51 + (-21-)
  52 + 28
  53 + (-28-)
  54 + 42
  55 + (-42-)
  56 + 49
  57 + (-49-)
  58 + 56
  59 + (-56-)
  60 + 63
  61 + (-63-)
  62 + 77
  63 + (-77-)
  64 + 84
  65 + (-84-)
  66 + 91
  67 + (-91-)
  68 + 98
  69 + (-98-)
  70 + 112
  71 + (-112-)
  72 + ]
  73 +>>
  74 +endobj
  75 +
  76 +%% Original object ID: 4 0
  77 +3 0 obj
  78 +<<
  79 + /Limits [
  80 + 119
  81 + 224
  82 + ]
  83 + /Nums [
  84 + 119
  85 + (-119-)
  86 + 126
  87 + (-126-)
  88 + 133
  89 + (-133-)
  90 + 147
  91 + (-147-)
  92 + 154
  93 + (-154-)
  94 + 161
  95 + (-161-)
  96 + 168
  97 + (-168-)
  98 + 182
  99 + (-182-)
  100 + 189
  101 + (-189-)
  102 + 196
  103 + (-196-)
  104 + 203
  105 + (-203-)
  106 + 217
  107 + (-217-)
  108 + 224
  109 + (-224-)
  110 + ]
  111 +>>
  112 +endobj
  113 +
  114 +%% Original object ID: 5 0
  115 +4 0 obj
  116 +<<
  117 + /Limits [
  118 + 231
  119 + 343
  120 + ]
  121 + /Nums [
  122 + 231
  123 + (-231-)
  124 + 238
  125 + (-238-)
  126 + 252
  127 + (-252-)
  128 + 259
  129 + (-259-)
  130 + 266
  131 + (-266-)
  132 + 273
  133 + (-273-)
  134 + 287
  135 + (-287-)
  136 + 294
  137 + (-294-)
  138 + 301
  139 + (-301-)
  140 + 308
  141 + (-308-)
  142 + 322
  143 + (-322-)
  144 + 329
  145 + (-329-)
  146 + 336
  147 + (-336-)
  148 + 343
  149 + (-343-)
  150 + ]
  151 +>>
  152 +endobj
  153 +
  154 +%% Original object ID: 2 0
  155 +5 0 obj
  156 +<<
  157 + /Count 0
  158 + /Kids [
  159 + ]
  160 + /Type /Pages
  161 +>>
  162 +endobj
  163 +
  164 +xref
  165 +0 6
  166 +0000000000 65535 f
  167 +0000000052 00000 n
  168 +0000000448 00000 n
  169 +0000000775 00000 n
  170 +0000001130 00000 n
  171 +0000001505 00000 n
  172 +trailer <<
  173 + /Root 1 0 R
  174 + /Size 6
  175 + /ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
  176 +>>
  177 +startxref
  178 +1567
  179 +%%EOF
... ...
include/qpdf/QPDFNameTreeObjectHelper.hh
... ... @@ -35,6 +35,9 @@
35 35 // up items in the name tree, use UTF-8 strings. All names are
36 36 // normalized for lookup purposes.
37 37  
  38 +// See examples/pdf-name-number-tree.cc for a demonstration of using
  39 +// QPDFNameTreeObjectHelper.
  40 +
38 41 class NNTreeImpl;
39 42 class NNTreeIterator;
40 43 class NNTreeDetails;
... ...
include/qpdf/QPDFNumberTreeObjectHelper.hh
... ... @@ -32,6 +32,9 @@
32 32 // This is an object helper for number trees. See section 7.9.7 in the
33 33 // PDF spec (ISO 32000) for a description of number trees.
34 34  
  35 +// See examples/pdf-name-number-tree.cc for a demonstration of using
  36 +// QPDFNumberTreeObjectHelper.
  37 +
35 38 class NNTreeImpl;
36 39 class NNTreeIterator;
37 40 class NNTreeDetails;
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -1237,6 +1237,9 @@ class QPDFDictItems
1237 1237 // // iter.second is a QPDFObjectHandle
1238 1238 // }
1239 1239  
  1240 + // See examples/pdf-name-number-tree.cc for a demonstration of
  1241 + // using this API.
  1242 +
1240 1243 public:
1241 1244 QPDF_DLL
1242 1245 QPDFDictItems(QPDFObjectHandle& oh);
... ... @@ -1324,6 +1327,9 @@ class QPDFArrayItems
1324 1327 // // iter is a QPDFObjectHandle
1325 1328 // }
1326 1329  
  1330 + // See examples/pdf-name-number-tree.cc for a demonstration of
  1331 + // using this API.
  1332 +
1327 1333 public:
1328 1334 QPDF_DLL
1329 1335 QPDFArrayItems(QPDFObjectHandle& oh);
... ...
manual/qpdf-manual.xml
... ... @@ -4854,7 +4854,8 @@ print &quot;\n&quot;;
4854 4854 <classname>QPDFObjectHandle</classname>, allowing C++-style
4855 4855 iteration, including range-for iteration, over dictionary
4856 4856 and array QPDFObjectHandles. See comments in
4857   - <filename>include/qpdf/QPDFObjectHandle.hh</filename> for
  4857 + <filename>include/qpdf/QPDFObjectHandle.hh</filename> and
  4858 + <filename>examples/pdf-name-number-tree.cc</filename> for
4858 4859 details.
4859 4860 </para>
4860 4861 </listitem>
... ...