mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(reader): use range-request-only mode for PDF lazy loading
This commit is contained in:
@@ -13,7 +13,7 @@ import type { PageDim } from '../composables/usePdf'
|
||||
const props = defineProps<{ bookId: number; fileId: number }>()
|
||||
const router = useRouter()
|
||||
|
||||
const { pdfDoc, totalPages, loading, error, load, getAllPageDims, renderPage, getTextContent } = usePdf()
|
||||
const { pdfDoc, totalPages, loading, error, load, getPageDim, renderPage, getTextContent } = usePdf()
|
||||
const progress = useReaderProgress(props.bookId, props.fileId)
|
||||
const { headerVisible, showHeader } = useVisibility()
|
||||
|
||||
@@ -51,7 +51,13 @@ const { scale, zoomMode, customScale, zoomLabel, adjustZoom, applyZoomPreset } =
|
||||
const layout = usePdfLayout(scrollRef, totalPages, effectiveDims, scale, containerH, spread)
|
||||
const { scrollMode, currentPage, pageInput, pageRows, rowHeights, goToPage, onScroll } = layout
|
||||
|
||||
const renderer = usePdfRenderer(renderPage, getTextContent, scale, rotation, totalPages)
|
||||
function onDimUpdate(pageNum: number, dim: PageDim) {
|
||||
const prev = pageDims.value[pageNum - 1]
|
||||
if (prev && prev.width === dim.width && prev.height === dim.height) return
|
||||
pageDims.value = pageDims.value.map((d, i) => (i === pageNum - 1 ? dim : d))
|
||||
}
|
||||
|
||||
const renderer = usePdfRenderer(renderPage, getTextContent, scale, rotation, totalPages, onDimUpdate)
|
||||
const { canvasMap, textLayerMap, invalidate, setupIO, reset, destroy } = renderer
|
||||
|
||||
// ── Invalidation ──────────────────────────────────────────────────────────────
|
||||
@@ -178,7 +184,11 @@ onMounted(async () => {
|
||||
await load(props.fileId)
|
||||
if (!pdfDoc.value) return
|
||||
|
||||
pageDims.value = await getAllPageDims()
|
||||
// Load only page 1 to establish initial layout dims. All pages are seeded with
|
||||
// page 1's size (uniform assumption). onDimUpdate corrects individual pages
|
||||
// lazily as they are rendered — no upfront full-document traversal.
|
||||
const firstDim = await getPageDim(1)
|
||||
pageDims.value = Array.from({ length: totalPages.value }, () => ({ ...firstDim }))
|
||||
reset()
|
||||
|
||||
await nextTick()
|
||||
|
||||
@@ -22,11 +22,10 @@ export function usePdf() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
// Pass URL directly so PDF.js can use HTTP range requests (206 Partial Content).
|
||||
// This avoids downloading the entire file upfront and allows rendering to start immediately.
|
||||
const doc = await pdfjsLib.getDocument({
|
||||
url: `/api/books/files/${fileId}/serve`,
|
||||
rangeChunkSize: 512 * 1024,
|
||||
rangeChunkSize: 65536,
|
||||
disableStream: true,
|
||||
disableAutoFetch: true,
|
||||
}).promise
|
||||
pdfDoc.value = doc
|
||||
@@ -38,26 +37,13 @@ export function usePdf() {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch ALL page natural dims in parallel (capped at 10 concurrent).
|
||||
async function getAllPageDims(): Promise<PageDim[]> {
|
||||
async function getPageDim(pageNum: number): Promise<PageDim> {
|
||||
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
|
||||
if (!doc) return { width: 595, height: 842 }
|
||||
const page = await doc.getPage(pageNum)
|
||||
const vp = page.getViewport({ scale: 1 })
|
||||
page.cleanup()
|
||||
return { width: vp.width, height: vp.height }
|
||||
}
|
||||
|
||||
async function renderPage(pageNum: number, canvas: HTMLCanvasElement, scale: number): Promise<void> {
|
||||
@@ -86,5 +72,5 @@ export function usePdf() {
|
||||
pdfDoc.value = null
|
||||
})
|
||||
|
||||
return { pdfDoc, totalPages, loading, error, load, getAllPageDims, renderPage, getTextContent }
|
||||
return { pdfDoc, totalPages, loading, error, load, getPageDim, renderPage, getTextContent }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { nextTick, ref } from 'vue'
|
||||
import type * as pdfjsLib from 'pdfjs-dist'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { PageDim } from './usePdf'
|
||||
|
||||
type RenderPageFn = (pageNum: number, canvas: HTMLCanvasElement, scale: number) => Promise<void>
|
||||
type GetTextContentFn = (pageNum: number) => Promise<{ content: pdfjsLib.TextContent; viewport: pdfjsLib.PageViewport } | null>
|
||||
type OnDimUpdateFn = (pageNum: number, dim: PageDim) => void
|
||||
|
||||
export function usePdfRenderer(
|
||||
renderPageFn: RenderPageFn,
|
||||
@@ -11,6 +13,7 @@ export function usePdfRenderer(
|
||||
scale: ComputedRef<number>,
|
||||
rotation: Ref<0 | 90 | 180 | 270>,
|
||||
totalPages: Ref<number>,
|
||||
onDimUpdate: OnDimUpdateFn,
|
||||
) {
|
||||
const canvasMap = ref(new Map<number, HTMLCanvasElement>())
|
||||
const textLayerMap = ref(new Map<number, HTMLElement>())
|
||||
@@ -35,6 +38,8 @@ export function usePdfRenderer(
|
||||
const canvas = canvasMap.value.get(pageNum)
|
||||
if (!canvas) continue
|
||||
await renderPageFn(pageNum, canvas, scale.value)
|
||||
// Report natural dim back so layout can correct any initial estimate.
|
||||
onDimUpdate(pageNum, { width: canvas.width / scale.value, height: canvas.height / scale.value })
|
||||
rendered.add(pageNum)
|
||||
await buildTextLayer(pageNum)
|
||||
}
|
||||
|
||||
@@ -5,28 +5,28 @@
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1767433168702,
|
||||
"when": 1767491359647,
|
||||
"tag": "0000_init_schema",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1767433168702,
|
||||
"when": 1767491359647,
|
||||
"tag": "0001_lethal_pixie",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1767433168702,
|
||||
"when": 1767491359647,
|
||||
"tag": "0002_powerful_thundra",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1767433168702,
|
||||
"when": 1767491359647,
|
||||
"tag": "0003_reading_progress_by_file",
|
||||
"breakpoints": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user