Coverage Report

Created: 2026-01-25 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}