pdfium

repository·main·Indexed 41 days ago

https://github.com/hyzyla/pdfium

A TypeScript/JavaScript wrapper for the Google PDFium library, providing high-performance PDF rendering via WebAssembly in both browser and Node.js environments. It allows developers to render PDF files to images with support for custom render engines, scaling, and color spaces.

Tokens
14.1K
Snippets
56
Records
63
Agent score
51%

What's inside pdfium

  1. Overview of @hyzyla/pdfium features and use cases

    main

    @hyzyla/pdfium is a TypeScript/JavaScript wrapper for the WebAssembly build of the PDFium library (the same engine used in Google Chrome).

    Key Features

    • Zero dependencies: The PDFium WebAssembly binary is bundled directly with the package.
    • Type-safe: Includes built-in TypeScript definitions.
    • Cross-platform: Compatible with both browser and Node.js runtimes.

    Primary Use Case

    • Rendering PDF files to images: This is the main functionality provided. For detailed implementation steps, see the "Render PDF to image" guide.
  2. Optimize parallel rendering with a Worker Pool

    main

    To avoid the high cost of WASM compilation in every worker, compile the WebAssembly.Module once on the main thread and share it with multiple workers using instantiateWasm.

    // main.ts
    import wasmUrl from "@hyzyla/pdfium/pdfium.wasm?url";
    
    const wasmBytes = await fetch(wasmUrl).then(r => r.arrayBuffer());
    const wasmModule = await WebAssembly.compile(wasmBytes); // Compile ONCE
    
    const workers: Worker[] = Array.from({ length: 4 }, () => {
      const w = new Worker(new URL("./my-render-worker.ts", import.meta.url), { type: "module" });
      w.postMessage({ type: "init", wasmModule });
      return w;
    });
    
    // my-render-worker.ts
    import { PDFiumLibrary } from "@hyzyla/pdfium";
    
    let libPromise: Promise<PDFiumLibrary> | null = null;
    
    self.addEventListener("message", async (event) => {
      if (event.data.type === "init") {
        libPromise = PDFiumLibrary.init({
          instantiateWasm: ((imports, success) => {
            WebAssembly.instantiate(event.data.wasmModule, imports).then(inst => {
              success(inst, event.data.wasmModule);
            });
            return {};
          }) as never,
        });
        return;
      }
      // ... handle render jobs ...
    });
  3. Configure the 'sharp' render engine

    main

    The 'sharp' engine is recommended for rendering to PNG. Since sharp is an optional dependency, you must install it manually in your project before use.

    yarn add sharp
    pnpm install sharp
    npm install sharp
    # yarn add sharp
    # pnpm install sharp
    npm install sharp
  4. Compress extracted images using sharp in the render method

    main

    Extracted images are rendered without compression by default. To reduce file size, pass a custom async function to the render method. This function receives PDFiumPageRenderOptions (containing the raw data, width, and height) and should return a Promise<Uint8Array> containing the compressed buffer (e.g., via sharp).

    import sharp from 'sharp';
    import { PDFiumPageRenderOptions } from '@hyzyla/pdfium';
    
    const document = await library.loadDocument(buff);
    const page = document.getPage(0);
    const object = page.getObject(0);
    
    const image = await object.render({
      render: async (options: PDFiumPageRenderOptions): Promise<Uint8Array> => {
        return await sharp(options.data, {
          raw: {
            width: options.width,
            height: options.height,
            channels: 4,
          },
        })
          .jpeg({ quality: 80 }) // Use JPEG format with 80% quality
          .toBuffer();
      },
    });
  5. How to use @hyzyla/pdfium to render PDF pages

    main

    To use the library, follow these steps:

    1. Initialize the library: Call PDFiumLibrary.init() once for your application. This returns a library instance.
    2. Load a document: Use library.loadDocument(buffer) to load a PDF from a buffer. If the document is encrypted, pass the password as the second argument.
    3. Access pages: Iterate over pages using document.pages().
    4. Render a page: Call page.render(options) on a page object. You must provide a render function in the options to handle the raw bitmap data returned by the library.
    5. Cleanup: Always call document.destroy() and library.destroy() when finished to free resources.

    Note: The render function receives a PDFiumPageRenderOptions object containing data (raw bitmap), width, and height. You are responsible for converting this raw data into a usable format (like PNG) using a library like sharp or your own implementation.

    import { PDFiumLibrary } from "@hyzyla/pdfium";
    import { promises as fs } from 'fs';
    import sharp from 'sharp';
    
    // Example render function using sharp to convert raw bitmap to PNG
    async function renderFunction(options: PDFiumPageRenderOptions) { 
      return await sharp(options.data, {
        raw: {
          width: options.width,
          height: options.height,
          channels: 4,
        },
      })
        .png()
        .toBuffer();
    }
    
    async function main() {
      const buff = await fs.readFile('test2.pdf');
    
      // 1. Initialize
      const library = await PDFiumLibrary.init();
    
      // 2. Load
      const document = await library.loadDocument(buff);
    
      // 3. Iterate and Render
      for (const page of document.pages()) {
        const image = await page.render({
          scale: 3, // 3x scale
          render: renderFunction, 
        });
    
        // image.data contains the result from your render function
        await fs.writeFile(`output/${page.number}.png`, Buffer.from(image.data));
      }
    
      // 4. Cleanup
      document.destroy();
      library.destroy();
    }
    
    main();
  6. Run the website in local development mode

    main

    Start a local development server using yarn start. This command will automatically open a browser window and support live reloading, meaning most changes will be reflected immediately without a server restart.

    $ yarn start
  7. Initialize the PDFiumLibrary

    main

    To use the package, you must create an instance of PDFiumLibrary using the init() method. This instance can be created once and reused throughout your application's lifetime. When you are finished with the library, call destroy() to clean up resources.

    import { PDFiumLibrary } from '@hyzyla/pdfium';
    
    const library = await PDFiumLibrary.init();
    
    // ... do something with the library
    
    library.destroy();
  8. Quick setup for Browser testing (CDN or Base64)

    main

    For rapid prototyping or testing in the browser, you can use specialized entry points that avoid manual .wasm URL management.

    Note: These methods are not recommended for production due to potential security risks, slower loading (CDN), or increased bundle size (Base64).

    • CDN approach: Uses @hyzyla/pdfium/browser/cdn to load the binary from a remote source.
    • Base64 approach: Uses @hyzyla/pdfium/browser/base64 to load the binary from an encoded string within your bundle.
    // Load .wasm from CDN
    import { PDFiumLibrary } from "@hyzyla/pdfium/browser/cdn";
    const library = await PDFiumLibrary.init();
    
    // Load .wasm from base64 encoded string
    import { PDFiumLibrary } from "@hyzyla/pdfium/browser/base64";
    const library = await PDFiumLibrary.init();
  9. Use Web Workers for non-blocking PDF rendering

    main

    Rendering PDFs is CPU-intensive and can freeze the UI. Use @hyzyla/pdfium/worker to move PDFium operations into a Web Worker. This provides a Promise-based API that mirrors the in-process PDFiumLibrary but dispatches calls via postMessage.

    Key differences from in-process API:

    • Every method is async: All calls return Promises because they cross the worker boundary.
    • No custom render callbacks: You cannot pass JS functions to render(). Instead, render() returns raw bitmap bytes which you must process on the main thread.
    • Opaque handles: PDFium objects (documents, pages) live in the worker; the client holds opaque handle strings.
    import { PDFiumWorkerClient } from "@hyzyla/pdfium/worker";
    import wasmUrl from "@hyzyla/pdfium/pdfium.wasm?url"; // Vite
    
    // 1. Spawn the worker
    const pdfium = await PDFiumWorkerClient.spawn({ wasmUrl });
    
    // 2. Load a document
    const bytes = new Uint8Array(await (await fetch("/my.pdf")).arrayBuffer());
    const doc = await pdfium.loadDocument(bytes);
    
    // 3. Render a page
    const page = await doc.getPage(0);
    const { data, width, height } = await page.render({ scale: 2 });
    
    // 4. Cleanup
    await doc.destroy();
    await pdfium.destroy();
  10. Initialize the PDFium library with initBase()

    main

    To use @hyzyla/pdfium, you must first initialize the library using the static initBase method. This method loads the WebAssembly module and configures the underlying PDFium engine.

    Browser Environment Requirements In a browser environment, you must provide a way to locate the WASM binary. You can do this by providing a wasmUrl, a wasmBinary (ArrayBuffer), or a custom instantiateWasm function. Failure to provide one of these in the browser will result in an error.

    Quick Setup (Not recommended for production) You can use the following imports for rapid prototyping:

    • @hyzyla/pdfium/browser/cdn
    • @hyzyla/pdfium/browser/base64
    import { PDFiumLibrary } from '@hyzyla/pdfium';
    
    const library = await PDFiumLibrary.initBase({
      vendor: async (options) => {
        // The vendor function is responsible for loading the underlying WASM module
        // using the provided options (like locateFile or wasmBinary).
        return await someVendorLoader(options);
      },
      wasmUrl: '/path/to/pdfium.wasm'
    });
  11. Initialize PDFiumLibrary in Node.js

    main

    In a Node.js environment, PDFiumLibrary.init() does not require any additional configuration. The .wasm binary is automatically loaded from your node_modules folder.

    import { PDFiumLibrary } from '@hyzyla/pdfium';
    
    const library = await PDFiumLibrary.init();