2008-04-29 12:55:25 +00:00
|
|
|
#include <qpdf/QIntC.hh>
|
|
|
|
#include <qpdf/QPDF.hh>
|
2018-12-19 18:11:59 +00:00
|
|
|
#include <qpdf/QPDFOutlineDocumentHelper.hh>
|
2022-02-10 13:47:15 +00:00
|
|
|
#include <qpdf/QPDFPageDocumentHelper.hh>
|
2008-04-29 12:55:25 +00:00
|
|
|
#include <qpdf/QTC.hh>
|
|
|
|
#include <qpdf/QUtil.hh>
|
2023-05-20 11:22:32 +00:00
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
2008-04-29 12:55:25 +00:00
|
|
|
#include <iostream>
|
|
|
|
|
2018-12-23 14:11:10 +00:00
|
|
|
// This program demonstrates extraction of bookmarks using the qpdf outlines API. Note that all the
|
|
|
|
// information shown by this program can also be obtained from a PDF file using qpdf's --json
|
|
|
|
// option.
|
2022-02-10 13:47:15 +00:00
|
|
|
//
|
|
|
|
// Ignore calls to QTC::TC - they are for qpdf CI testing only.
|
2018-12-23 14:11:10 +00:00
|
|
|
|
2022-07-26 11:37:50 +00:00
|
|
|
static char const* whoami = nullptr;
|
2008-04-29 12:55:25 +00:00
|
|
|
static enum { st_none, st_numbers, st_lines } style = st_none;
|
|
|
|
static bool show_open = false;
|
|
|
|
static bool show_targets = false;
|
2013-06-14 15:58:37 +00:00
|
|
|
static std::map<QPDFObjGen, int> page_map;
|
2008-04-29 12:55:25 +00:00
|
|
|
|
|
|
|
void
|
|
|
|
usage()
|
|
|
|
{
|
|
|
|
std::cerr << "Usage: " << whoami << " [options] file.pdf [password]" << std::endl
|
2022-02-08 14:18:08 +00:00
|
|
|
<< "Options:" << std::endl
|
|
|
|
<< " --numbers give bookmarks outline-style numbers" << std::endl
|
|
|
|
<< " --lines draw lines to show bookmark hierarchy" << std::endl
|
|
|
|
<< " --show-open indicate whether a bookmark is initially open" << std::endl
|
|
|
|
<< " --show-targets show target if possible" << std::endl;
|
2008-04-29 12:55:25 +00:00
|
|
|
exit(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
print_lines(std::vector<int>& numbers)
|
|
|
|
{
|
|
|
|
for (unsigned int i = 0; i < numbers.size() - 1; ++i) {
|
2022-02-08 14:18:08 +00:00
|
|
|
if (numbers.at(i)) {
|
|
|
|
std::cout << "| ";
|
|
|
|
} else {
|
|
|
|
std::cout << " ";
|
|
|
|
}
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
generate_page_map(QPDF& qpdf)
|
|
|
|
{
|
2018-06-18 19:06:51 +00:00
|
|
|
QPDFPageDocumentHelper dh(qpdf);
|
2008-04-29 12:55:25 +00:00
|
|
|
int n = 0;
|
2022-02-10 13:47:15 +00:00
|
|
|
for (auto const& page: dh.getAllPages()) {
|
|
|
|
page_map[page.getObjectHandle().getObjGen()] = ++n;
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-19 18:11:59 +00:00
|
|
|
void
|
|
|
|
show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers)
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
2018-12-19 18:11:59 +00:00
|
|
|
// No default so gcc will warn on missing tag
|
|
|
|
switch (style) {
|
|
|
|
case st_none:
|
|
|
|
QTC::TC("examples", "pdf-bookmarks none");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case st_numbers:
|
|
|
|
QTC::TC("examples", "pdf-bookmarks numbers");
|
2022-02-10 13:47:15 +00:00
|
|
|
for (auto const& number: numbers) {
|
|
|
|
std::cout << number << ".";
|
2018-12-19 18:11:59 +00:00
|
|
|
}
|
|
|
|
std::cout << " ";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case st_lines:
|
|
|
|
QTC::TC("examples", "pdf-bookmarks lines");
|
|
|
|
print_lines(numbers);
|
|
|
|
std::cout << "|" << std::endl;
|
|
|
|
print_lines(numbers);
|
|
|
|
std::cout << "+-+ ";
|
|
|
|
break;
|
|
|
|
}
|
2008-04-29 12:55:25 +00:00
|
|
|
|
2018-12-19 18:11:59 +00:00
|
|
|
if (show_open) {
|
|
|
|
int count = outline.getCount();
|
|
|
|
if (count) {
|
|
|
|
QTC::TC("examples", "pdf-bookmarks has count");
|
|
|
|
if (count > 0) {
|
|
|
|
// hierarchy is open at this point
|
|
|
|
QTC::TC("examples", "pdf-bookmarks open");
|
|
|
|
std::cout << "(v) ";
|
|
|
|
} else {
|
|
|
|
QTC::TC("examples", "pdf-bookmarks closed");
|
|
|
|
std::cout << "(>) ";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
QTC::TC("examples", "pdf-bookmarks no count");
|
|
|
|
std::cout << "( ) ";
|
|
|
|
}
|
|
|
|
}
|
2008-04-29 12:55:25 +00:00
|
|
|
|
2018-12-19 18:11:59 +00:00
|
|
|
if (show_targets) {
|
|
|
|
QTC::TC("examples", "pdf-bookmarks targets");
|
|
|
|
std::string target = "unknown";
|
|
|
|
QPDFObjectHandle dest_page = outline.getDestPage();
|
|
|
|
if (!dest_page.isNull()) {
|
|
|
|
QTC::TC("examples", "pdf-bookmarks dest");
|
|
|
|
QPDFObjGen og = dest_page.getObjGen();
|
|
|
|
if (page_map.count(og)) {
|
2022-09-21 16:49:21 +00:00
|
|
|
target = std::to_string(page_map[og]);
|
2018-12-19 18:11:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
std::cout << "[ -> " << target << " ] ";
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
2018-12-19 18:11:59 +00:00
|
|
|
std::cout << outline.getTitle() << std::endl;
|
|
|
|
}
|
|
|
|
|
2019-07-03 17:34:02 +00:00
|
|
|
void
|
|
|
|
extract_bookmarks(std::vector<QPDFOutlineObjectHelper> outlines, std::vector<int>& numbers)
|
2018-12-19 18:11:59 +00:00
|
|
|
{
|
2022-02-10 13:47:15 +00:00
|
|
|
// For style == st_numbers, numbers.at(n) contains the numerical label for the outline, so we
|
|
|
|
// count up from 1.
|
|
|
|
// For style == st_lines, numbers.at(n) == 0 indicates the last outline at level n, and we don't
|
|
|
|
// otherwise care what the value is, so we count up to zero.
|
|
|
|
numbers.push_back((style == st_lines) ? -QIntC::to_int(outlines.size()) : 0);
|
|
|
|
for (auto& outline: outlines) {
|
2018-12-19 18:11:59 +00:00
|
|
|
++(numbers.back());
|
2022-02-10 13:47:15 +00:00
|
|
|
show_bookmark_details(outline, numbers);
|
|
|
|
extract_bookmarks(outline.getKids(), numbers);
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
2018-12-19 18:11:59 +00:00
|
|
|
numbers.pop_back();
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char* argv[])
|
|
|
|
{
|
2009-07-15 04:26:32 +00:00
|
|
|
whoami = QUtil::getWhoami(argv[0]);
|
|
|
|
|
2008-04-29 12:55:25 +00:00
|
|
|
if ((argc == 2) && (strcmp(argv[1], "--version") == 0)) {
|
2022-02-08 14:18:08 +00:00
|
|
|
std::cout << whoami << " version 1.5" << std::endl;
|
|
|
|
exit(0);
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int arg;
|
|
|
|
for (arg = 1; arg < argc; ++arg) {
|
2022-02-08 14:18:08 +00:00
|
|
|
if (argv[arg][0] == '-') {
|
|
|
|
if (strcmp(argv[arg], "--numbers") == 0) {
|
|
|
|
style = st_numbers;
|
|
|
|
} else if (strcmp(argv[arg], "--lines") == 0) {
|
|
|
|
style = st_lines;
|
|
|
|
} else if (strcmp(argv[arg], "--show-open") == 0) {
|
|
|
|
show_open = true;
|
|
|
|
} else if (strcmp(argv[arg], "--show-targets") == 0) {
|
|
|
|
show_targets = true;
|
|
|
|
} else {
|
|
|
|
usage();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (arg >= argc) {
|
2022-02-08 14:18:08 +00:00
|
|
|
usage();
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
char const* filename = argv[arg++];
|
|
|
|
char const* password = "";
|
|
|
|
|
|
|
|
if (arg < argc) {
|
2022-02-08 14:18:08 +00:00
|
|
|
password = argv[arg++];
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
if (arg != argc) {
|
2022-02-08 14:18:08 +00:00
|
|
|
usage();
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2022-02-08 14:18:08 +00:00
|
|
|
QPDF qpdf;
|
|
|
|
qpdf.processFile(filename, password);
|
2008-04-29 12:55:25 +00:00
|
|
|
|
2018-12-19 18:11:59 +00:00
|
|
|
QPDFOutlineDocumentHelper odh(qpdf);
|
2022-02-08 14:18:08 +00:00
|
|
|
if (odh.hasOutlines()) {
|
|
|
|
std::vector<int> numbers;
|
|
|
|
if (show_targets) {
|
|
|
|
generate_page_map(qpdf);
|
|
|
|
}
|
|
|
|
extract_bookmarks(odh.getTopLevelOutlines(), numbers);
|
|
|
|
} else {
|
|
|
|
std::cout << filename << " has no bookmarks" << std::endl;
|
|
|
|
}
|
2008-04-29 12:55:25 +00:00
|
|
|
} catch (std::exception& e) {
|
2022-02-08 14:18:08 +00:00
|
|
|
std::cerr << whoami << " processing file " << filename << ": " << e.what() << std::endl;
|
|
|
|
exit(2);
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|