qpdf/libqpdf/QPDFPageLabelDocumentHelper.cc

135 lines
4.5 KiB
C++

#include <qpdf/QPDFPageLabelDocumentHelper.hh>
#include <qpdf/QTC.hh>
QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) :
QPDFDocumentHelper(qpdf),
m(new Members())
{
QPDFObjectHandle root = qpdf.getRoot();
if (root.hasKey("/PageLabels")) {
m->labels =
std::make_shared<QPDFNumberTreeObjectHelper>(root.getKey("/PageLabels"), this->qpdf);
}
}
bool
QPDFPageLabelDocumentHelper::hasPageLabels()
{
return nullptr != m->labels;
}
QPDFObjectHandle
QPDFPageLabelDocumentHelper::getLabelForPage(long long page_idx)
{
QPDFObjectHandle result(QPDFObjectHandle::newNull());
if (!hasPageLabels()) {
return result;
}
QPDFNumberTreeObjectHelper::numtree_number offset = 0;
QPDFObjectHandle label;
if (!m->labels->findObjectAtOrBelow(page_idx, label, offset)) {
return result;
}
if (!label.isDictionary()) {
return result;
}
QPDFObjectHandle S = label.getKey("/S"); // type (D, R, r, A, a)
QPDFObjectHandle P = label.getKey("/P"); // prefix
QPDFObjectHandle St = label.getKey("/St"); // starting number
long long start = 1;
if (St.isInteger()) {
start = St.getIntValue();
}
QIntC::range_check(start, offset);
start += offset;
result = QPDFObjectHandle::newDictionary();
result.replaceKey("/S", S);
result.replaceKey("/P", P);
result.replaceKey("/St", QPDFObjectHandle::newInteger(start));
return result;
}
void
QPDFPageLabelDocumentHelper::getLabelsForPageRange(
long long start_idx,
long long end_idx,
long long new_start_idx,
std::vector<QPDFObjectHandle>& new_labels)
{
// Start off with a suitable label for the first page. For every remaining page, if that page
// has an explicit entry, copy it. Otherwise, let the subsequent page just sequence from the
// prior entry. If there is no entry for the first page, fabricate one that would match how the
// page would look in a new file in which it also didn't have an explicit label.
QPDFObjectHandle label = getLabelForPage(start_idx);
if (label.isNull()) {
label = QPDFObjectHandle::newDictionary();
label.replaceKey("/St", QPDFObjectHandle::newInteger(1 + new_start_idx));
}
// See if the new label is redundant based on the previous entry in the vector. If so, don't add
// it.
size_t size = new_labels.size();
bool skip_first = false;
if (size >= 2) {
QPDFObjectHandle last = new_labels.at(size - 1);
QPDFObjectHandle last_idx = new_labels.at(size - 2);
if (last_idx.isInteger() && last.isDictionary() &&
(label.getKey("/S").unparse() == last.getKey("/S").unparse()) &&
(label.getKey("/P").unparse() == last.getKey("/P").unparse()) &&
label.getKey("/St").isInteger() && last.getKey("/St").isInteger()) {
long long int st_delta =
label.getKey("/St").getIntValue() - last.getKey("/St").getIntValue();
long long int idx_delta = new_start_idx - last_idx.getIntValue();
if (st_delta == idx_delta) {
QTC::TC("qpdf", "QPDFPageLabelDocumentHelper skip first");
skip_first = true;
}
}
}
if (!skip_first) {
new_labels.push_back(QPDFObjectHandle::newInteger(new_start_idx));
new_labels.push_back(label);
}
long long int idx_offset = new_start_idx - start_idx;
for (long long i = start_idx + 1; i <= end_idx; ++i) {
if (m->labels->hasIndex(i) && (label = getLabelForPage(i)).isDictionary()) {
new_labels.push_back(QPDFObjectHandle::newInteger(i + idx_offset));
new_labels.push_back(label);
}
}
}
QPDFObjectHandle
QPDFPageLabelDocumentHelper::pageLabelDict(
qpdf_page_label_e label_type, int start_num, std::string_view prefix)
{
auto num = QPDFObjectHandle::newDictionary();
switch (label_type) {
case pl_none:
break;
case pl_digits:
num.replaceKey("/S", "/D"_qpdf);
break;
case pl_alpha_lower:
num.replaceKey("/S", "/a"_qpdf);
break;
case pl_alpha_upper:
num.replaceKey("/S", "/A"_qpdf);
break;
case pl_roman_lower:
num.replaceKey("/S", "/r"_qpdf);
break;
case pl_roman_upper:
num.replaceKey("/S", "/R"_qpdf);
break;
}
if (!prefix.empty()) {
num.replaceKey("/P", QPDFObjectHandle::newUnicodeString(std::string(prefix)));
}
if (start_num != 1) {
num.replaceKey("/St", QPDFObjectHandle::newInteger(start_num));
}
return num;
}