Skip to content

Commit 9462857

Browse files
authored
Merge pull request #449 from Harbour-Enterprises/artem-HAR-9411-v2
HAR-9411 - optimize pdf viewer
2 parents 929d4b1 + 5dcfb6a commit 9462857

3 files changed

Lines changed: 72 additions & 34 deletions

File tree

packages/superdoc/src/components/PdfViewer/PdfViewer.vue

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
<script setup>
22
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
33
import * as pdfjsViewer from 'pdfjs-dist/web/pdf_viewer';
4-
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?worker';
54
import workerSrc from './worker.js?raw';
5+
import { range } from './helpers/range.js';
66
77
import { storeToRefs } from 'pinia';
8-
import { onMounted, ref, reactive, computed, getCurrentInstance } from 'vue';
8+
import { onMounted, onUnmounted, ref, getCurrentInstance } from 'vue';
99
import { useSuperdocStore } from '@superdoc/stores/superdoc-store';
1010
import useSelection from '@superdoc/helpers/use-selection';
1111
12-
window.pdfjsWorker = pdfjsWorker;
13-
pdfjsLib.GlobalWorkerOptions.workerSrc = URL.createObjectURL(
14-
new Blob([workerSrc], {
15-
type: 'application/javascript',
16-
}),
17-
);
12+
const workerUrl = URL.createObjectURL(new Blob([workerSrc], { type: 'text/javascript' }));
13+
pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;
1814
1915
const emit = defineEmits(['page-loaded', 'ready', 'selection-change', 'bypass-selection']);
2016
const superdocStore = useSuperdocStore();
2117
const { proxy } = getCurrentInstance();
2218
const { activeZoom } = storeToRefs(superdocStore);
2319
const totalPages = ref(null);
2420
const viewer = ref(null);
21+
22+
const pdfViewerConfig = proxy.$superdoc.config.pdfViewer;
23+
const textLayerMode = pdfViewerConfig.textLayerMode ?? 0;
24+
25+
let pdfjsLoadingTask = null;
26+
let pdfjsDocument = null;
27+
let pdfPageViews = [];
28+
2529
const props = defineProps({
2630
documentData: {
2731
type: Object,
@@ -31,7 +35,6 @@ const props = defineProps({
3135
3236
const id = props.documentData.id;
3337
const pdfData = props.documentData.data;
34-
const selectionBounds = reactive({});
3538
3639
const getOriginalPageSize = (page) => {
3740
const viewport = page.getViewport({ scale: 1 });
@@ -42,62 +45,70 @@ const getOriginalPageSize = (page) => {
4245
4346
async function initPdfLayer(arrayBuffer) {
4447
const loadingTask = pdfjsLib.getDocument(arrayBuffer);
45-
return await loadingTask.promise;
48+
const document = await loadingTask.promise;
49+
return { loadingTask, document };
4650
}
4751
4852
async function loadPDF(fileObject) {
4953
const fileReader = new FileReader();
5054
fileReader.onload = async function (event) {
51-
const pdfDocument = await initPdfLayer(event.target.result);
52-
await renderPages(pdfDocument);
55+
const { loadingTask, document } = await initPdfLayer(event.target.result);
56+
pdfjsLoadingTask = loadingTask;
57+
pdfjsDocument = document;
58+
renderPages(document);
5359
};
5460
fileReader.readAsArrayBuffer(fileObject);
5561
}
5662
57-
const enableTextLayer = (container, state) => {
58-
const textLayer = container.querySelector('.textLayer');
59-
if (textLayer) textLayer.style.pointerEvents = state ? 'auto' : 'none';
60-
};
61-
6263
const renderPages = (pdfDocument) => {
6364
setTimeout(() => {
6465
_renderPages(pdfDocument);
6566
}, 150);
6667
};
6768
69+
async function getPdfjsPages(pdf, firstPage, lastPage) {
70+
const allPagesPromises = range(firstPage, lastPage + 1).map((num) => pdf.getPage(num));
71+
return await Promise.all(allPagesPromises);
72+
}
73+
6874
async function _renderPages(pdfDocument) {
6975
try {
7076
const numPages = pdfDocument.numPages;
7177
totalPages.value = numPages;
7278
73-
for (let i = 1; i <= numPages; i++) {
74-
const page = await pdfDocument.getPage(i);
79+
const firstPage = 1;
80+
const pdfjsPages = await getPdfjsPages(pdfDocument, firstPage, numPages);
81+
82+
for (const [index, page] of pdfjsPages.entries()) {
7583
const container = document.createElement('div');
7684
container.className = 'pdf-page';
77-
container.dataset.pageNumber = i;
78-
container.id = `${id}-page-${i}`;
85+
container.dataset.pageNumber = index + 1;
86+
container.id = `${id}-page-${index + 1}`;
7987
viewer.value.appendChild(container);
8088
8189
const { width, height } = getOriginalPageSize(page);
90+
8291
const scale = 1;
8392
const eventBus = new pdfjsViewer.EventBus();
8493
const pdfPageView = new pdfjsViewer.PDFPageView({
8594
container,
86-
id: i,
95+
id: index + 1,
8796
scale,
8897
defaultViewport: page.getViewport({ scale }),
8998
eventBus,
99+
textLayerMode,
90100
});
101+
pdfPageViews.push(pdfPageView);
91102
92-
const viewport = page.getViewport({ scale });
93103
const containerBounds = container.getBoundingClientRect();
94104
containerBounds.originalWidth = width;
95105
containerBounds.originalHeight = height;
106+
96107
pdfPageView.setPdfPage(page);
97108
await pdfPageView.draw();
98109
99110
// Emit page information
100-
emit('page-loaded', id, i, containerBounds);
111+
emit('page-loaded', id, index, containerBounds);
101112
}
102113
103114
emit('ready', id, viewer);
@@ -152,11 +163,6 @@ function getSelectedTextBoundingBox(container) {
152163
return boundingBox;
153164
}
154165
155-
onMounted(async () => {
156-
await import('pdfjs-dist/web/pdf_viewer.css');
157-
const doc = await loadPDF(pdfData);
158-
});
159-
160166
const handlePdfClick = (e) => {
161167
const { target } = e;
162168
if (target.tagName !== 'SPAN') {
@@ -175,6 +181,28 @@ const handleMouseUp = (e) => {
175181
emit('selection-change', sel);
176182
}
177183
};
184+
185+
const destroy = () => {
186+
pdfPageViews.forEach((view) => view.destroy()); // will cleanup page resources
187+
188+
pdfjsDocument.cleanup();
189+
pdfjsDocument.destroy();
190+
191+
pdfPageViews = [];
192+
pdfjsDocument = null;
193+
pdfjsLoadingTask = null;
194+
195+
URL.revokeObjectURL(workerUrl);
196+
};
197+
198+
onMounted(async () => {
199+
await import('pdfjs-dist/web/pdf_viewer.css');
200+
await loadPDF(pdfData);
201+
});
202+
203+
onUnmounted(() => {
204+
destroy();
205+
});
178206
</script>
179207
180208
<template>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const range = (start, end) => {
2+
const length = end - start;
3+
return Array.from({ length }, (_, i) => start + i);
4+
};

packages/superdoc/src/core/SuperDoc.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ export class SuperDoc extends EventEmitter {
118118
// telemetry config
119119
telemetry: null,
120120

121+
pdfViewer: {},
122+
121123
// Events
122124
onEditorBeforeCreate: () => null,
123125
onEditorCreate: () => null,
@@ -582,12 +584,16 @@ export class SuperDoc extends EventEmitter {
582584
}
583585

584586
destroy() {
585-
if (!this.app) return;
587+
if (!this.app) {
588+
return;
589+
}
590+
586591
this.log('[superdoc] Unmounting app');
587592

588-
this.config.socket.cancelWebsocketRetry();
589-
this.config.socket.disconnect();
590-
this.config.socket.destroy();
593+
this.config.socket?.cancelWebsocketRetry();
594+
this.config.socket?.disconnect();
595+
this.config.socket?.destroy();
596+
591597
this.ydoc?.destroy();
592598
this.provider?.disconnect();
593599
this.provider?.destroy();
@@ -599,7 +605,7 @@ export class SuperDoc extends EventEmitter {
599605
};
600606

601607
// Destroy the ydoc
602-
doc.ydoc.destroy();
608+
doc.ydoc?.destroy();
603609
});
604610

605611
this.superdocStore.reset();

0 commit comments

Comments
 (0)