diff --git a/sources/pdf_links.cpp b/sources/pdf_links.cpp index 9b591305f..af892836a 100644 --- a/sources/pdf_links.cpp +++ b/sources/pdf_links.cpp @@ -194,6 +194,22 @@ void injectCrossRefLinks(QPdfEngine *engine, Diagram *diagram, engine->drawHyperlink(link.rect, link.url); } +/// The trailer dictionary of the xref table at \a xrefStart, from +/// "trailer" up to its startxref, which the caller writes again with the +/// new offset. Copying the old startxref too left a second one in the +/// file, with the size the file had before its links were rewritten, so +/// the bytes depended on the output path the links carry (#1178). +static QByteArray trailerDictionary(const QByteArray &data, int xrefStart) +{ + const int start = data.indexOf("trailer", xrefStart); + if (start == -1) + return "trailer\n<<>>"; + const int end = data.indexOf("startxref", start); + if (end == -1) + return "trailer\n<<>>"; + return data.mid(start, end - start).trimmed(); +} + void convertUriToGoTo(const QString &pdfPath) { // --- 1. Read raw bytes --- @@ -383,19 +399,7 @@ void convertUriToGoTo(const QString &pdfPath) } } - // Find trailer dict from the original xref section - int trailerPos = data.indexOf("trailer", xrefStart); - int trailerEnd = -1; - if (trailerPos != -1) { - trailerEnd = data.indexOf("%%EOF", trailerPos); - if (trailerEnd != -1) trailerEnd += 5; - } - - QByteArray trailer; - if (trailerPos != -1 && trailerEnd != -1) - trailer = data.mid(trailerPos, trailerEnd - trailerPos); - else - trailer = "trailer\n<<>>\n%%EOF"; + const QByteArray trailer = trailerDictionary(data, xrefStart); int newXrefOffset = body.size(); @@ -814,18 +818,7 @@ void convertComponentInfoAnnotations(const QString &pdfPath, } // Copy trailer and bump /Size to account for the new XObject - QByteArray trailer; - { - int tPos = data.indexOf("trailer", xrefStart); - if (tPos != -1) { - int tEnd = data.indexOf("%%EOF", tPos); - if (tEnd != -1) tEnd += 5; - if (tEnd != -1) - trailer = data.mid(tPos, tEnd - tPos); - } - } - if (trailer.isEmpty()) - trailer = "trailer\n<<>>\n%%EOF"; + QByteArray trailer = trailerDictionary(data, xrefStart); // Bump /Size: original was maxObjNum+1, now it's emptyXObjNum+1 { @@ -842,16 +835,6 @@ void convertComponentInfoAnnotations(const QString &pdfPath, } } - // Remove duplicate startxref if present in copied trailer - { - int stPos = trailer.indexOf("\nstartxref\n"); - if (stPos != -1) - trailer = trailer.left(stPos); - // Ensure trailer ends with %%EOF - if (!trailer.endsWith("%%EOF\n")) - trailer += "\n%%EOF\n"; - } - QByteArray result; result.reserve(out.size() + xref.size() + trailer.size() + 64); result += out; diff --git a/tests/qttest/tst_pdfreproducible.cpp b/tests/qttest/tst_pdfreproducible.cpp index a1f37450d..05bd946e1 100644 --- a/tests/qttest/tst_pdfreproducible.cpp +++ b/tests/qttest/tst_pdfreproducible.cpp @@ -22,12 +22,17 @@ class tst_pdfreproducible : public QObject QTemporaryDir m_dir; int m_run = 0; - QByteArray exportPdf(const QString &project, const QByteArray &epoch) + QByteArray exportPdf(const QString &project, const QByteArray &epoch, + const QString &folder = QString()) { const int run = m_run++; const QString home = m_dir.filePath(QStringLiteral("home%1").arg(run)); const QString tmp = m_dir.filePath(QStringLiteral("tmp%1").arg(run)); - const QString out = m_dir.filePath(QStringLiteral("out%1.pdf").arg(run)); + const QString out = folder.isEmpty() + ? m_dir.filePath(QStringLiteral("out%1.pdf").arg(run)) + : m_dir.filePath(folder + QStringLiteral("/same.pdf")); + if (!folder.isEmpty()) + QDir().mkpath(m_dir.filePath(folder)); QDir().mkpath(home); QDir().mkpath(tmp); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); @@ -145,6 +150,23 @@ private slots: "the same drawing with new uuids gives a different PDF"); } + void sameBytesInAnyFolder() + { + // #1178: the links carry the output path, and rewriting them left + // the old startxref in the file, with the size the file had before. + const QString project = + QStringLiteral(QET_EXAMPLES_DIR) + QStringLiteral("/iso_sfc_example.qet"); + QVERIFY2(QFile::exists(project), "examples/iso_sfc_example.qet not found"); + + const QByteArray first = exportPdf(project, "1700000000", QStringLiteral("p")); + QVERIFY(!first.isEmpty()); + QVERIFY(first.contains("/GoTo")); + QCOMPARE(first.count("startxref"), 1); + QVERIFY2(xrefMatches(first), "the xref table does not match the file"); + QVERIFY2(exportPdf(project, "1700000000", QStringLiteral("longer-folder-name")) == first, + "the same project gives a different PDF in another folder"); + } + void nowWithoutTheVariable() { const QString project =