DocsPDFium JavaScript APIExamplesRender Page to Canvas

Rendering PDF Pages to Canvas

Description

One of the most common operations when working with PDFs is rendering pages to display them visually. PDFium provides powerful page rendering capabilities through its WebAssembly API. This guide explains how to render PDF pages to an HTML canvas element with complete control over scaling, rotation, and other rendering parameters.

Interactive Example

This interactive example demonstrates how to render PDF pages to a canvas element. You can navigate between pages, zoom in/out, and rotate the page:

PREVIEW:Render PDF Page to Canvas
Loading...
100%

The Basic Workflow

Rendering a PDF page to a canvas involves several steps:

  1. Initialize PDFium
  2. Load the PDF document
  3. Load the specific page to render
  4. Create a bitmap for rendering
  5. Render the page to the bitmap
  6. Transfer the bitmap data to the canvas
  7. Clean up resources

Prerequisites

The examples in this guide use the initializePdfium helper function from our Getting Started guide. This function properly initializes the PDFium library, including calling the required PDFiumExt_Init() function. Make sure to include this initialization code in your application before trying the examples below.

Example Implementation

Here’s a complete implementation for loading a PDF document and rendering its pages to a canvas:

import { initializePdfium } from './initialize-pdfium'; // Note: The initializePdfium function is a helper that initializes the PDFium library. // For the full implementation, see: /docs/pdfium/getting-started /** * Loads a PDF document and returns an object with methods to access and render it. * This avoids loading the document multiple times for different operations. */ export async function loadPdfDocument(pdfData: Uint8Array) { // Initialize PDFium const pdfium = await initializePdfium(); // Allocate memory for the PDF data const filePtr = pdfium.pdfium.wasmExports.malloc(pdfData.length); pdfium.pdfium.HEAPU8.set(pdfData, filePtr); // Load the document const docPtr = pdfium.FPDF_LoadMemDocument(filePtr, pdfData.length, 0); if (!docPtr) { const error = pdfium.FPDF_GetLastError(); pdfium.pdfium.wasmExports.free(filePtr); // Handle password-protected documents if (error === 4) { return { hasPassword: true, pageCount: 0, close: () => {}, // No-op since no document was loaded getPageCount: () => 0, renderPage: async () => { throw new Error('Document is password protected'); } }; } throw new Error(`Failed to load PDF: ${error}`); } // Get page count const pageCount = pdfium.FPDF_GetPageCount(docPtr); // Return an object with document info and rendering capabilities return { hasPassword: false, pageCount, // Close the document and free resources close: () => { pdfium.FPDF_CloseDocument(docPtr); pdfium.pdfium.wasmExports.free(filePtr); }, // Get the current page count (useful if pages are added/removed) getPageCount: () => pdfium.FPDF_GetPageCount(docPtr), // Render a specific page to a canvas renderPage: async ( pageIndex: number, scale: number = 1.0, rotation: number = 0, canvas: HTMLCanvasElement, dpr: number = window.devicePixelRatio || 1.0 ): Promise<{ width: number; height: number; }> => { // Check if the page index is valid if (pageIndex < 0 || pageIndex >= pageCount) { throw new Error(`Invalid page index: ${pageIndex}. Document has ${pageCount} pages.`); } // Load the page const pagePtr = pdfium.FPDF_LoadPage(docPtr, pageIndex); if (!pagePtr) { throw new Error(`Failed to load page ${pageIndex}`); } try { // Get the page dimensions const width = pdfium.FPDF_GetPageWidthF(pagePtr); const height = pdfium.FPDF_GetPageHeightF(pagePtr); // Calculate the scaled dimensions with device pixel ratio const effectiveScale = scale * dpr; let scaledWidth = Math.floor(width * effectiveScale); let scaledHeight = Math.floor(height * effectiveScale); // Apply rotation if requested let rotateFlag = 0; switch (rotation) { case 90: rotateFlag = 1; break; case 180: rotateFlag = 2; break; case 270: rotateFlag = 3; break; } // Swap dimensions for 90 and 270 degree rotations if (rotation === 90 || rotation === 270) { [scaledWidth, scaledHeight] = [scaledHeight, scaledWidth]; } // Create a bitmap for rendering const bitmapPtr = pdfium.FPDFBitmap_Create(scaledWidth, scaledHeight, 0); if (!bitmapPtr) { throw new Error('Failed to create bitmap'); } try { // Set canvas CSS dimensions for proper display canvas.style.width = `${scaledWidth / dpr}px`; canvas.style.height = `${scaledHeight / dpr}px`; // Set actual canvas buffer size canvas.width = scaledWidth; canvas.height = scaledHeight; // Fill the bitmap with white background pdfium.FPDFBitmap_FillRect(bitmapPtr, 0, 0, scaledWidth, scaledHeight, 0xFFFFFFFF); // Render the page to the bitmap pdfium.FPDF_RenderPageBitmap( bitmapPtr, pagePtr, 0, 0, scaledWidth, scaledHeight, rotateFlag, 16 // Use FPDF_REVERSE_BYTE_ORDER flag for correct color representation ); // Get the bitmap buffer const bufferPtr = pdfium.FPDFBitmap_GetBuffer(bitmapPtr); if (!bufferPtr) { throw new Error('Failed to get bitmap buffer'); } const bufferSize = scaledWidth * scaledHeight * 4; // RGBA // Create a COPY of the buffer data to prevent memory issues // This is crucial - we must slice() to copy the data instead of using a view const buffer = new Uint8Array( pdfium.pdfium.HEAPU8.buffer, pdfium.pdfium.HEAPU8.byteOffset + bufferPtr, bufferSize ).slice(); // Create ImageData from the buffer copy const imageData = new ImageData( new Uint8ClampedArray(buffer.buffer), scaledWidth, scaledHeight ); // Draw the image data to the canvas const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Failed to get 2D context from canvas'); } ctx.putImageData(imageData, 0, 0); // Return the dimensions adjusted for DPR return { width: scaledWidth / dpr, height: scaledHeight / dpr }; } finally { // Clean up bitmap pdfium.FPDFBitmap_Destroy(bitmapPtr); } } finally { // Clean up page pdfium.FPDF_ClosePage(pagePtr); } } }; }

