/src/qpdf/libqpdf/QPDFPageLabelDocumentHelper.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include <qpdf/QPDFPageLabelDocumentHelper.hh> |
2 | | |
3 | | #include <qpdf/QTC.hh> |
4 | | |
5 | | QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) : |
6 | 0 | QPDFDocumentHelper(qpdf), |
7 | 0 | m(new Members()) |
8 | 0 | { |
9 | 0 | QPDFObjectHandle root = qpdf.getRoot(); |
10 | 0 | if (root.hasKey("/PageLabels")) { |
11 | 0 | m->labels = |
12 | 0 | std::make_shared<QPDFNumberTreeObjectHelper>(root.getKey("/PageLabels"), this->qpdf); |
13 | 0 | } |
14 | 0 | } |
15 | | |
16 | | bool |
17 | | QPDFPageLabelDocumentHelper::hasPageLabels() |
18 | 0 | { |
19 | 0 | return nullptr != m->labels; |
20 | 0 | } |
21 | | |
22 | | QPDFObjectHandle |
23 | | QPDFPageLabelDocumentHelper::getLabelForPage(long long page_idx) |
24 | 0 | { |
25 | 0 | QPDFObjectHandle result(QPDFObjectHandle::newNull()); |
26 | 0 | if (!hasPageLabels()) { |
27 | 0 | return result; |
28 | 0 | } |
29 | 0 | QPDFNumberTreeObjectHelper::numtree_number offset = 0; |
30 | 0 | QPDFObjectHandle label; |
31 | 0 | if (!m->labels->findObjectAtOrBelow(page_idx, label, offset)) { |
32 | 0 | return result; |
33 | 0 | } |
34 | 0 | if (!label.isDictionary()) { |
35 | 0 | return result; |
36 | 0 | } |
37 | 0 | QPDFObjectHandle S = label.getKey("/S"); // type (D, R, r, A, a) |
38 | 0 | QPDFObjectHandle P = label.getKey("/P"); // prefix |
39 | 0 | QPDFObjectHandle St = label.getKey("/St"); // starting number |
40 | 0 | long long start = 1; |
41 | 0 | if (St.isInteger()) { |
42 | 0 | start = St.getIntValue(); |
43 | 0 | } |
44 | 0 | QIntC::range_check(start, offset); |
45 | 0 | start += offset; |
46 | 0 | result = QPDFObjectHandle::newDictionary(); |
47 | 0 | result.replaceKey("/S", S); |
48 | 0 | result.replaceKey("/P", P); |
49 | 0 | result.replaceKey("/St", QPDFObjectHandle::newInteger(start)); |
50 | 0 | return result; |
51 | 0 | } |
52 | | |
53 | | void |
54 | | QPDFPageLabelDocumentHelper::getLabelsForPageRange( |
55 | | long long start_idx, |
56 | | long long end_idx, |
57 | | long long new_start_idx, |
58 | | std::vector<QPDFObjectHandle>& new_labels) |
59 | 0 | { |
60 | | // Start off with a suitable label for the first page. For every remaining page, if that page |
61 | | // has an explicit entry, copy it. Otherwise, let the subsequent page just sequence from the |
62 | | // prior entry. If there is no entry for the first page, fabricate one that would match how the |
63 | | // page would look in a new file in which it also didn't have an explicit label. |
64 | 0 | QPDFObjectHandle label = getLabelForPage(start_idx); |
65 | 0 | if (label.isNull()) { |
66 | 0 | label = QPDFObjectHandle::newDictionary(); |
67 | 0 | label.replaceKey("/St", QPDFObjectHandle::newInteger(1 + new_start_idx)); |
68 | 0 | } |
69 | | // See if the new label is redundant based on the previous entry in the vector. If so, don't add |
70 | | // it. |
71 | 0 | size_t size = new_labels.size(); |
72 | 0 | bool skip_first = false; |
73 | 0 | if (size >= 2) { |
74 | 0 | QPDFObjectHandle last = new_labels.at(size - 1); |
75 | 0 | QPDFObjectHandle last_idx = new_labels.at(size - 2); |
76 | 0 | if (last_idx.isInteger() && last.isDictionary() && |
77 | 0 | (label.getKey("/S").unparse() == last.getKey("/S").unparse()) && |
78 | 0 | (label.getKey("/P").unparse() == last.getKey("/P").unparse()) && |
79 | 0 | label.getKey("/St").isInteger() && last.getKey("/St").isInteger()) { |
80 | 0 | long long int st_delta = |
81 | 0 | label.getKey("/St").getIntValue() - last.getKey("/St").getIntValue(); |
82 | 0 | long long int idx_delta = new_start_idx - last_idx.getIntValue(); |
83 | 0 | if (st_delta == idx_delta) { |
84 | 0 | QTC::TC("qpdf", "QPDFPageLabelDocumentHelper skip first"); |
85 | 0 | skip_first = true; |
86 | 0 | } |
87 | 0 | } |
88 | 0 | } |
89 | 0 | if (!skip_first) { |
90 | 0 | new_labels.push_back(QPDFObjectHandle::newInteger(new_start_idx)); |
91 | 0 | new_labels.push_back(label); |
92 | 0 | } |
93 | |
|
94 | 0 | long long int idx_offset = new_start_idx - start_idx; |
95 | 0 | for (long long i = start_idx + 1; i <= end_idx; ++i) { |
96 | 0 | if (m->labels->hasIndex(i) && (label = getLabelForPage(i)).isDictionary()) { |
97 | 0 | new_labels.push_back(QPDFObjectHandle::newInteger(i + idx_offset)); |
98 | 0 | new_labels.push_back(label); |
99 | 0 | } |
100 | 0 | } |
101 | 0 | } |
102 | | |
103 | | QPDFObjectHandle |
104 | | QPDFPageLabelDocumentHelper::pageLabelDict( |
105 | | qpdf_page_label_e label_type, int start_num, std::string_view prefix) |
106 | 0 | { |
107 | 0 | auto num = QPDFObjectHandle::newDictionary(); |
108 | 0 | switch (label_type) { |
109 | 0 | case pl_none: |
110 | 0 | break; |
111 | 0 | case pl_digits: |
112 | 0 | num.replaceKey("/S", "/D"_qpdf); |
113 | 0 | break; |
114 | 0 | case pl_alpha_lower: |
115 | 0 | num.replaceKey("/S", "/a"_qpdf); |
116 | 0 | break; |
117 | 0 | case pl_alpha_upper: |
118 | 0 | num.replaceKey("/S", "/A"_qpdf); |
119 | 0 | break; |
120 | 0 | case pl_roman_lower: |
121 | 0 | num.replaceKey("/S", "/r"_qpdf); |
122 | 0 | break; |
123 | 0 | case pl_roman_upper: |
124 | 0 | num.replaceKey("/S", "/R"_qpdf); |
125 | 0 | break; |
126 | 0 | } |
127 | 0 | if (!prefix.empty()) { |
128 | 0 | num.replaceKey("/P", QPDFObjectHandle::newUnicodeString(std::string(prefix))); |
129 | 0 | } |
130 | 0 | if (start_num != 1) { |
131 | 0 | num.replaceKey("/St", QPDFObjectHandle::newInteger(start_num)); |
132 | 0 | } |
133 | 0 | return num; |
134 | 0 | } |