Stirling-Tools/Stirling-PDF · error · Error

Canvas 2D context unavailable

Error message

Canvas 2D context unavailable

What it means

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.

Source

Thrown at frontend/editor/src/core/hooks/tools/adjustContrast/useAdjustContrastOperation.ts:29

import { applyAdjustmentsToCanvas } from "@app/components/tools/adjustContrast/utils";
import { pdfWorkerManager } from "@app/services/pdfWorkerManager";
import { createFileFromApiResponse } from "@app/utils/fileResponseUtils";
import { getPdfiumModule, saveRawDocument } from "@app/services/pdfiumService";
import { copyRgbaToBgraHeap } from "@app/utils/pdfiumBitmapUtils";
import type { PDFDocumentProxy } from "pdfjs-dist";

async function renderPdfPageToCanvas(
  pdf: PDFDocumentProxy,
  pageNumber: number,
  scale: number,
): Promise<HTMLCanvasElement> {
  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 ctx = canvas.getContext("2d");
  if (!ctx) throw new Error("Canvas 2D context unavailable");
  await page.render({ canvasContext: ctx, canvas, viewport }).promise;
  return canvas;
}

// Render, adjust, and assemble all pages of a single PDF into a new PDF using PDFium
async function buildAdjustedPdfForFile(
  file: File,
  params: AdjustContrastParameters,
): Promise<File> {
  const m = await getPdfiumModule();
  const arrayBuffer = await file.arrayBuffer();
  const pdf = await pdfWorkerManager.createDocument(arrayBuffer, {});
  const pageCount = pdf.numPages;

  const docPtr = m.FPDF_CreateNewDocument();
  if (!docPtr) throw new Error("PDFium: failed to create document");

  try {

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Reduce the render scale (2 → 1) for large pages, or compute scale from page dimensions to cap backing-store pixels.
  2. Free intermediate canvases explicitly (set width/height to 0 and drop references) after each page so the context pool is not exhausted.
  3. Cap page pixel dimensions and downscale before getContext, splitting oversized pages into tiles if needed.
  4. Detect navigator/Worker memory and warn the user before attempting very large PDFs.

Example fix

// before
const canvas = document.createElement("canvas");
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("Canvas 2D context unavailable");

// after — downscale to a max backing-store size and retry
const MAX_PIXELS = 4096 * 4096;
let scale = 2;
let viewport = page.getViewport({ scale });
while (viewport.width * viewport.height > MAX_PIXELS && scale > 0.5) {
  scale -= 0.25;
  viewport = page.getViewport({ scale });
}
const canvas = document.createElement("canvas");
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error(`Canvas 2D context unavailable (page ${pageNumber} at scale ${scale})`);
Defensive patterns

Strategy: validation

Validate before calling

// Cap backing-store size before requesting a 2D context
const MAX_PIXELS = 4096 * 4096;
let scale = 2;
let vp = page.getViewport({ scale });
while (vp.width * vp.height > MAX_PIXELS && scale > 0.5) {
  scale -= 0.25;
  vp = page.getViewport({ scale });
}

Type guard

function has2dContext(c: CanvasRenderingContext2D | null): c is CanvasRenderingContext2D {
  return c !== null;
}

Try / catch

try {
  return await renderPdfPageToCanvas(pdf, pageNumber, scale);
} catch (e) {
  if (e instanceof Error && e.message === "Canvas 2D context unavailable") {
    // retry once at a lower scale, then warn the user
    return await renderPdfPageToCanvas(pdf, pageNumber, Math.max(scale / 2, 0.5));
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/a8fca56a346f9c8c. Report an issue: GitHub.