Stirling-Tools/Stirling-PDF · error · Error

Could not get canvas context

Error message

Could not get canvas context

What it means

Identical mechanism to error 20: `renderPageThumbnail` creates a fresh canvas and requests a 2D context; null triggers the throw. The only differences are the caller (page-editor thumbnail at low/med/high quality) and that it serialises to JPEG.

Source

Thrown at frontend/editor/src/core/services/enhancedPDFProcessingService.ts:431

  /**
   * Render a page thumbnail with specified quality
   */
  private async renderPageThumbnail(
    page: any,
    quality: "low" | "medium" | "high",
  ): Promise<string> {
    const scales = { low: 0.2, medium: 0.5, high: 0.8 }; // Reduced low quality for page editor
    const scale = scales[quality];

    const viewport = page.getViewport({ scale, rotation: 0 });
    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("image/jpeg", 0.8); // Use JPEG for better compression
  }

  /**
   * Create a ProcessedFile object
   */
  private createProcessedFile(
    file: File,
    pages: PDFPage[],
    totalPages: number,
  ): ProcessedFile {
    return {
      id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      pages,
      totalPages,

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Reuse one canvas per quality tier and clear between renders rather than allocating per page.
  2. Cap concurrent thumbnail renders with a queue and skip/defer low-priority pages.
  3. Return a placeholder JPEG when the context is null so the page editor stays usable.
  4. In tests, run with a software GL backend so contexts are allocatable.

Example fix

// before
const context = canvas.getContext("2d");
if (!context) {
  throw new Error("Could not get canvas context");
}

// after
const context = canvas.getContext("2d");
if (!context) {
  return PLACEHOLDER_JPEG_DATA_URL;
}
Defensive patterns

Strategy: fallback

Validate before calling

// Concurrency-cap page-editor thumbnail renders
const MAX = 4; // keep under the browser's live-context budget

Type guard

function has2d(canvas: HTMLCanvasElement): canvas is HTMLCanvasElement & { getContext(c:"2d"): CanvasRenderingContext2D } {
  return canvas.getContext("2d") !== null;
}

Try / catch

try {
  const ctx = canvas.getContext("2d");
  if (!ctx) return PLACEHOLDER_JPEG_DATA_URL;
  await page.render({ canvasContext: ctx, viewport }).promise;
  return canvas.toDataURL("image/jpeg", 0.8);
} catch {
  return PLACEHOLDER_JPEG_DATA_URL;
}

Prevention

When it happens

Trigger: Bulk page-editor thumbnail rendering across many pages; headless/no-compositor environments; Safari canvas-context exhaustion; memory pressure during editing of a large multi-page document.

Common situations: Opening the page editor on a large PDF triggers dozens of concurrent `renderPageThumbnail` calls; testing the editor under Playwright without GPU; Safari denying context allocation after heavy use.

Related errors


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