Usage Example

Here’s a simple example of how to use the rendering functions with a canvas element:

// Get a reference to the canvas element const canvas = document.getElementById('pdf-canvas'); // Load a PDF file async function loadAndRenderPdf() { try { // Fetch the PDF file const response = await fetch('sample.pdf'); const arrayBuffer = await response.arrayBuffer(); const pdfData = new Uint8Array(arrayBuffer); // Load the document const pdfDocument = await loadPdfDocument(pdfData); // Set up page navigation let currentPage = 0; const pageCount = pdfDocument.pageCount; // Render the first page await pdfDocument.renderPage(currentPage, 1.0, 0, canvas); // Set up navigation document.getElementById('prev-button').addEventListener('click', async () => { if (currentPage > 0) { currentPage--; await pdfDocument.renderPage(currentPage, 1.0, 0, canvas); } }); document.getElementById('next-button').addEventListener('click', async () => { if (currentPage < pageCount - 1) { currentPage++; await pdfDocument.renderPage(currentPage, 1.0, 0, canvas); } }); // Clean up when done window.addEventListener('beforeunload', () => { pdfDocument.close(); }); } catch (error) { console.error('Error rendering PDF:', error); } } loadAndRenderPdf();

Memory Management Considerations

When rendering PDF pages, proper memory management is critical to prevent memory leaks:

  1. Bitmap cleanup: Always destroy bitmaps using FPDFBitmap_Destroy when you’re done with them.

  2. Page cleanup: Always close pages using FPDF_ClosePage when you’re done with them.

  3. Buffer copying: When accessing the bitmap buffer, create a copy of the data instead of using a direct view. This prevents issues with memory being freed while you’re still using the data:

// GOOD - Create a copy of the buffer data const buffer = new Uint8Array( pdfium.pdfium.HEAPU8.buffer, pdfium.pdfium.HEAPU8.byteOffset + bufferPtr, bufferSize ).slice(); // BAD - Direct view can lead to problems if memory is freed const buffer = new Uint8Array( pdfium.pdfium.HEAPU8.buffer, pdfium.pdfium.HEAPU8.byteOffset + bufferPtr, bufferSize );

Performance Tips

  1. Cache rendered pages: If you’re building a PDF viewer, consider caching rendered pages to avoid re-rendering the same page repeatedly.

  2. Progressive rendering: For large documents, implement progressive loading and rendering to improve the perceived performance.

  3. Render at appropriate resolution: Adjust the scale based on the display device. For high-DPI displays, you may want to render at a higher scale.

  4. Use Web Workers: Consider using Web Workers for rendering to avoid blocking the main thread.

Handling High-DPI Displays

The example implementation includes support for high-DPI displays (like Retina displays) through the optional dpr parameter, which defaults to window.devicePixelRatio. This ensures optimal rendering quality on all devices.

When working with high-DPI displays:

  • The canvas is rendered at the physical resolution (scaled by DPR) for sharpness
  • CSS dimensions are set to maintain the correct visual size
  • The returned dimensions are adjusted to match the logical size rather than the buffer size

This approach prevents PDFs from appearing blurry on high-resolution displays while maintaining the expected size in the user interface.

// Example of explicitly setting DPR when rendering await pdfDocument.renderPage( 0, // First page 1.0, // 100% scale 0, // No rotation canvas, // Canvas element window.devicePixelRatio // Current device's pixel ratio );
  • FPDF_LoadMemDocument - Load a PDF document from memory
  • FPDF_GetPageCount - Get the number of pages in a document
  • FPDF_LoadPage - Load a specific page from a document
  • FPDF_GetPageWidthF - Get the width of a page in points
  • FPDF_GetPageHeightF - Get the height of a page in points
  • FPDFBitmap_Create - Create a bitmap for rendering
  • FPDFBitmap_FillRect - Fill a rectangle in a bitmap
  • FPDF_RenderPageBitmap - Render a page to a bitmap
  • FPDFBitmap_GetBuffer - Get the buffer containing bitmap data
  • FPDFBitmap_Destroy - Destroy a bitmap and release resources
  • FPDF_ClosePage - Close a page and release resources
  • FPDF_CloseDocument - Close a document and release resources
  • FPDF_GetLastError - Get the error code for the last failed operation
Last updated on April 5, 2025