From 6de65ce6bb678939c51ffa1f756d8dd234810e69 Mon Sep 17 00:00:00 2001 From: Zuzanna Date: Wed, 13 Aug 2025 14:09:26 +0200 Subject: [PATCH] hyperlinks working in presentation pdf exports --- package-lock.json | 55 ++++++++++++++ package.json | 1 + scripts/copy-components.js | 1 + www/common/onlyoffice/inner.js | 127 ++++++++++++++++++++++++++++++++- 4 files changed, 182 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e5ef0f44..698dc8c0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "open-sans-fontface": "^1.4.0", "openid-client": "^5.7.1", "pako": "^2.1.0", + "pdf-lib": "^1.17.1", "prompt-confirm": "^2.0.4", "pull-stream": "^3.6.1", "require-css": "0.1.10", @@ -577,6 +578,36 @@ "node": ">= 8" } }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/standard-fonts/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.10" + } + }, + "node_modules/@pdf-lib/upng/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/@rollup/plugin-commonjs": { "version": "28.0.6", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", @@ -5061,6 +5092,30 @@ "node": ">=8" } }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "license": "MIT", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "node_modules/pdf-lib/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/pdf-lib/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/package.json b/package.json index 4eb46a165..40570b8d3 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "open-sans-fontface": "^1.4.0", "openid-client": "^5.7.1", "pako": "^2.1.0", + "pdf-lib": "^1.17.1", "prompt-confirm": "^2.0.4", "pull-stream": "^3.6.1", "require-css": "0.1.10", diff --git a/scripts/copy-components.js b/scripts/copy-components.js index 7d728156d..ca76427b1 100644 --- a/scripts/copy-components.js +++ b/scripts/copy-components.js @@ -35,6 +35,7 @@ Fse.rmSync(oldComponentsPath, { recursive: true, force: true }); "open-sans-fontface", "tweetnacl", "tweetnacl-util", + "pdf-lib", "require-css", "requirejs", "requirejs-plugins", diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index d91394fc4..f32b48e17 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -26,7 +26,9 @@ define([ '/common/onlyoffice/current-version.js', '/common/onlyoffice/broken-formats.js', + '/components/pdf-lib/dist/pdf-lib.js', '/components/file-saver/FileSaver.min.js', + 'css!/components/bootstrap/dist/css/bootstrap.min.css', 'less!/components/components-font-awesome/css/font-awesome.min.css', @@ -53,7 +55,8 @@ define([ EmptySlide, Channel, OOCurrentVersion, - BrokenFormats) + BrokenFormats, + PDFLib) { var saveAs = window.saveAs; var Nacl = window.nacl; @@ -2556,6 +2559,10 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null }); }; + + + + APP.printPdf = function (obj, cb) { var bin = getContent(); x2tConvertData({ @@ -2566,7 +2573,123 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null var md = common.getMetadataMgr().getMetadataLazy(); var type = common.getMetadataMgr().getPrivateData().ooType; var title = md.title || md.defaultTitle || type; - var blob = new Blob([xlsData], {type: "application/pdf"}); + var extractLinks = function () { + var results = []; + var m_aPairs = getEditor().WordControl.m_oDrawingDocument + ?.m_oLogicDocument?. + TableId?.m_aPairs; + + if (!m_aPairs || typeof m_aPairs !== 'object') { + return results; + } + + var parseContent = function (contentArray, context = {}) { + if (!Array.isArray(contentArray)) return; + + contentArray.forEach((contentItem, contentIndex) => { + var path = { ...context, contentIndex }; + + if (typeof contentItem?.Value === "string" && contentItem.Value.trim() !== "") { + results.push( + contentItem.Value + ); + } + + if (Array.isArray(contentItem?.Content)) { + parseContent(contentItem.Content, path); + } + }); + } + + for (const pairKey in m_aPairs) { + var pair = m_aPairs[pairKey]; + var spTreeArray = pair?.cSld?.spTree; + + if (!Array.isArray(spTreeArray)) continue; + + spTreeArray.forEach((spTreeItem, spTreeIndex) => { + var contentArray = spTreeItem?.txBody?.content?.Content; + var context = { pairKey, spTreeIndex }; + parseContent(contentArray, context); + }); + } + + return results; + } + var links = extractLinks() + + var fixLinkAnnotations = async function (pdfBin, links) { + var { + PDFDocument, + PDFName, + PDFString, + PDFNumber, + } = PDFLib; + + var pdfDoc = await PDFDocument.load(pdfBin); + var pages = pdfDoc.getPages(); + + let linkIndex = 0; + + for (const page of pages) { + var annotsRef = page.node.get(PDFName.of('Annots')); + if (!annotsRef) continue; + + var annots = pdfDoc.context.lookup(annotsRef); + if (!annots?.asArray) continue; + + for (const annotRef of annots.asArray()) { + if (linkIndex >= links.length) break; + + var annot = pdfDoc.context.lookup(annotRef); + var subtype = annot.get(PDFName.of('Subtype')); + if (!subtype || subtype.value() !== '/Link') continue; + let actionDict = annot.get(PDFName.of('A')); + if (!actionDict) { + actionDict = pdfDoc.context.obj({}); + annot.set(PDFName.of('A'), actionDict); + } + + var uriString = links[linkIndex]; + actionDict.set(PDFName.of('S'), PDFName.of('URI')); + actionDict.set(PDFName.of('URI'), PDFString.of(uriString)); + + if (!annot.get(PDFName.of('Rect'))) { + annot.set( + PDFName.of('Rect'), + pdfDoc.context.obj([ + PDFNumber.of(50), + PDFNumber.of(750), + PDFNumber.of(200), + PDFNumber.of(770), + ]), + ); + } + + if (!annot.get(PDFName.of('Border'))) { + annot.set( + PDFName.of('Border'), + pdfDoc.context.obj([PDFNumber.of(0), PDFNumber.of(0), PDFNumber.of(0)]), + ); + } + + linkIndex++; + } + } + + return await pdfDoc.save(); + } + + + var run = async function () { + return updatedpdfBin = await fixLinkAnnotations(xlsData, links); + } + + var blob; + run().then(function (updatedpdfBin) { + blob = new Blob([updatedpdfBin], { type: "application/pdf" }); + saveAs(blob, APP.exportPdfName || title+'.pdf'); + }); UI.removeModals(); cb(); saveAs(blob, APP.exportPdfName || title+'.pdf');