{"record":{"id":"a8fca56a346f9c8c","repo":"Stirling-Tools/Stirling-PDF","slug":"canvas-2d-context-unavailable","errorCode":null,"errorMessage":"Canvas 2D context unavailable","messagePattern":"Canvas 2D context unavailable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/hooks/tools/adjustContrast/useAdjustContrastOperation.ts","lineNumber":29,"sourceCode":"import { applyAdjustmentsToCanvas } from \"@app/components/tools/adjustContrast/utils\";\nimport { pdfWorkerManager } from \"@app/services/pdfWorkerManager\";\nimport { createFileFromApiResponse } from \"@app/utils/fileResponseUtils\";\nimport { getPdfiumModule, saveRawDocument } from \"@app/services/pdfiumService\";\nimport { copyRgbaToBgraHeap } from \"@app/utils/pdfiumBitmapUtils\";\nimport type { PDFDocumentProxy } from \"pdfjs-dist\";\n\nasync function renderPdfPageToCanvas(\n  pdf: PDFDocumentProxy,\n  pageNumber: number,\n  scale: number,\n): Promise<HTMLCanvasElement> {\n  const page = await pdf.getPage(pageNumber);\n  const viewport = page.getViewport({ scale });\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = viewport.width;\n  canvas.height = viewport.height;\n  const ctx = canvas.getContext(\"2d\");\n  if (!ctx) throw new Error(\"Canvas 2D context unavailable\");\n  await page.render({ canvasContext: ctx, canvas, viewport }).promise;\n  return canvas;\n}\n\n// Render, adjust, and assemble all pages of a single PDF into a new PDF using PDFium\nasync function buildAdjustedPdfForFile(\n  file: File,\n  params: AdjustContrastParameters,\n): Promise<File> {\n  const m = await getPdfiumModule();\n  const arrayBuffer = await file.arrayBuffer();\n  const pdf = await pdfWorkerManager.createDocument(arrayBuffer, {});\n  const pageCount = pdf.numPages;\n\n  const docPtr = m.FPDF_CreateNewDocument();\n  if (!docPtr) throw new Error(\"PDFium: failed to create document\");\n\n  try {","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/hooks/tools/adjustContrast/useAdjustContrastOperation.ts#L11-L47","documentation":"Thrown by `renderPdfPageToCanvas` when `HTMLCanvasElement.getContext(\"2d\")` returns null. Browsers return null when they cannot allocate a 2D context — usually because the page is out of GPU/JS memory, an active context-limit has been hit, or the canvas dimensions are too large. Here pages are rendered at scale 2, so a large page yields an enormous backing store.","triggerScenarios":"A very large page (e.g. A0/poster or a high-DPI scan) rendered at scale 2 exceeds the max canvas backing-store size; too many canvases are alive simultaneously (per-tab context limit ~16 in some browsers); the tab is under heavy memory pressure after processing many files.","commonSituations":"Adjust-contrast on a multi-megapixel scanned PDF; running on a low-memory device or a tab that has been open for hours; browser hardware acceleration disabled forcing software allocation failure.","solutions":["Reduce the render scale (2 → 1) for large pages, or compute scale from page dimensions to cap backing-store pixels.","Free intermediate canvases explicitly (set width/height to 0 and drop references) after each page so the context pool is not exhausted.","Cap page pixel dimensions and downscale before getContext, splitting oversized pages into tiles if needed.","Detect navigator/Worker memory and warn the user before attempting very large PDFs."],"exampleFix":"// before\nconst canvas = document.createElement(\"canvas\");\ncanvas.width = viewport.width;\ncanvas.height = viewport.height;\nconst ctx = canvas.getContext(\"2d\");\nif (!ctx) throw new Error(\"Canvas 2D context unavailable\");\n\n// after — downscale to a max backing-store size and retry\nconst MAX_PIXELS = 4096 * 4096;\nlet scale = 2;\nlet viewport = page.getViewport({ scale });\nwhile (viewport.width * viewport.height > MAX_PIXELS && scale > 0.5) {\n  scale -= 0.25;\n  viewport = page.getViewport({ scale });\n}\nconst canvas = document.createElement(\"canvas\");\ncanvas.width = viewport.width;\ncanvas.height = viewport.height;\nconst ctx = canvas.getContext(\"2d\");\nif (!ctx) throw new Error(`Canvas 2D context unavailable (page ${pageNumber} at scale ${scale})`);","handlingStrategy":"validation","validationCode":"// Cap backing-store size before requesting a 2D context\nconst MAX_PIXELS = 4096 * 4096;\nlet scale = 2;\nlet vp = page.getViewport({ scale });\nwhile (vp.width * vp.height > MAX_PIXELS && scale > 0.5) {\n  scale -= 0.25;\n  vp = page.getViewport({ scale });\n}","typeGuard":"function has2dContext(c: CanvasRenderingContext2D | null): c is CanvasRenderingContext2D {\n  return c !== null;\n}","tryCatchPattern":"try {\n  return await renderPdfPageToCanvas(pdf, pageNumber, scale);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Canvas 2D context unavailable\") {\n    // retry once at a lower scale, then warn the user\n    return await renderPdfPageToCanvas(pdf, pageNumber, Math.max(scale / 2, 0.5));\n  }\n  throw e;\n}","preventionTips":["Downscale oversized pages before rendering so the backing store stays within browser limits.","Free intermediate canvases (zero out width/height) after each page to avoid hitting the context limit.","Warn users before processing extremely large PDFs in the adjust-contrast tool."],"tags":["canvas","pdfjs","memory","adjust-contrast","browser-limits"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}