diff --git a/package-lock.json b/package-lock.json index 186b49e..d5f6fe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@tato30/vue-pdf", - "version": "1.9.5", + "version": "1.9.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tato30/vue-pdf", - "version": "1.9.5", + "version": "1.9.7", "license": "MIT", "dependencies": { - "pdfjs-dist": "3.11.174" + "pdfjs-dist": "^4.2.67" }, "devDependencies": { "@antfu/eslint-config": "^0.38.5", @@ -8309,13 +8309,13 @@ "node": ">=8" } }, - "node_modules/path2d-polyfill": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", - "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "node_modules/path2d": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.0.tgz", + "integrity": "sha512-KdPAykQX6kmLSOO6Jpu2KNcCED7CKjmaBNGGNuctOsG0hgYO1OdYQaan6cYXJiG0WmXOwZZPILPBimu5QAIw3A==", "optional": true, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/pathe": { @@ -8334,15 +8334,15 @@ } }, "node_modules/pdfjs-dist": { - "version": "3.11.174", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", - "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "version": "4.2.67", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.2.67.tgz", + "integrity": "sha512-rJmuBDFpD7cqC8WIkQUEClyB4UAH05K4AsyewToMTp2gSy3Rrx8c1ydAVqlJlGv3yZSOrhEERQU/4ScQQFlLHA==", "engines": { "node": ">=18" }, "optionalDependencies": { "canvas": "^2.11.2", - "path2d-polyfill": "^2.0.1" + "path2d": "^0.2.0" } }, "node_modules/pend": { diff --git a/package.json b/package.json index f2b64f6..b7a1e66 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "vue": "^3.2.33" }, "dependencies": { - "pdfjs-dist": "3.11.174" + "pdfjs-dist": "4.2.67" }, "devDependencies": { "@antfu/eslint-config": "^0.38.5", diff --git a/src/components/usePDF.ts b/src/components/composable.ts similarity index 65% rename from src/components/usePDF.ts rename to src/components/composable.ts index 6c92e0e..e822e3d 100644 --- a/src/components/usePDF.ts +++ b/src/components/composable.ts @@ -6,6 +6,7 @@ import type { PDFDocumentLoadingTask, PDFDocumentProxy } from 'pdfjs-dist' import type { Ref } from 'vue' import type { OnPasswordCallback, PDFDestination, PDFInfo, PDFOptions, PDFSrc } from './types' import { getDestinationArray, getDestinationRef, getLocation, isSpecLike } from './utils/destination' +import { addStylesToIframe, createIframe } from './utils/miscellaneous' // Could not find a way to make this work with vite, importing the worker entry bundle the whole worker to the the final output // https://erindoyle.dev/using-pdfjs-with-vite/ @@ -114,6 +115,76 @@ export function usePDF(src: PDFSrc | Ref, return { pageIndex, location: location ?? { type: 'Fit', spec: [] } } } + async function download(filename = 'filename') { + if (!pdfDoc.value) + throw new Error('Current PDFDocumentProxy have not loaded yet') + const bytes = await pdfDoc.value?.saveDocument() + const blobBytes = new Blob([bytes], { type: 'application/pdf' }) + const blobUrl = URL.createObjectURL(blobBytes) + + const anchorDownload = document.createElement('a') + document.body.appendChild(anchorDownload) + anchorDownload.href = blobUrl + anchorDownload.download = filename + anchorDownload.style.display = 'none' + anchorDownload.click() + + setTimeout(() => { + URL.revokeObjectURL(blobUrl) + document.body.removeChild(anchorDownload) + }, 10) + } + + async function print(dpi = 150, filename = 'filename') { + if (!pdfDoc.value) + throw new Error('Current PDFDocumentProxy have not loaded yet') + const bytes = await pdfDoc.value?.saveDocument() + const savedLoadingTask = PDFJS.getDocument(bytes.buffer) + const savedDocument = await savedLoadingTask.promise + + const PRINT_UNITS = dpi / 72 + const CSS_UNITS = 96 / 72 + + const iframe = await createIframe() + const contentWindow = iframe.contentWindow + contentWindow!.document.title = filename + + const pagesNumbers = [...Array(savedDocument.numPages).keys()].map(val => val + 1) + + for (const pageNumber of pagesNumbers) { + const pageToPrint = await savedDocument.getPage(pageNumber) + const viewport = pageToPrint.getViewport({ scale: 1 })! + + if (pageNumber === 1) { + addStylesToIframe( + contentWindow!, + (viewport.width * PRINT_UNITS) / CSS_UNITS, + (viewport.height * PRINT_UNITS) / CSS_UNITS, + ) + } + + const canvas = document.createElement('canvas') + canvas.width = viewport.width * PRINT_UNITS + canvas.height = viewport.height * PRINT_UNITS + + const canvasCloned = canvas.cloneNode() as HTMLCanvasElement + contentWindow?.document.body.appendChild(canvasCloned) + + await pageToPrint?.render({ + canvasContext: canvas.getContext('2d')!, + intent: 'print', + transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], + viewport, + }).promise + + canvasCloned.getContext('2d')?.drawImage(canvas, 0, 0) + } + + contentWindow?.focus() + contentWindow?.print() + document.body.removeChild(iframe) + } + if (isRef(src)) { if (src.value) processLoadingTask(src.value) @@ -131,6 +202,8 @@ export function usePDF(src: PDFSrc | Ref, pdf, pages, info, + print, + download, getPDFDestination, } } diff --git a/src/components/index.ts b/src/components/index.ts index f29882d..c8be1e4 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,3 +1,3 @@ -export * from './types' -export * from './usePDF' export { default as VuePDF } from './VuePDF.vue' +export * from './composable' +export * from './types' diff --git a/src/components/layers/AnnotationLayer.vue b/src/components/layers/AnnotationLayer.vue index 48c3dac..86ae20a 100644 --- a/src/components/layers/AnnotationLayer.vue +++ b/src/components/layers/AnnotationLayer.vue @@ -98,9 +98,9 @@ async function render() { accessibilityManager: undefined, annotationCanvasMap: canvasMap, div: layer.value!, - l10n: null, page: page!, viewport: viewport!.clone({ dontFlip: true }), + annotationEditorUIManager: null, } const renderParameters: AnnotationLayerParameters = { diff --git a/src/components/layers/TextLayer.vue b/src/components/layers/TextLayer.vue index e3e565f..e1baeb0 100644 --- a/src/components/layers/TextLayer.vue +++ b/src/components/layers/TextLayer.vue @@ -66,7 +66,6 @@ function render() { textContentSource: textStream!, viewport: viewport!, container: layer.value!, - isOffscreenCanvasSupported: true, textDivs, textDivProperties: new WeakMap(), } diff --git a/src/components/utils/miscellaneous.ts b/src/components/utils/miscellaneous.ts new file mode 100644 index 0000000..da50a61 --- /dev/null +++ b/src/components/utils/miscellaneous.ts @@ -0,0 +1,40 @@ +async function createIframe(): Promise { + return new Promise((resolve, reject) => { + const iframe = document.createElement('iframe') + + iframe.width = '0px' + iframe.height = '0px' + iframe.style.cssText = 'position: absolute; top:0; left:0' + iframe.style.display = 'none' + + iframe.onload = function () { + resolve(iframe) + } + document.body.appendChild(iframe) + }) +} + +function addStylesToIframe(content: Window, sizeX: number, sizeY: number) { + const style = content.document.createElement('style') + style.textContent = ` + @page { + margin: 0; + size: ${sizeX}pt ${sizeY}pt; + } + body { + margin: 0; + width: 100%; + } + canvas { + width: 100%; + page-break-after: always; + page-break-before: avoid; + page-break-inside: avoid; + } + ` + content.document.head.appendChild(style) +} + +export { + addStylesToIframe, createIframe, +} diff --git a/vite.config.ts b/vite.config.ts index 453c697..efea3be 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -14,6 +14,13 @@ export default defineConfig({ headless: true, }, }, + optimizeDeps: { + esbuildOptions: { + supported: { + 'top-level-await': true, + }, + }, + }, build: { lib: { entry: resolve(__dirname, './src/index.ts'), diff --git a/vite.playground.ts b/vite.playground.ts index e97bd5e..5be5590 100644 --- a/vite.playground.ts +++ b/vite.playground.ts @@ -2,6 +2,13 @@ import vue from '@vitejs/plugin-vue' import { defineConfig } from 'vite' export default defineConfig({ + optimizeDeps: { + esbuildOptions: { + supported: { + 'top-level-await': true, + }, + }, + }, plugins: [ vue(), ],