Stirling-Tools/Stirling-PDF · error · Error
Could not get canvas context
Error message
Could not get canvas context
What it means
`HTMLCanvasElement.getContext('2d')` returns null instead of throwing when the 2D rendering context cannot be acquired. This guard fires in `generateThumbnailFromPDF` (and the sibling `generatePageThumbnail`), which both create a throwaway canvas to render a pdf.js page into a data-URL thumbnail. A null return means the browser refused to hand out a 2D context for that canvas element.
Source
Thrown at frontend/editor/src/core/hooks/usePDFProcessor.ts:62
);
// Internal function to generate thumbnail from already-opened PDF
const generateThumbnailFromPDF = useCallback(
async (
pdf: any,
pageNumber: number,
scale: number = 0.5,
): Promise<string> => {
const page = await pdf.getPage(pageNumber);
const viewport = page.getViewport({ scale });
const canvas = document.createElement("canvas");
canvas.width = viewport.width;
canvas.height = viewport.height;
const context = canvas.getContext("2d");
if (!context) {
throw new Error("Could not get canvas context");
}
await page.render({ canvasContext: context, viewport }).promise;
return canvas.toDataURL();
},
[],
);
const processPDFFile = useCallback(
async (file: File): Promise<PDFDocument> => {
setLoading(true);
setError(null);
try {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfWorkerManager.createDocument(arrayBuffer);
const totalPages = pdf.numPages;
View on GitHub (pinned to 9ef20dcab8)
Solutions
- Recycle a single shared canvas across thumbnail renders instead of calling `document.createElement('canvas')` per page, and reset it via `context.clearRect(0,0,canvas.width,canvas.height)` between renders.
- Rate-limit concurrent thumbnail generation (queue with a small concurrency cap, e.g. 3-4) so the live-context budget is never exhausted.
- Before rendering, guard `const ctx = canvas.getContext('2d'); if (!ctx) return placeholderThumbnail();` to degrade gracefully rather than aborting the whole file.
- For headless test environments, configure the runner with `--use-gl=swiftshader` / a software WebGL backend so canvas contexts are allocatable.
Example fix
// before
const canvas = document.createElement("canvas");
canvas.width = viewport.width;
canvas.height = viewport.height;
const context = canvas.getContext("2d");
if (!context) {
throw new Error("Could not get canvas context");
}
// after (graceful degradation + reusable canvas)
const context = canvas.getContext("2d");
if (!context) {
console.warn(`Canvas 2D context unavailable for page ${pageNumber}; using placeholder`);
return PLACEHOLDER_THUMBNAIL_DATA_URL;
} Defensive patterns
Strategy: fallback
Validate before calling
// Check before requesting the context
function canGet2dContext(): boolean {
try {
const probe = document.createElement("canvas");
return probe.getContext("2d") !== null;
} catch {
return false;
}
}
// Concurrency-cap thumbnail generation so the live-context budget isn't hit
const MAX_CONCURRENT_THUMBS = 4;
async function pool<T>(items: T[], n: number, fn: (t: T) => Promise<T>): Promise<T[]> { /* simple semaphore */ return items; } Type guard
// A canvas whose 2D context is actually available
function hasUsable2dContext(canvas: HTMLCanvasElement): canvas is HTMLCanvasElement & { getContext(c: "2d"): CanvasRenderingContext2D } {
return canvas.getContext("2d") !== null;
} Try / catch
try {
const context = canvas.getContext("2d");
if (!context) return PLACEHOLDER_THUMBNAIL_DATA_URL;
await page.render({ canvasContext: context, viewport }).promise;
return canvas.toDataURL();
} catch (e) {
console.warn('Thumbnail render failed, using placeholder', e);
return PLACEHOLDER_THUMBNAIL_DATA_URL;
} Prevention
- Reuse a single shared canvas across a batch of thumbnail renders instead of allocating one per page.
- Cap concurrent thumbnail renders (e.g. 3-4) to stay under the browser's context budget.
- Always provide a placeholder data-URL fallback so one missing context never aborts the whole file.
- In headless test runners, enable a software GL backend (`--use-gl=swiftshader`).
When it happens
Trigger: Calling `generatePageThumbnail`/`generateThumbnailFromPDF` in a tight loop (e.g. generating thumbnails for a 100+ page PDF) exhausts the browser's per-tab canvas-context budget; Safari/WebKit hard-caps live contexts. It also returns null in headless/SSR environments where `document.createElement('canvas')` has no GPU/2D backend, or if `OffscreenCanvas`/a non-2D context was already requested on that element.
Common situations: Thumbnail generation for large PDFs; running the editor in a headless browser (Puppeteer/Playwright) or an environment without a real compositor; Safari with many simultaneous canvases; memory pressure causing the browser to deny context allocation.
Related errors
- Could not get canvas context
- Canvas 2D context unavailable
- Database not initialized
- File ${file.name} appears to be corrupted
- Failed to decode image
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/095ebbdf9f0b201c.
Report an issue: GitHub.