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)
|
||||
try {
|
||||
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')
|
||||
} catch (pdfErr) {
|
||||
const pdfMsg = apiMessage(pdfErr).message
|
||||
|
||||
@ -12,20 +12,27 @@ export interface PdfPageImage {
|
||||
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 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) {
|
||||
const page = await pdf.getPage(pageNumber)
|
||||
const baseViewport = page.getViewport({ scale: 1 })
|
||||
const scale = targetWidth / baseViewport.width
|
||||
const viewport = page.getViewport({ scale })
|
||||
const canvas = document.createElement("canvas")
|
||||
canvas.width = Math.ceil(viewport.width)
|
||||
canvas.height = Math.ceil(viewport.height)
|
||||
const context = canvas.getContext("2d")
|
||||
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
|
||||
pages.push({
|
||||
pageNumber,
|
||||
@ -33,6 +40,7 @@ export async function loadPdfPageImages(pdfBytes: ArrayBuffer, targetWidth = PAG
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
})
|
||||
onPageReady?.([...pages])
|
||||
}
|
||||
|
||||
return pages
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user