From 1fec40454ef72c6e2f079b599e9c807ce69a4bec Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 30 Jan 2021 07:34:08 -0500 Subject: Add example of name/number trees and dictionary/array iteration --- examples/pdf-name-number-tree.cc | 214 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 examples/pdf-name-number-tree.cc (limited to 'examples/pdf-name-number-tree.cc') diff --git a/examples/pdf-name-number-tree.cc b/examples/pdf-name-number-tree.cc new file mode 100644 index 00000000..f1df6f14 --- /dev/null +++ b/examples/pdf-name-number-tree.cc @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include + +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; +} -- cgit v1.2.3-54-g00ecf