From 98bb29a519da06da55eaf8c3b2e8e7f7ee0e4fd3 Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Sat, 27 Dec 2025 20:54:45 -0600 Subject: [PATCH] fix(reader): fix PDF viewer with shallowRef and parallel page loading --- .../reader/components/PdfReaderView.vue | 575 +++++++++++------- .../src/features/reader/composables/usePdf.ts | 66 +- server/src/db/migrations/meta/_journal.json | 8 +- 3 files changed, 401 insertions(+), 248 deletions(-) diff --git a/client/src/features/reader/components/PdfReaderView.vue b/client/src/features/reader/components/PdfReaderView.vue index 7b417e15e..b29fb2797 100644 --- a/client/src/features/reader/components/PdfReaderView.vue +++ b/client/src/features/reader/components/PdfReaderView.vue @@ -1,133 +1,209 @@ + + diff --git a/client/src/features/reader/composables/usePdf.ts b/client/src/features/reader/composables/usePdf.ts index 4c7022572..06956a1ff 100644 --- a/client/src/features/reader/composables/usePdf.ts +++ b/client/src/features/reader/composables/usePdf.ts @@ -1,4 +1,4 @@ -import { onUnmounted, ref } from 'vue' +import { onUnmounted, shallowRef, ref } from 'vue' import * as pdfjsLib from 'pdfjs-dist' import pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url' @@ -6,23 +6,19 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl export type ZoomMode = 'fit-width' | 'fit-page' | 'custom' -// Renders one page onto the given canvas at the given scale (pdfjs-dist v5 API). -async function renderPageToCanvas(doc: pdfjsLib.PDFDocumentProxy, pageNum: number, canvas: HTMLCanvasElement, scale: number) { - const page = await doc.getPage(pageNum) - const viewport = page.getViewport({ scale }) - canvas.width = viewport.width - canvas.height = viewport.height - await page.render({ canvas, viewport }).promise - page.cleanup() +export interface PageDim { + width: number + height: number } export function usePdf() { - const pdfDoc = ref(null) + // shallowRef is critical — Vue's deep proxy breaks PDF.js internal state + const pdfDoc = shallowRef(null) const totalPages = ref(0) const loading = ref(false) const error = ref(null) - async function load(fileId: number) { + async function load(fileId: number): Promise { loading.value = true error.value = null try { @@ -39,13 +35,47 @@ export function usePdf() { } } - // Returns the natural width/height of a page at scale=1 (used to size placeholder divs). - async function getPageViewport(pageNum: number, scale: number) { - if (!pdfDoc.value) return null - const page = await pdfDoc.value.getPage(pageNum) - const vp = page.getViewport({ scale }) + // Fetch ALL page natural dims in parallel (capped at 10 concurrent). + async function getAllPageDims(): Promise { + const doc = pdfDoc.value + if (!doc) return [] + const total = doc.numPages + const results: PageDim[] = new Array(total) + const CONCURRENCY = 10 + + async function fetchDim(i: number) { + const page = await doc.getPage(i + 1) + const vp = page.getViewport({ scale: 1 }) + page.cleanup() + results[i] = { width: vp.width, height: vp.height } + } + + for (let start = 0; start < total; start += CONCURRENCY) { + const batch = Array.from({ length: Math.min(CONCURRENCY, total - start) }, (_, k) => fetchDim(start + k)) + await Promise.all(batch) + } + return results + } + + async function renderPage(pageNum: number, canvas: HTMLCanvasElement, scale: number): Promise { + const doc = pdfDoc.value + if (!doc) return + const page = await doc.getPage(pageNum) + const viewport = page.getViewport({ scale }) + canvas.width = viewport.width + canvas.height = viewport.height + await page.render({ canvas, viewport }).promise page.cleanup() - return vp + } + + async function getTextContent(pageNum: number) { + const doc = pdfDoc.value + if (!doc) return null + const page = await doc.getPage(pageNum) + const content = await page.getTextContent() + const viewport = page.getViewport({ scale: 1 }) + page.cleanup() + return { content, viewport } } onUnmounted(() => { @@ -53,5 +83,5 @@ export function usePdf() { pdfDoc.value = null }) - return { pdfDoc, totalPages, loading, error, load, renderPageToCanvas, getPageViewport } + return { pdfDoc, totalPages, loading, error, load, getAllPageDims, renderPage, getTextContent } } diff --git a/server/src/db/migrations/meta/_journal.json b/server/src/db/migrations/meta/_journal.json index 49719b479..425b14ecf 100644 --- a/server/src/db/migrations/meta/_journal.json +++ b/server/src/db/migrations/meta/_journal.json @@ -5,28 +5,28 @@ { "idx": 0, "version": "7", - "when": 1766926208614, + "when": 1766937175999, "tag": "0000_init_schema", "breakpoints": true }, { "idx": 1, "version": "7", - "when": 1766926208614, + "when": 1766937175999, "tag": "0001_lethal_pixie", "breakpoints": true }, { "idx": 2, "version": "7", - "when": 1766926208614, + "when": 1766937175999, "tag": "0002_powerful_thundra", "breakpoints": true }, { "idx": 3, "version": "7", - "when": 1766926208614, + "when": 1766937175999, "tag": "0003_reading_progress_by_file", "breakpoints": true }