mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Add example of name/number trees and dictionary/array iteration
This commit is contained in:
parent
ce19ec5c4b
commit
1fec40454e
@ -1,3 +1,8 @@
|
||||
2021-01-30 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Add examples/pdf-name-number-tree.cc to illustrate new
|
||||
name/number tree API and new array/dictionary iterator API.
|
||||
|
||||
2021-01-29 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Add wrappers QPDFDictItems and QPDFArrayItems around
|
||||
|
@ -7,6 +7,7 @@ BINS_examples = \
|
||||
pdf-filter-tokens \
|
||||
pdf-invert-images \
|
||||
pdf-mod-info \
|
||||
pdf-name-number-tree \
|
||||
pdf-npages \
|
||||
pdf-overlay-page \
|
||||
pdf-parse-content \
|
||||
|
214
examples/pdf-name-number-tree.cc
Normal file
214
examples/pdf-name-number-tree.cc
Normal file
@ -0,0 +1,214 @@
|
||||
#include <qpdf/QPDF.hh>
|
||||
#include <qpdf/QPDFNameTreeObjectHelper.hh>
|
||||
#include <qpdf/QPDFNumberTreeObjectHelper.hh>
|
||||
#include <qpdf/QPDFWriter.hh>
|
||||
#include <qpdf/QUtil.hh>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
static char const* whoami = 0;
|
||||
|
||||
void usage()
|
||||
{
|
||||
std::cerr << "Usage: " << whoami << " outfile.pdf"
|
||||
<< std::endl
|
||||
<< "Create some name/number trees and write to a file"
|
||||
<< std::endl;
|
||||
exit(2);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
whoami = QUtil::getWhoami(argv[0]);
|
||||
|
||||
// For libtool's sake....
|
||||
if (strncmp(whoami, "lt-", 3) == 0)
|
||||
{
|
||||
whoami += 3;
|
||||
}
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
usage();
|
||||
}
|
||||
|
||||
char const* outfilename = argv[1];
|
||||
|
||||
QPDF qpdf;
|
||||
qpdf.emptyPDF();
|
||||
|
||||
// This example doesn't do anything particularly useful other than
|
||||
// just illustrate how to use the APIs for name and number trees.
|
||||
// It also demonstrates use of the iterators for dictionaries and
|
||||
// arrays introduced at the same time with qpdf 10.2.
|
||||
|
||||
// To use this example, compile it and run it. Study the output
|
||||
// and compare it to what you expect. When done, look at the
|
||||
// generated output file in a text editor to inspect the structure
|
||||
// of the trees as left in the file.
|
||||
|
||||
// We're just going to create some name and number trees, hang
|
||||
// them off the document catalog (root), and write an empty PDF to
|
||||
// a file. The PDF will have no pages and won't be viewable, but
|
||||
// you can look at it in a text editor to see the resulting
|
||||
// structure of the PDF.
|
||||
|
||||
// Create a dictionary off the root where we will hang our name
|
||||
// and number tree.
|
||||
auto root = qpdf.getRoot();
|
||||
auto example = QPDFObjectHandle::newDictionary();
|
||||
root.replaceKey("/Example", example);
|
||||
|
||||
// Create a name tree, attach it to the file, and add some items.
|
||||
auto name_tree = QPDFNameTreeObjectHelper::newEmpty(qpdf);
|
||||
auto name_tree_oh = name_tree.getObjectHandle();
|
||||
example.replaceKey("/NameTree", name_tree_oh);
|
||||
name_tree.insert("K", QPDFObjectHandle::newUnicodeString("king"));
|
||||
name_tree.insert("Q", QPDFObjectHandle::newUnicodeString("queen"));
|
||||
name_tree.insert("R", QPDFObjectHandle::newUnicodeString("rook"));
|
||||
name_tree.insert("B", QPDFObjectHandle::newUnicodeString("bishop"));
|
||||
name_tree.insert("N", QPDFObjectHandle::newUnicodeString("knight"));
|
||||
auto iter = name_tree.insert(
|
||||
"P", QPDFObjectHandle::newUnicodeString("pawn"));
|
||||
// Look at the iterator
|
||||
std::cout << "just inserted " << iter->first << " -> "
|
||||
<< iter->second.unparse() << std::endl;
|
||||
--iter;
|
||||
std::cout << "predecessor: " << iter->first << " -> "
|
||||
<< iter->second.unparse() << std::endl;
|
||||
++iter;
|
||||
++iter;
|
||||
std::cout << "successor: " << iter->first << " -> "
|
||||
<< iter->second.unparse() << std::endl;
|
||||
|
||||
// Use range-for iteration
|
||||
std::cout << "Name tree items:" << std::endl;
|
||||
for (auto i: name_tree)
|
||||
{
|
||||
std::cout << " " << i.first << " -> "
|
||||
<< i.second.unparse() << std::endl;
|
||||
}
|
||||
|
||||
// This is a small tree, so everything will be at the root. We can
|
||||
// look at it using dictionary and array iterators.
|
||||
std::cout << "Keys in name tree object:" << std::endl;
|
||||
QPDFObjectHandle names;
|
||||
for (auto const& i: QPDFDictItems(name_tree_oh))
|
||||
{
|
||||
std::cout << i.first << std::endl;
|
||||
if (i.first == "/Names")
|
||||
{
|
||||
names = i.second;
|
||||
}
|
||||
}
|
||||
// Values in names array:
|
||||
std::cout << "Values in names:" << std::endl;
|
||||
for (auto& i: QPDFArrayItems(names))
|
||||
{
|
||||
std::cout << " " << i.unparse() << std::endl;
|
||||
}
|
||||
|
||||
// pre 10.2 API
|
||||
std::cout << "Has Q?: " << name_tree.hasName("Q") << std::endl;
|
||||
std::cout << "Has W?: " << name_tree.hasName("W") << std::endl;
|
||||
QPDFObjectHandle obj;
|
||||
std::cout << "Found W?: " << name_tree.findObject("W", obj) << std::endl;
|
||||
std::cout << "Found Q?: " << name_tree.findObject("Q", obj) << std::endl;
|
||||
std::cout << "Q: " << obj.unparse() << std::endl;
|
||||
|
||||
// 10.2 API
|
||||
iter = name_tree.find("Q");
|
||||
std::cout << "Q: " << iter->first << " -> "
|
||||
<< iter->second.unparse() << std::endl;
|
||||
iter = name_tree.find("W");
|
||||
std::cout << "W found: " << (iter != name_tree.end()) << std::endl;
|
||||
// Allow find to return predecessor
|
||||
iter = name_tree.find("W", true);
|
||||
std::cout << "W's predecessor: " << iter->first << " -> "
|
||||
<< iter->second.unparse() << std::endl;
|
||||
|
||||
// We can also remove items
|
||||
std::cout << "Remove P: " << name_tree.remove("P", &obj) << std::endl;
|
||||
std::cout << "Value removed: " << obj.unparse() << std::endl;
|
||||
std::cout << "Has P?: " << name_tree.hasName("P") << std::endl;
|
||||
// Or we can remove using an iterator
|
||||
iter = name_tree.find("K");
|
||||
std::cout << "Find K: " << iter->second.unparse() << std::endl;
|
||||
iter.remove();
|
||||
std::cout << "Iter after removing K: " << iter->first << " -> "
|
||||
<< iter->second.unparse() << std::endl;
|
||||
std::cout << "Has K?: " << name_tree.hasName("K") << std::endl;
|
||||
|
||||
// Illustrate some more advanced usage using number trees. These
|
||||
// calls work for name trees too.
|
||||
|
||||
// The safe way to populate a tree is to call insert repeatedly as
|
||||
// above, but if you know you are definitely inserting items in
|
||||
// order, it is more efficient to insert them using insertAfter,
|
||||
// which avoids doing a binary search through the tree for each
|
||||
// insertion. Note that if you don't insert items in order using
|
||||
// this method, you will create an invalid tree.
|
||||
auto number_tree = QPDFNumberTreeObjectHelper::newEmpty(qpdf);
|
||||
auto number_tree_oh = number_tree.getObjectHandle();
|
||||
example.replaceKey("/NumberTree", number_tree_oh);
|
||||
auto iter2 = number_tree.begin();
|
||||
for (int i = 7; i <= 350; i += 7)
|
||||
{
|
||||
iter2.insertAfter(i, QPDFObjectHandle::newString(
|
||||
"-" + QUtil::int_to_string(i) + "-"));
|
||||
}
|
||||
std::cout << "Numbers:" << std::endl;
|
||||
int n = 1;
|
||||
for (auto& i: number_tree)
|
||||
{
|
||||
std::cout << i.first << " -> " << i.second.getUTF8Value();
|
||||
if (n % 5)
|
||||
{
|
||||
std::cout << ", ";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << std::endl;
|
||||
}
|
||||
++n;
|
||||
}
|
||||
|
||||
// When you remove an item with an iterator, the iterator
|
||||
// advances. This makes it possible to filter while iterating.
|
||||
// Remove all items that are multiples of 5.
|
||||
iter2 = number_tree.begin();
|
||||
while (iter2 != number_tree.end())
|
||||
{
|
||||
if (iter2->first % 5 == 0)
|
||||
{
|
||||
iter2.remove(); // also advances
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter2;
|
||||
}
|
||||
}
|
||||
std::cout << "Numbers after filtering:" << std::endl;
|
||||
n = 1;
|
||||
for (auto& i: number_tree)
|
||||
{
|
||||
std::cout << i.first << " -> " << i.second.getUTF8Value();
|
||||
if (n % 5)
|
||||
{
|
||||
std::cout << ", ";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << std::endl;
|
||||
}
|
||||
++n;
|
||||
}
|
||||
|
||||
// Write to an output file
|
||||
QPDFWriter w(qpdf, outfilename);
|
||||
w.setQDFMode(true);
|
||||
w.setStaticID(true); // for testing only
|
||||
w.write();
|
||||
|
||||
return 0;
|
||||
}
|
30
examples/qtest/name-number-tree.test
Normal file
30
examples/qtest/name-number-tree.test
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env perl
|
||||
require 5.008;
|
||||
BEGIN { $^W = 1; }
|
||||
use strict;
|
||||
|
||||
chdir("name-number-tree") or die "chdir testdir failed: $!\n";
|
||||
|
||||
require TestDriver;
|
||||
|
||||
my $td = new TestDriver('name-number-tree');
|
||||
|
||||
cleanup();
|
||||
|
||||
$td->runtest("name/number tree",
|
||||
{$td->COMMAND => 'pdf-name-number-tree a.pdf'},
|
||||
{$td->FILE => 'nn.out', $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
$td->runtest("check output",
|
||||
{$td->FILE => "a.pdf"},
|
||||
{$td->FILE => "out.pdf"});
|
||||
|
||||
cleanup();
|
||||
|
||||
$td->report(2);
|
||||
|
||||
sub cleanup
|
||||
{
|
||||
unlink 'a.pdf';
|
||||
}
|
59
examples/qtest/name-number-tree/nn.out
Normal file
59
examples/qtest/name-number-tree/nn.out
Normal file
@ -0,0 +1,59 @@
|
||||
just inserted P -> (pawn)
|
||||
predecessor: N -> (knight)
|
||||
successor: Q -> (queen)
|
||||
Name tree items:
|
||||
B -> (bishop)
|
||||
K -> (king)
|
||||
N -> (knight)
|
||||
P -> (pawn)
|
||||
Q -> (queen)
|
||||
R -> (rook)
|
||||
Keys in name tree object:
|
||||
/Names
|
||||
Values in names:
|
||||
(B)
|
||||
(bishop)
|
||||
(K)
|
||||
(king)
|
||||
(N)
|
||||
(knight)
|
||||
(P)
|
||||
(pawn)
|
||||
(Q)
|
||||
(queen)
|
||||
(R)
|
||||
(rook)
|
||||
Has Q?: 1
|
||||
Has W?: 0
|
||||
Found W?: 0
|
||||
Found Q?: 1
|
||||
Q: (queen)
|
||||
Q: Q -> (queen)
|
||||
W found: 0
|
||||
W's predecessor: R -> (rook)
|
||||
Remove P: 1
|
||||
Value removed: (pawn)
|
||||
Has P?: 0
|
||||
Find K: (king)
|
||||
Iter after removing K: N -> (knight)
|
||||
Has K?: 0
|
||||
Numbers:
|
||||
7 -> -7-, 14 -> -14-, 21 -> -21-, 28 -> -28-, 35 -> -35-
|
||||
42 -> -42-, 49 -> -49-, 56 -> -56-, 63 -> -63-, 70 -> -70-
|
||||
77 -> -77-, 84 -> -84-, 91 -> -91-, 98 -> -98-, 105 -> -105-
|
||||
112 -> -112-, 119 -> -119-, 126 -> -126-, 133 -> -133-, 140 -> -140-
|
||||
147 -> -147-, 154 -> -154-, 161 -> -161-, 168 -> -168-, 175 -> -175-
|
||||
182 -> -182-, 189 -> -189-, 196 -> -196-, 203 -> -203-, 210 -> -210-
|
||||
217 -> -217-, 224 -> -224-, 231 -> -231-, 238 -> -238-, 245 -> -245-
|
||||
252 -> -252-, 259 -> -259-, 266 -> -266-, 273 -> -273-, 280 -> -280-
|
||||
287 -> -287-, 294 -> -294-, 301 -> -301-, 308 -> -308-, 315 -> -315-
|
||||
322 -> -322-, 329 -> -329-, 336 -> -336-, 343 -> -343-, 350 -> -350-
|
||||
Numbers after filtering:
|
||||
7 -> -7-, 14 -> -14-, 21 -> -21-, 28 -> -28-, 42 -> -42-
|
||||
49 -> -49-, 56 -> -56-, 63 -> -63-, 77 -> -77-, 84 -> -84-
|
||||
91 -> -91-, 98 -> -98-, 112 -> -112-, 119 -> -119-, 126 -> -126-
|
||||
133 -> -133-, 147 -> -147-, 154 -> -154-, 161 -> -161-, 168 -> -168-
|
||||
182 -> -182-, 189 -> -189-, 196 -> -196-, 203 -> -203-, 217 -> -217-
|
||||
224 -> -224-, 231 -> -231-, 238 -> -238-, 252 -> -252-, 259 -> -259-
|
||||
266 -> -266-, 273 -> -273-, 287 -> -287-, 294 -> -294-, 301 -> -301-
|
||||
308 -> -308-, 322 -> -322-, 329 -> -329-, 336 -> -336-, 343 -> -343-
|
179
examples/qtest/name-number-tree/out.pdf
Normal file
179
examples/qtest/name-number-tree/out.pdf
Normal file
@ -0,0 +1,179 @@
|
||||
%PDF-1.3
|
||||
%¿÷¢þ
|
||||
%QDF-1.0
|
||||
|
||||
%% Original object ID: 1 0
|
||||
1 0 obj
|
||||
<<
|
||||
/Example <<
|
||||
/NameTree <<
|
||||
/Names [
|
||||
(B)
|
||||
(bishop)
|
||||
(N)
|
||||
(knight)
|
||||
(Q)
|
||||
(queen)
|
||||
(R)
|
||||
(rook)
|
||||
]
|
||||
>>
|
||||
/NumberTree <<
|
||||
/Kids [
|
||||
2 0 R
|
||||
3 0 R
|
||||
4 0 R
|
||||
]
|
||||
/Limits [
|
||||
7
|
||||
343
|
||||
]
|
||||
>>
|
||||
>>
|
||||
/Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 3 0
|
||||
2 0 obj
|
||||
<<
|
||||
/Limits [
|
||||
7
|
||||
112
|
||||
]
|
||||
/Nums [
|
||||
7
|
||||
(-7-)
|
||||
14
|
||||
(-14-)
|
||||
21
|
||||
(-21-)
|
||||
28
|
||||
(-28-)
|
||||
42
|
||||
(-42-)
|
||||
49
|
||||
(-49-)
|
||||
56
|
||||
(-56-)
|
||||
63
|
||||
(-63-)
|
||||
77
|
||||
(-77-)
|
||||
84
|
||||
(-84-)
|
||||
91
|
||||
(-91-)
|
||||
98
|
||||
(-98-)
|
||||
112
|
||||
(-112-)
|
||||
]
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 4 0
|
||||
3 0 obj
|
||||
<<
|
||||
/Limits [
|
||||
119
|
||||
224
|
||||
]
|
||||
/Nums [
|
||||
119
|
||||
(-119-)
|
||||
126
|
||||
(-126-)
|
||||
133
|
||||
(-133-)
|
||||
147
|
||||
(-147-)
|
||||
154
|
||||
(-154-)
|
||||
161
|
||||
(-161-)
|
||||
168
|
||||
(-168-)
|
||||
182
|
||||
(-182-)
|
||||
189
|
||||
(-189-)
|
||||
196
|
||||
(-196-)
|
||||
203
|
||||
(-203-)
|
||||
217
|
||||
(-217-)
|
||||
224
|
||||
(-224-)
|
||||
]
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 5 0
|
||||
4 0 obj
|
||||
<<
|
||||
/Limits [
|
||||
231
|
||||
343
|
||||
]
|
||||
/Nums [
|
||||
231
|
||||
(-231-)
|
||||
238
|
||||
(-238-)
|
||||
252
|
||||
(-252-)
|
||||
259
|
||||
(-259-)
|
||||
266
|
||||
(-266-)
|
||||
273
|
||||
(-273-)
|
||||
287
|
||||
(-287-)
|
||||
294
|
||||
(-294-)
|
||||
301
|
||||
(-301-)
|
||||
308
|
||||
(-308-)
|
||||
322
|
||||
(-322-)
|
||||
329
|
||||
(-329-)
|
||||
336
|
||||
(-336-)
|
||||
343
|
||||
(-343-)
|
||||
]
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 2 0
|
||||
5 0 obj
|
||||
<<
|
||||
/Count 0
|
||||
/Kids [
|
||||
]
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000052 00000 n
|
||||
0000000448 00000 n
|
||||
0000000775 00000 n
|
||||
0000001130 00000 n
|
||||
0000001505 00000 n
|
||||
trailer <<
|
||||
/Root 1 0 R
|
||||
/Size 6
|
||||
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
|
||||
>>
|
||||
startxref
|
||||
1567
|
||||
%%EOF
|
@ -35,6 +35,9 @@
|
||||
// up items in the name tree, use UTF-8 strings. All names are
|
||||
// normalized for lookup purposes.
|
||||
|
||||
// See examples/pdf-name-number-tree.cc for a demonstration of using
|
||||
// QPDFNameTreeObjectHelper.
|
||||
|
||||
class NNTreeImpl;
|
||||
class NNTreeIterator;
|
||||
class NNTreeDetails;
|
||||
|
@ -32,6 +32,9 @@
|
||||
// This is an object helper for number trees. See section 7.9.7 in the
|
||||
// PDF spec (ISO 32000) for a description of number trees.
|
||||
|
||||
// See examples/pdf-name-number-tree.cc for a demonstration of using
|
||||
// QPDFNumberTreeObjectHelper.
|
||||
|
||||
class NNTreeImpl;
|
||||
class NNTreeIterator;
|
||||
class NNTreeDetails;
|
||||
|
@ -1237,6 +1237,9 @@ class QPDFDictItems
|
||||
// // iter.second is a QPDFObjectHandle
|
||||
// }
|
||||
|
||||
// See examples/pdf-name-number-tree.cc for a demonstration of
|
||||
// using this API.
|
||||
|
||||
public:
|
||||
QPDF_DLL
|
||||
QPDFDictItems(QPDFObjectHandle& oh);
|
||||
@ -1324,6 +1327,9 @@ class QPDFArrayItems
|
||||
// // iter is a QPDFObjectHandle
|
||||
// }
|
||||
|
||||
// See examples/pdf-name-number-tree.cc for a demonstration of
|
||||
// using this API.
|
||||
|
||||
public:
|
||||
QPDF_DLL
|
||||
QPDFArrayItems(QPDFObjectHandle& oh);
|
||||
|
@ -4854,7 +4854,8 @@ print "\n";
|
||||
<classname>QPDFObjectHandle</classname>, allowing C++-style
|
||||
iteration, including range-for iteration, over dictionary
|
||||
and array QPDFObjectHandles. See comments in
|
||||
<filename>include/qpdf/QPDFObjectHandle.hh</filename> for
|
||||
<filename>include/qpdf/QPDFObjectHandle.hh</filename> and
|
||||
<filename>examples/pdf-name-number-tree.cc</filename> for
|
||||
details.
|
||||
</para>
|
||||
</listitem>
|
||||
|
Loading…
Reference in New Issue
Block a user