1//========================================================================
2//
3// This file is under the GPLv2 or later license
4//
5// Copyright (C) 2005-2006 Kristian Høgsberg <krh@redhat.com>
6// Copyright (C) 2005, 2009, 2013, 2017, 2018, 2020, 2021, 2023 Albert Astals Cid <aacid@kde.org>
7// Copyright (C) 2011 Simon Kellner <kellner@kit.edu>
8// Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
9// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
10// Copyright (C) 2024 Oliver Sander <oliver.sander@tu-dresden.de>
11//
12// To see a description of the changes please see the Changelog file that
13// came with your tarball or type make ChangeLog if you are building from git
14//
15//========================================================================
16
17#include <config.h>
18#include <climits>
19#include <cstdlib>
20#include <cstdio>
21#include <cassert>
22
23#include <algorithm>
24
25#include "PageLabelInfo.h"
26#include "PageLabelInfo_p.h"
27
28PageLabelInfo::Interval::Interval(Object *dict, int baseA)
29{
30 style = None;
31 Object obj = dict->dictLookup(key: "S");
32 if (obj.isName()) {
33 if (obj.isName(nameA: "D")) {
34 style = Arabic;
35 } else if (obj.isName(nameA: "R")) {
36 style = UppercaseRoman;
37 } else if (obj.isName(nameA: "r")) {
38 style = LowercaseRoman;
39 } else if (obj.isName(nameA: "A")) {
40 style = UppercaseLatin;
41 } else if (obj.isName(nameA: "a")) {
42 style = LowercaseLatin;
43 }
44 }
45
46 obj = dict->dictLookup(key: "P");
47 if (obj.isString()) {
48 const auto str = obj.getString();
49 prefix.assign(s: str->c_str(), n: str->getLength());
50 }
51
52 obj = dict->dictLookup(key: "St");
53 if (obj.isInt()) {
54 first = obj.getInt();
55 } else {
56 first = 1;
57 }
58
59 base = baseA;
60}
61
62PageLabelInfo::PageLabelInfo(Object *tree, int numPages)
63{
64 RefRecursionChecker alreadyParsedRefs;
65 parse(tree, parsedRefs&: alreadyParsedRefs);
66
67 if (intervals.empty()) {
68 return;
69 }
70
71 auto curr = intervals.begin();
72 for (auto next = curr + 1; next != intervals.end(); ++next, ++curr) {
73 curr->length = std::max(a: 0, b: next->base - curr->base);
74 }
75 curr->length = std::max(a: 0, b: numPages - curr->base);
76}
77
78void PageLabelInfo::parse(const Object *tree, RefRecursionChecker &alreadyParsedRefs)
79{
80 // leaf node
81 Object nums = tree->dictLookup(key: "Nums");
82 if (nums.isArray()) {
83 for (int i = 0; i < nums.arrayGetLength(); i += 2) {
84 Object obj = nums.arrayGet(i);
85 if (!obj.isInt()) {
86 continue;
87 }
88 const int base = obj.getInt();
89 if (base < 0) {
90 continue;
91 }
92 obj = nums.arrayGet(i: i + 1);
93 if (!obj.isDict()) {
94 continue;
95 }
96
97 intervals.emplace_back(args: &obj, args: base);
98 }
99 }
100
101 Object kids = tree->dictLookup(key: "Kids");
102 if (kids.isArray()) {
103 const Array *kidsArray = kids.getArray();
104 for (int i = 0; i < kidsArray->getLength(); ++i) {
105 Ref ref;
106 const Object kid = kidsArray->get(i, returnRef: &ref);
107 if (!alreadyParsedRefs.insert(ref)) {
108 error(category: errSyntaxError, pos: -1, msg: "loop in PageLabelInfo (ref.num: {0:d})", ref.num);
109 continue;
110 }
111 if (kid.isDict()) {
112 parse(tree: &kid, alreadyParsedRefs);
113 }
114 }
115 }
116}
117
118bool PageLabelInfo::labelToIndex(GooString *label, int *index) const
119{
120 const char *const str = label->c_str();
121 const std::size_t strLen = label->getLength();
122 const bool strUnicode = hasUnicodeByteOrderMark(s: label->toStr());
123 int number;
124 bool ok;
125
126 for (const auto &interval : intervals) {
127 const std::size_t prefixLen = interval.prefix.size();
128 if (strLen < prefixLen || interval.prefix.compare(pos: 0, n1: prefixLen, s: str, n2: prefixLen) != 0) {
129 continue;
130 }
131
132 switch (interval.style) {
133 case Interval::Arabic:
134 std::tie(args&: number, args&: ok) = fromDecimal(str: label->toStr().substr(pos: prefixLen), unicode: strUnicode);
135 if (ok && number - interval.first < interval.length) {
136 *index = interval.base + number - interval.first;
137 return true;
138 }
139 break;
140 case Interval::LowercaseRoman:
141 case Interval::UppercaseRoman:
142 number = fromRoman(buffer: str + prefixLen);
143 if (number >= 0 && number - interval.first < interval.length) {
144 *index = interval.base + number - interval.first;
145 return true;
146 }
147 break;
148 case Interval::UppercaseLatin:
149 case Interval::LowercaseLatin:
150 number = fromLatin(buffer: str + prefixLen);
151 if (number >= 0 && number - interval.first < interval.length) {
152 *index = interval.base + number - interval.first;
153 return true;
154 }
155 break;
156 case Interval::None:
157 if (interval.length == 1 && label->toStr() == interval.prefix) {
158 *index = interval.base;
159 return true;
160 } else {
161 error(category: errSyntaxError, pos: -1, msg: "asking to convert label to page index in an unknown scenario, report a bug");
162 }
163 break;
164 }
165 }
166
167 return false;
168}
169
170bool PageLabelInfo::indexToLabel(int index, GooString *label) const
171{
172 char buffer[32];
173 int base, number;
174 const Interval *matching_interval;
175 GooString number_string;
176
177 base = 0;
178 matching_interval = nullptr;
179 for (const auto &interval : intervals) {
180 if (base <= index && index < base + interval.length) {
181 matching_interval = &interval;
182 break;
183 }
184 base += interval.length;
185 }
186
187 if (!matching_interval) {
188 return false;
189 }
190
191 number = index - base + matching_interval->first;
192 switch (matching_interval->style) {
193 case Interval::Arabic:
194 snprintf(s: buffer, maxlen: sizeof(buffer), format: "%d", number);
195 number_string.append(str: buffer);
196 break;
197 case Interval::LowercaseRoman:
198 toRoman(number, str: &number_string, uppercase: false);
199 break;
200 case Interval::UppercaseRoman:
201 toRoman(number, str: &number_string, uppercase: true);
202 break;
203 case Interval::LowercaseLatin:
204 toLatin(number, str: &number_string, uppercase: false);
205 break;
206 case Interval::UppercaseLatin:
207 toLatin(number, str: &number_string, uppercase: true);
208 break;
209 case Interval::None:
210 break;
211 }
212
213 label->clear();
214 label->append(str: matching_interval->prefix.c_str(), lengthA: matching_interval->prefix.size());
215 if (hasUnicodeByteOrderMark(s: label->toStr())) {
216 int i, len;
217 char ucs2_char[2];
218
219 /* Convert the ascii number string to ucs2 and append. */
220 len = number_string.getLength();
221 ucs2_char[0] = 0;
222 for (i = 0; i < len; ++i) {
223 ucs2_char[1] = number_string.getChar(i);
224 label->append(str: ucs2_char, lengthA: 2);
225 }
226 } else {
227 label->append(str: &number_string);
228 }
229
230 return true;
231}
232

source code of poppler/poppler/PageLabelInfo.cc