Coverage Report

Created: 2026-08-25 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/poppler/PageLabelInfo.cc
Line
Count
Source
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, 2025, 2026 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
// Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
12
// Copyright (C) 2026 Stefan Brüns <stefan.bruens@rwth-aachen.de>
13
//
14
// To see a description of the changes please see the Changelog file that
15
// came with your tarball or type make ChangeLog if you are building from git
16
//
17
//========================================================================
18
19
#include <config.h>
20
#include <cstdlib>
21
#include <cstdio>
22
#include <cassert>
23
24
#include <algorithm>
25
26
#include "Object.h"
27
#include "PageLabelInfo.h"
28
#include "PageLabelInfo_p.h"
29
30
PageLabelInfo::Interval::Interval(const Dict &dict, int baseA)
31
0
{
32
0
    style = None;
33
0
    Object obj = dict.lookup("S");
34
0
    if (obj.isName()) {
35
0
        if (obj.isName("D")) {
36
0
            style = Arabic;
37
0
        } else if (obj.isName("R")) {
38
0
            style = UppercaseRoman;
39
0
        } else if (obj.isName("r")) {
40
0
            style = LowercaseRoman;
41
0
        } else if (obj.isName("A")) {
42
0
            style = UppercaseLatin;
43
0
        } else if (obj.isName("a")) {
44
0
            style = LowercaseLatin;
45
0
        }
46
0
    }
47
48
0
    obj = dict.lookup("P");
49
0
    if (obj.isString()) {
50
0
        const std::string &str = obj.getString();
51
0
        prefix.assign(str);
52
0
    }
53
54
0
    obj = dict.lookup("St");
55
0
    if (obj.isInt()) {
56
0
        first = obj.getInt();
57
0
    } else {
58
0
        first = 1;
59
0
    }
60
61
0
    base = baseA;
62
0
}
63
64
PageLabelInfo::PageLabelInfo(const Dict &tree, int numPages)
65
0
{
66
0
    RefRecursionChecker alreadyParsedRefs;
67
0
    parse(tree, alreadyParsedRefs);
68
69
0
    if (intervals.empty()) {
70
0
        return;
71
0
    }
72
73
0
    auto curr = intervals.begin();
74
0
    for (auto next = curr + 1; next != intervals.end(); ++next, ++curr) {
75
0
        curr->length = std::max(0, next->base - curr->base);
76
0
    }
77
0
    curr->length = std::max(0, numPages - curr->base);
78
0
}
79
80
void PageLabelInfo::parse(const Dict &tree, RefRecursionChecker &alreadyParsedRefs)
81
0
{
82
    // leaf node
83
0
    Object nums = tree.lookup("Nums");
84
0
    if (nums.isArray()) {
85
0
        for (int i = 0; i < nums.arrayGetLength(); i += 2) {
86
0
            Object obj = nums.arrayGet(i);
87
0
            if (!obj.isInt()) {
88
0
                continue;
89
0
            }
90
0
            const int base = obj.getInt();
91
0
            if (base < 0) {
92
0
                continue;
93
0
            }
94
0
            obj = nums.arrayGet(i + 1);
95
0
            if (!obj.isDict()) {
96
0
                continue;
97
0
            }
98
99
0
            intervals.emplace_back(*obj.getDict(), base);
100
0
        }
101
0
    }
102
103
0
    Object kids = tree.lookup("Kids");
104
0
    if (kids.isArray()) {
105
0
        const Array *kidsArray = kids.getArray();
106
0
        for (int i = 0; i < kidsArray->getLength(); ++i) {
107
0
            Ref ref;
108
0
            const Object kid = kidsArray->get(i, &ref);
109
0
            if (!alreadyParsedRefs.insert(ref)) {
110
0
                error(errSyntaxError, -1, "loop in PageLabelInfo (ref.num: {0:d})", ref.num);
111
0
                continue;
112
0
            }
113
0
            if (kid.isDict()) {
114
0
                parse(*kid.getDict(), alreadyParsedRefs);
115
0
            }
116
0
        }
117
0
    }
118
0
}
119
120
std::optional<int> PageLabelInfo::labelToIndex(const std::string &label) const
121
0
{
122
0
    const char *const str = label.c_str();
123
0
    const std::size_t strLen = label.size();
124
0
    const bool strUnicode = hasUnicodeByteOrderMark(label);
125
0
    int number;
126
127
0
    for (const auto &interval : intervals) {
128
0
        const std::size_t prefixLen = interval.prefix.size();
129
0
        if (strLen < prefixLen || interval.prefix.compare(0, prefixLen, str, prefixLen) != 0) {
130
0
            continue;
131
0
        }
132
133
0
        switch (interval.style) {
134
0
        case Interval::Arabic:
135
0
            bool ok;
136
0
            std::tie(number, ok) = fromDecimal(label.substr(prefixLen), strUnicode);
137
0
            if (ok && number - interval.first < interval.length) {
138
0
                return interval.base + number - interval.first;
139
0
            }
140
0
            break;
141
0
        case Interval::LowercaseRoman:
142
0
        case Interval::UppercaseRoman:
143
0
            number = fromRoman(str + prefixLen);
144
0
            if (number >= 0 && number - interval.first < interval.length) {
145
0
                return interval.base + number - interval.first;
146
0
            }
147
0
            break;
148
0
        case Interval::UppercaseLatin:
149
0
        case Interval::LowercaseLatin:
150
0
            number = fromLatin(str + prefixLen);
151
0
            if (number >= 0 && number - interval.first < interval.length) {
152
0
                return interval.base + number - interval.first;
153
0
            }
154
0
            break;
155
0
        case Interval::None:
156
0
            if (interval.length == 1 && label == interval.prefix) {
157
0
                return interval.base;
158
0
            } else {
159
0
                error(errSyntaxError, -1, "asking to convert label to page index in an unknown scenario, report a bug");
160
0
            }
161
0
            break;
162
0
        }
163
0
    }
164
165
0
    return {};
166
0
}
167
168
bool PageLabelInfo::indexToLabel(int index, std::string *label) const
169
0
{
170
0
    char buffer[32];
171
0
    int base, number;
172
0
    const Interval *matching_interval;
173
0
    std::string number_string;
174
175
0
    base = 0;
176
0
    matching_interval = nullptr;
177
0
    for (const auto &interval : intervals) {
178
0
        if (base <= index && index < base + interval.length) {
179
0
            matching_interval = &interval;
180
0
            break;
181
0
        }
182
0
        base += interval.length;
183
0
    }
184
185
0
    if (!matching_interval) {
186
0
        return false;
187
0
    }
188
189
0
    number = index - base + matching_interval->first;
190
0
    switch (matching_interval->style) {
191
0
    case Interval::Arabic:
192
0
        snprintf(buffer, sizeof(buffer), "%d", number);
193
0
        number_string.append(buffer);
194
0
        break;
195
0
    case Interval::LowercaseRoman:
196
0
        toRoman(number, &number_string, false);
197
0
        break;
198
0
    case Interval::UppercaseRoman:
199
0
        toRoman(number, &number_string, true);
200
0
        break;
201
0
    case Interval::LowercaseLatin:
202
0
        toLatin(number, &number_string, false);
203
0
        break;
204
0
    case Interval::UppercaseLatin:
205
0
        toLatin(number, &number_string, true);
206
0
        break;
207
0
    case Interval::None:
208
0
        break;
209
0
    }
210
211
0
    label->clear();
212
0
    label->append(matching_interval->prefix.c_str(), matching_interval->prefix.size());
213
0
    if (hasUnicodeByteOrderMark(*label)) {
214
0
        int i, len;
215
0
        char ucs2_char[2];
216
217
        /* Convert the ascii number string to ucs2 and append. */
218
0
        len = number_string.size();
219
0
        ucs2_char[0] = 0;
220
0
        for (i = 0; i < len; ++i) {
221
0
            ucs2_char[1] = number_string[i];
222
0
            label->append(ucs2_char, 2);
223
0
        }
224
0
    } else {
225
0
        label->append(number_string);
226
0
    }
227
228
0
    return true;
229
0
}