fix: incremental PDF page rendering for exam setup backdrop
Some checks failed
app-ci-deploy / test-build-deploy (push) Has been cancelled
Some checks failed
app-ci-deploy / test-build-deploy (push) Has been cancelled
Rendering a 36-page AQA exam PDF sequentially created 36 large canvas elements, causing memory pressure and keeping pdfStatus='loading' for 60–120s in headless Chrome. Two changes: 1. pdfLoader.ts: reuse a single canvas (reduces peak memory from ~120MB to ~4MB) and fire onPageReady callback after each page so callers can stream pages to the canvas as they render. 2. ExamTemplateSetupPage.tsx: use the callback to add each PDF page shape to the tldraw canvas the moment it renders, making the first page visible within a few seconds rather than after all pages load. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2de3e29179
commit
8e8a345e61
@ -159,7 +159,21 @@ const ExamTemplateSetupInner: React.FC = () => {
|
|||||||
setPdfError(null)
|
setPdfError(null)
|
||||||
try {
|
try {
|
||||||
const bytes = await examRepository.getTemplateSourcePdf(templateId)
|
const bytes = await examRepository.getTemplateSourcePdf(templateId)
|
||||||
pages = await loadPdfPageImages(bytes)
|
pages = await loadPdfPageImages(bytes, undefined, (partialPages) => {
|
||||||
|
const newPage = partialPages[partialPages.length - 1]
|
||||||
|
const allGeometries = pageGeometryFromImages(partialPages)
|
||||||
|
pageGeometriesRef.current = allGeometries
|
||||||
|
const ed = editorRef.current
|
||||||
|
if (ed) {
|
||||||
|
const geometry = allGeometries[partialPages.length - 1]
|
||||||
|
const shapeId = createShapeId(PDF_PAGE_IDS_PREFIX + newPage.pageNumber)
|
||||||
|
if (!ed.getCurrentPageShapes().find((s) => s.id === shapeId)) {
|
||||||
|
ed.createShapes([{ id: shapeId, type: PDF_PAGE_SHAPE_TYPE, x: geometry.x, y: geometry.y, isLocked: true, props: { w: geometry.w, h: geometry.h, src: newPage.src, pageNumber: newPage.pageNumber } } as any])
|
||||||
|
try { ed.sendToBack([shapeId as any]) } catch { /* */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setPdfStatus('ready')
|
||||||
|
})
|
||||||
setPdfStatus(pages.length ? 'ready' : 'missing')
|
setPdfStatus(pages.length ? 'ready' : 'missing')
|
||||||
} catch (pdfErr) {
|
} catch (pdfErr) {
|
||||||
const pdfMsg = apiMessage(pdfErr).message
|
const pdfMsg = apiMessage(pdfErr).message
|
||||||
|
|||||||
@ -12,20 +12,27 @@ export interface PdfPageImage {
|
|||||||
height: number
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadPdfPageImages(pdfBytes: ArrayBuffer, targetWidth = PAGE_WIDTH): Promise<PdfPageImage[]> {
|
export async function loadPdfPageImages(
|
||||||
|
pdfBytes: ArrayBuffer,
|
||||||
|
targetWidth = PAGE_WIDTH,
|
||||||
|
onPageReady?: (pages: PdfPageImage[]) => void,
|
||||||
|
): Promise<PdfPageImage[]> {
|
||||||
const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(pdfBytes) }).promise
|
const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(pdfBytes) }).promise
|
||||||
const pages: PdfPageImage[] = []
|
const pages: PdfPageImage[] = []
|
||||||
|
// Reuse a single canvas across all pages to avoid allocating ~120 MB of canvas memory
|
||||||
|
// for a typical 36-page exam paper.
|
||||||
|
const canvas = document.createElement("canvas")
|
||||||
|
|
||||||
for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber += 1) {
|
for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber += 1) {
|
||||||
const page = await pdf.getPage(pageNumber)
|
const page = await pdf.getPage(pageNumber)
|
||||||
const baseViewport = page.getViewport({ scale: 1 })
|
const baseViewport = page.getViewport({ scale: 1 })
|
||||||
const scale = targetWidth / baseViewport.width
|
const scale = targetWidth / baseViewport.width
|
||||||
const viewport = page.getViewport({ scale })
|
const viewport = page.getViewport({ scale })
|
||||||
const canvas = document.createElement("canvas")
|
|
||||||
canvas.width = Math.ceil(viewport.width)
|
canvas.width = Math.ceil(viewport.width)
|
||||||
canvas.height = Math.ceil(viewport.height)
|
canvas.height = Math.ceil(viewport.height)
|
||||||
const context = canvas.getContext("2d")
|
const context = canvas.getContext("2d")
|
||||||
if (!context) throw new Error("Unable to create PDF render canvas")
|
if (!context) throw new Error("Unable to create PDF render canvas")
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
await page.render({ canvasContext: context, viewport }).promise
|
await page.render({ canvasContext: context, viewport }).promise
|
||||||
pages.push({
|
pages.push({
|
||||||
pageNumber,
|
pageNumber,
|
||||||
@ -33,6 +40,7 @@ export async function loadPdfPageImages(pdfBytes: ArrayBuffer, targetWidth = PAG
|
|||||||
width: canvas.width,
|
width: canvas.width,
|
||||||
height: canvas.height,
|
height: canvas.height,
|
||||||
})
|
})
|
||||||
|
onPageReady?.([...pages])
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages
|
return pages
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user