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