diff --git a/sources/pdf_links.cpp b/sources/pdf_links.cpp index 0e7d131..3c3442d 100644 --- a/sources/pdf_links.cpp +++ b/sources/pdf_links.cpp @@ -181,21 +181,50 @@ void convertUriToGoTo(const QString &pdfPath) // marker also occurs inside content streams, and a forward lookahead // wrongly tags neighbouring objects (it found 280 "pages" for a 137-page // document). Qt writes a single, flat /Kids array listing every page. + // + // A single indexOf() for "/Type /Pages" is not enough: the same byte + // sequence can appear incidentally inside binary object streams (an + // embedded logo/background image in the title block, for instance), + // before the real page-tree object. Iterate over every occurrence and + // keep the /Kids array that actually parses into the most page + // references — a stray match inside image data essentially never + // happens to be followed by a well-formed "/Kids [ N 0 R ... ]" array, + // so the real page tree reliably wins this comparison. QVector pageObjs; { - int pagesPos = data.indexOf("/Type /Pages"); - int kidsPos = (pagesPos == -1) ? -1 : data.indexOf("/Kids", pagesPos); - int lb = (kidsPos == -1) ? -1 : data.indexOf('[', kidsPos); - int rb = (lb == -1) ? -1 : data.indexOf(']', lb); - if (lb != -1 && rb != -1 && rb > lb) { + int searchFrom = 0; + while (true) { + int pagesPos = data.indexOf("/Type /Pages", searchFrom); + if (pagesPos == -1) + break; + searchFrom = pagesPos + 1; + + int kidsPos = data.indexOf("/Kids", pagesPos); + int lb = (kidsPos != -1) ? data.indexOf('[', kidsPos) : -1; + int rb = (lb != -1) ? data.indexOf(']', lb) : -1; + // /Kids must belong to the same object as this /Type /Pages: + // don't let it bleed into the next object if the current one + // has no /Kids at all (guard with the next "endobj"). + int nextEndObj = data.indexOf("endobj", pagesPos); + if (kidsPos == -1 || lb == -1 || rb == -1 || rb < lb + || (nextEndObj != -1 && kidsPos > nextEndObj)) + continue; + const QString kids = QString::fromLatin1(data.mid(lb + 1, rb - lb - 1)); QRegularExpression re(QStringLiteral("(\\d+)\\s+\\d+\\s+R")); + QVector candidate; auto it = re.globalMatch(kids); while (it.hasNext()) { int objNum = it.next().captured(1).toInt(); - if (objNum > 0) pageObjs.append(objNum); + if (objNum > 0) candidate.append(objNum); } + + // Keep the candidate with the most kids: the genuine page tree + // root lists every page, while a false positive (if its /Kids + // even parses at all) won't. + if (candidate.size() > pageObjs.size()) + pageObjs = candidate; } }