Coverage Report

Created: 2025-07-01 06:14

/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
}