diff --git a/web/libs/editor/src/tags/object/Pdf.jsx b/web/libs/editor/src/tags/object/Pdf.jsx index 080368e3cc64..021b0decf3a6 100644 --- a/web/libs/editor/src/tags/object/Pdf.jsx +++ b/web/libs/editor/src/tags/object/Pdf.jsx @@ -1,4 +1,5 @@ import { inject, observer } from "mobx-react"; +import { useEffect, useRef } from "react"; import { types } from "mobx-state-tree"; import Registry from "../../core/Registry"; @@ -35,8 +36,88 @@ const PdfModel = types.compose("PdfModel", Base, ProcessAttrsMixin, AnnotationMi const HtxPdf = inject("store")( observer(({ item }) => { + const containerRef = useRef(null); + + useEffect(() => { + if (!item._url || !containerRef.current) return; + + const container = containerRef.current; + // Clear previous render + container.innerHTML = ""; + + let isCancelled = false; + let loadingTask = null; + + // automatically setup the worker + import("pdfjs-dist/webpack.mjs") + .then((pdfjsLib) => { + loadingTask = pdfjsLib.getDocument({ url: item._url }); + + return loadingTask.promise; + }) + .then(async (pdfDoc) => { + if (isCancelled) return; + + // Render pages sequentially + for (let pageIndex = 1; pageIndex <= pdfDoc.numPages; pageIndex++) { + if (isCancelled) break; + const page = await pdfDoc.getPage(pageIndex); + + const viewport = page.getViewport({ scale: 1 }); + const containerWidth = container.clientWidth || 800; + const scale = containerWidth / viewport.width; + const scaledViewport = page.getViewport({ scale }); + + const canvas = document.createElement("canvas"); + canvas.width = Math.ceil(scaledViewport.width); + canvas.height = Math.ceil(scaledViewport.height); + canvas.style.width = "100%"; + canvas.style.height = `${Math.ceil(scaledViewport.height)}px`; + canvas.style.display = "block"; + + if (pageIndex > 1) { + canvas.style.marginTop = "8px"; + } + + if (pageIndex === 1) { + container.style.height = canvas.style.height; + } + + const context = canvas.getContext("2d"); + if (!context) continue; + + container.appendChild(canvas); + + await page.render({ canvasContext: context, viewport: scaledViewport }).promise; + } + }) + .catch(() => { + if (isCancelled) return; + // Render a fallback message + const errorElem = document.createElement("div"); + errorElem.textContent = "Failed to load PDF"; + errorElem.style.color = "#d00"; + container.appendChild(errorElem); + }); + + return () => { + isCancelled = true; + try { + // @todo this seems like a correct cleanup, but most probably it's called more than once, + // @todo destroying properly loaded pdf + // loadingTask.destroy(); + } catch (e) {} + }; + }, [item._url]); + if (!item._url) return null; - return ; + + return ( +
+ ); }), ); diff --git a/web/package.json b/web/package.json index 8dc1857fde3f..19ee7650dcbf 100644 --- a/web/package.json +++ b/web/package.json @@ -90,6 +90,7 @@ "nanoid": "^3.3.8", "pako": "^2.1.0", "papaparse": "^5.4.1", + "pdfjs-dist": "^5.4.149", "pleasejs": "^0.4.2", "rc-tree": "^5.7.8", "react": "18.3.1", diff --git a/web/yarn.lock b/web/yarn.lock index 78b1a09cb536..aa8385b24e8d 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -3373,6 +3373,72 @@ "@module-federation/runtime" "0.16.0" "@module-federation/sdk" "0.16.0" +"@napi-rs/canvas-android-arm64@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.78.tgz#5d2e2d8f2ae3494deb65b57d90d5da10ce983a5d" + integrity sha512-N1ikxztjrRmh8xxlG5kYm1RuNr8ZW1EINEDQsLhhuy7t0pWI/e7SH91uFVLZKCMDyjel1tyWV93b5fdCAi7ggw== + +"@napi-rs/canvas-darwin-arm64@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.78.tgz#d83a1ce5d91ff96c5862176495d64fd78c52904d" + integrity sha512-FA3aCU3G5yGc74BSmnLJTObnZRV+HW+JBTrsU+0WVVaNyVKlb5nMvYAQuieQlRVemsAA2ek2c6nYtHh6u6bwFw== + +"@napi-rs/canvas-darwin-x64@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.78.tgz#9903a8605fe2a43bc849f9f5788cbd47394c77fd" + integrity sha512-xVij69o9t/frixCDEoyWoVDKgE3ksLGdmE2nvBWVGmoLu94MWUlv2y4Qzf5oozBmydG5Dcm4pRHFBM7YWa1i6g== + +"@napi-rs/canvas-linux-arm-gnueabihf@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.78.tgz#44171f8b0676bef4ef834b744fc5fb72e293be5a" + integrity sha512-aSEXrLcIpBtXpOSnLhTg4jPsjJEnK7Je9KqUdAWjc7T8O4iYlxWxrXFIF8rV8J79h5jNdScgZpAUWYnEcutR3g== + +"@napi-rs/canvas-linux-arm64-gnu@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.78.tgz#c8d984d9b659d129b6359f6e8739aaaae6825035" + integrity sha512-dlEPRX1hLGKaY3UtGa1dtkA1uGgFITn2mDnfI6YsLlYyLJQNqHx87D1YTACI4zFCUuLr/EzQDzuX+vnp9YveVg== + +"@napi-rs/canvas-linux-arm64-musl@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.78.tgz#803c5a35b44bc9ee3c40738efbe6cca3d7a46283" + integrity sha512-TsCfjOPZtm5Q/NO1EZHR5pwDPSPjPEttvnv44GL32Zn1uvudssjTLbvaG1jHq81Qxm16GTXEiYLmx4jOLZQYlg== + +"@napi-rs/canvas-linux-riscv64-gnu@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.78.tgz#ba2a2c90310f5b45b2abbd66282b31f7c07a89a3" + integrity sha512-+cpTTb0GDshEow/5Fy8TpNyzaPsYb3clQIjgWRmzRcuteLU+CHEU/vpYvAcSo7JxHYPJd8fjSr+qqh+nI5AtmA== + +"@napi-rs/canvas-linux-x64-gnu@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.78.tgz#ba82107c108a3258e2652c47178a2d0eefb18be2" + integrity sha512-wxRcvKfvYBgtrO0Uy8OmwvjlnTcHpY45LLwkwVNIWHPqHAsyoTyG/JBSfJ0p5tWRzMOPDCDqdhpIO4LOgXjeyg== + +"@napi-rs/canvas-linux-x64-musl@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.78.tgz#9bfe6f41c79f42682b3bcb76aa9177865177b639" + integrity sha512-vQFOGwC9QDP0kXlhb2LU1QRw/humXgcbVp8mXlyBqzc/a0eijlLF9wzyarHC1EywpymtS63TAj8PHZnhTYN6hg== + +"@napi-rs/canvas-win32-x64-msvc@0.1.78": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.78.tgz#382151972afeaf56549273e2f4cb22e7884b76b5" + integrity sha512-/eKlTZBtGUgpRKalzOzRr6h7KVSuziESWXgBcBnXggZmimwIJWPJlEcbrx5Tcwj8rPuZiANXQOG9pPgy9Q4LTQ== + +"@napi-rs/canvas@^0.1.77": + version "0.1.78" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas/-/canvas-0.1.78.tgz#08cc1889ac127e8bfb06d21ee0b418055075ebd7" + integrity sha512-YaBHJvT+T1DoP16puvWM6w46Lq3VhwKIJ8th5m1iEJyGh7mibk5dT7flBvMQ1EH1LYmMzXJ+OUhu+8wQ9I6u7g== + optionalDependencies: + "@napi-rs/canvas-android-arm64" "0.1.78" + "@napi-rs/canvas-darwin-arm64" "0.1.78" + "@napi-rs/canvas-darwin-x64" "0.1.78" + "@napi-rs/canvas-linux-arm-gnueabihf" "0.1.78" + "@napi-rs/canvas-linux-arm64-gnu" "0.1.78" + "@napi-rs/canvas-linux-arm64-musl" "0.1.78" + "@napi-rs/canvas-linux-riscv64-gnu" "0.1.78" + "@napi-rs/canvas-linux-x64-gnu" "0.1.78" + "@napi-rs/canvas-linux-x64-musl" "0.1.78" + "@napi-rs/canvas-win32-x64-msvc" "0.1.78" + "@napi-rs/nice-android-arm-eabi@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz#9a0cba12706ff56500df127d6f4caf28ddb94936" @@ -14360,6 +14426,13 @@ pathval@^2.0.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== +pdfjs-dist@^5.4.149: + version "5.4.149" + resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-5.4.149.tgz#7ff5b8427f37cd43e16377adf509af8595938afc" + integrity sha512-Xe8/1FMJEQPUVSti25AlDpwpUm2QAVmNOpFP0SIahaPIOKBKICaefbzogLdwey3XGGoaP4Lb9wqiw2e9Jqp0LA== + optionalDependencies: + "@napi-rs/canvas" "^0.1.77" + peek-readable@^5.3.1: version "5.4.2" resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.4.2.tgz#aff1e1ba27a7d6911ddb103f35252ffc1787af49"