aboutsummaryrefslogtreecommitdiffstats
path: root/examples/pdf-name-number-tree.cc
blob: 7701e70ee436cc0e00b79bf8c5c432ae2bed9fb6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFNameTreeObjectHelper.hh>
#include <qpdf/QPDFNumberTreeObjectHelper.hh>
#include <qpdf/QPDFWriter.hh>
#include <qpdf/QUtil.hh>
#include <iostream>

static char const* whoami = nullptr;

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]);

    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 trees.
    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: name_tree_oh.ditems()) {
        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: names.aitems()) {
        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("-" + std::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;
}