Skip to main content Skip to footer

JavaScript PDF Generation in the Browser using a Wasm Based API

Quick Start Guide
Tutorial Concept

Learn how to programmatically generate multi-page PDFs with formatted text and images using a JavaScript PDF API library.

What You Will Need npm package: @mescius/ds-pdf
Or Download the Latest Release
Controls Referenced Document Solutions for PDF JS - JavaScript PDF API
Online Demo Explorer | Documentation

Document Solutions for PDF JS, or DsPdfJS, is a JavaScript PDF API that allows developers to create, edit, and process PDF documents directly in modern web applications. With DsPdfJS, PDF generation can happen entirely in the browser using WebAssembly, without requiring server-side PDF processing, native dependencies, or a separate document-generation service.

In this getting started tutorial, we’ll build a browser-based PDF generation sample step-by-step. We’ll start with a simple “Hello World” PDF, then add a second page with formatted text examples, and finally add a third page that demonstrates how to draw and clip an image.

By the end, you’ll have a single PDF generated in the browser with:

  1. A basic getting started page
  2. A formatted text examples page
  3. An image examples page

Click here to download the full sample, or follow along by creating it yourself with the blog below!

Generate PDFs in the Browser with JavaScript:

  1. What Is DsPdfJS?
  2. What You’ll Build
  3. Prerequisites
  4. Getting Started with Browser PDF Generation Using JavaScript
  5. Build the PDF Generation Workflow
  6. Troubleshooting Image Loading
  7. Understanding ObjectManager
  8. Next Steps

What Is DsPdfJS?

Document Solutions for PDF JS is a client-side JavaScript PDF library designed for browser-based PDF workflows. It runs using WebAssembly, allowing PDF documents to be generated directly in the browser.

This makes DsPdfJS useful for scenarios such as:

  • Generating invoices, reports, forms, or certificates in web apps
  • Creating downloadable PDF files without server round trips
  • Adding text, images, and other content dynamically
  • Building JavaScript-first PDF workflows
  • Keeping PDF processing local to the user’s browser

Because DsPdfJS runs client-side, it does not require server processing or native installation. Users can generate PDF files directly from a browser-based application.


What You’ll Build

This tutorial creates a browser application with a Generate PDF button. When clicked, the application creates a PDF document entirely in the browser and displays a download link.

The final PDF will include three pages:

  1. Hello from DsPdfJS
  2. Formatted drawText examples
  3. Image drawing and clipping example

The sample uses:

DsPdfJS objects are backed by WebAssembly resources, and each object belongs to an ObjectManager, which controls the lifetime of those resources. For a standalone npm/Vite application, this tutorial uses an explicit ObjectManager, passes it to the document and image APIs, and disposes it after the PDF has been saved. 


Prerequisites

Before getting started, make sure you have:

  • Node.js 16 or higher
  • npm 7 or higher
  • A modern browser with WebAssembly support, such as Chrome, Edge, Firefox, or Safari
  • A code editor, such as Visual Studio Code
  • Basic knowledge of HTML and JavaScript 

This sample uses Vite as a lightweight development server.


Getting Started with Browser PDF Generation Using JavaScript

Step 1: Create a New Project

Create a new project folder and initialize it with npm:

mkdir dspdf-browser-demo
cd dspdf-browser-demo
npm init -y

This creates a basic package.json file for the project.

Step 2: Install DsPdfJS

Install the DsPdfJS npm package:

npm install @mescius/ds-pdf

This adds the DsPdfJS package and the WebAssembly assets required by the library.

Step 3: Install and Configure Vite

Modern browsers require WebAssembly files to be loaded from a web server. For local development, install Vite:

npm install vite --save-dev

Then update the scripts section of your package.json file:

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview"
}

These scripts let you run the project locally, build it for production, and preview the production build.

Step 4: Add an Image File

The final page of the PDF will add an image, so include a sample image to your project.

Create a public/images folder, then add an image named sample.png.

Your project should look like this:

dspdf-browser-demo/
├── public/
│   └── images/
│       └── sample.png
├── index.html
├── package.json
└── node_modules/

With Vite, files in the public folder are served from the site root. That means this file:

public/images/sample.png

can be loaded in JavaScript using:

/images/sample.png

Step 5: Create the Basic HTML Page

Create an index.html file in the root of your project. Start with a simple page containing a button and a hidden download link:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>DsPdfJS Browser Example – npm + Vite</title>
</head>
<body>
    <h1>Create a PDF in the Browser with DsPdfJS</h1>
    <p>
        This example shows how to generate a PDF file in the browser using
        <strong>DsPdfJS</strong> (<code>@mescius/ds-pdf</code>) installed via npm.
    </p>
    <button id="generateBtn">Generate PDF</button>
    <br /><br />
    <a id="downloadLink" style="display:none;"></a>
    <script type="module">
        // The DsPdfJS code will go here.
    </script>
</body>
</html>

Next, we’ll start building the PDF generation logic inside the <script type="module"> block.


Build the PDF Generation Workflow

Step 6: Import DsPdfJS and Configure WebAssembly

Inside the <script type="module"> block, import the DsPdfJS APIs used in this tutorial:

import {
    connectDsPdf,
    DsPdfConfig,
    ObjectManager,
    PdfDocument,
    Image,
    Format,
    Font,
    StandardPdfFont
} from "@mescius/ds-pdf";

import wasmUrl from "@mescius/ds-pdf/assets/DsPdf.wasm?url";

DsPdfConfig.wasmUrl = wasmUrl;

The wasmUrl import tells Vite how to locate the DsPdfJS WebAssembly file.

Step 7: Add a Helper Function to Load the Image

The image page needs to load an image file as a byte array before passing it to Image.load(). Add this helper function below the imports:

async function loadFileAsArray(url) {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`Failed to load file: ${url}`);
    }
    return new Uint8Array(await response.arrayBuffer());
}

This function uses fetch() to load the image from the public/images folder and converts it into a Uint8Array.

Step 8: Create the Generate PDF Function

Now add the main generatePdf() function. This function initializes DsPdfJS, creates an ObjectManager, creates a new PDF document, and save the finished PDF.

async function generatePdf() {
    const connected = await connectDsPdf();
    if (!connected) {
        alert("Failed to initialize DsPdfJS.");
        return;
    }
    const om = new ObjectManager();
    try {
        const doc = new PdfDocument(om);
        // Page content will be added here.
        const pdfData = doc.savePdf();
        const buffer = pdfData.buffer || pdfData;
        const blob = new Blob([buffer], { type: "application/pdf" });
        const url = URL.createObjectURL(blob);
        const link = document.getElementById("downloadLink");
        link.href = url;
        link.download = "dspdf-browser-example.pdf";
        link.textContent = "Download generated PDF";
        link.style.display = "inline";
    } catch (error) {
        console.error(error);
        alert("Failed to generate PDF. Check the browser console for details.");
    } finally {
        om.dispose();
    }
}

The ObjectManager is disposed in the finally block so the WebAssembly-backed resources are released after the PDF has been generated.

Now connect the function to the button:

document
    .getElementById("generateBtn")
    .addEventListener("click", generatePdf);

At this point, the app is ready to generate a PDF, but the PDF has no page content yet. Next, we’ll add Page 1.


Page 1: Create a Hello World PDF Page

The first page will be a simple getting started example. It creates a page and draws text near the top-left corner.

Inside generatePdf(), add a new page and draw two text blocks:

const doc = new PdfDocument(om);

// Page 1: Hello World
const page1 = doc.newPageContext();

page1.drawText(
    { text: "Hello from DsPdfJS!", fontSize: 24 },
    50,
    50
);

page1.drawText(
    {
        text: "This PDF was generated entirely in the browser using JavaScript and WebAssembly.",
        fontSize: 12
    },
    50,
    90
);

const pdfData = doc.savePdf();

If you run the project now, the generated PDF will contain a single page with basic text.

Hello World Example PDF | Generated using a PDF JavaScript API
Simple generated text on the first page of the PDF

Next, we’ll continue building the same PDF by adding a second page with more advanced text formatting.


Page 2: Add Formatted Text Examples

DsPdfJS supports multiple ways to draw text. You can use a Format object, pass font attributes directly, or draw a text run object.

For the second page, add the following code immediately after the Page 1 code and before doc.savePdf().

const page2 = doc.newPageContext();
const inch = page2.resolution;
const left = inch;
const gap = inch / 4;
const maxWidth = page2.width - inch * 2;
const sampleText = "The quick brown fox jumps over the lazy dog. ".repeat(3);
const timesBold = Font.getPdfFont(om, StandardPdfFont.TimesBold);
const timesBoldItalic = Font.getPdfFont(om, StandardPdfFont.TimesBoldItalic);
const courierBoldItalic = Font.getPdfFont(om, StandardPdfFont.CourierBoldItalic);
let top = inch;
page2.drawText(
    { text: "Formatted Text Examples", fontSize: 20 },
    left,
    top
);
top += 40;
// 1. Draw text using a Format object.
const redFormat = new Format(om, {
    font: timesBold,
    fontSize: 16,
    foreColor: "Red"
});
page2.drawText(sampleText, redFormat, left, top, maxWidth);
top += page2.measureText(sampleText, redFormat, maxWidth).height + gap;
// 2. Draw text using specified font attributes.
page2.drawText(sampleText, timesBoldItalic, 16, "Green", left, top, maxWidth);
top += page2.measureText(sampleText, timesBoldItalic, 16, maxWidth).height + gap;
// 3. Draw a text run.
const run = {
    text: sampleText,
    font: courierBoldItalic,
    fontSize: 16,
    foreColor: "Blue"
};
page2.drawText(run, left, top, maxWidth);
top += page2.measureText(run, maxWidth).height + gap;
// 4. Draw a final short line.
page2.drawText(
    "The end.",
    new Format(om, { fontSize: 12, foreColor: "Purple" }),
    left,
    top,
    100
);

This second page demonstrates several useful text drawing techniques:

  • Drawing text with a Format object
  • Drawing text with a specific font, font size, and color
  • Drawing a text run object 

 Using measureText() to position each text block below the previous one 

The key idea is that measureText() helps calculate how much vertical space the previous text block used. That makes it easier to build dynamic layouts without hard-coding every y coordinate.

Formatted Text in Generated PDF | Developer Solution | PDF JavaScript API
Generated formatted text on a newly-made PDF

Now the same PDF contains two pages: a simple getting started page and a formatted text page.

Next, we’ll add a third page with an image.


Page 3: Add an Image Example

For the third page, we’ll load an image from the project’s public/images folder, draw it at one-fifth size, and then draw a clipped version of the same image.

Add this code after the Page 2 code and before doc.savePdf().

const page3 = doc.newPageContext();
const margin = page3.resolution / 2;
page3.drawText(
    { text: "Image Drawing Example", fontSize: 20 },
    margin,
    margin
);
const imageBytes = await loadFileAsArray("/images/sample.png");
// Important: pass the ObjectManager first.
const img = Image.load(om, imageBytes);
const scale = 0.2;
const imageTop = margin + 40;
// Draw image at one-fifth size.
page3.drawImage(
    img,
    margin,
    imageTop,
    img.width * scale,
    img.height * scale,
    {
        keepAspectRatio: true,
        opacity: 1
    }
);
// Draw image again with a clipped region.
page3.drawImage(
    img,
    margin,
    imageTop + img.height * scale + 24,
    img.width * scale,
    img.height * scale,
    {
        clipBounds: {
            x: margin,
            y: imageTop + img.height * scale + 24,
            width: 200,
            height: 150
        },
        keepAspectRatio: true
    }
);

The image is loaded with:

const imageBytes = await loadFileAsArray("/images/sample.png");
const img = Image.load(om, imageBytes);

The first line loads the image file from the browser. The second line creates a DsPdfJS image object from those bytes.

Then drawImage() places the image on the page.

The first drawImage() call draws the full image at one-fifth size. The second drawImage() call uses clipBounds to show only a portion of the image.

Draw Images on Programmatically Generated PDFs using a JavaScript PDF API Library
Two images added to the PDF, one clipped in half

Full Final Code

Here is the completed index.html file with all three pages built into the same PDF.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>DsPdfJS Browser Example – npm + Vite</title>
</head>
<body>
    <h1>Create a PDF in the Browser with DsPdfJS</h1>
    <p>
        This example shows how to generate a PDF file in the browser using
        <strong>DsPdfJS</strong> (<code>@mescius/ds-pdf</code>) installed via npm.
    </p>
    <button id="generateBtn">Generate PDF</button>
    <br /><br />
    <a id="downloadLink" style="display:none;"></a>
    <script type="module">
        import {
            connectDsPdf,
            DsPdfConfig,
            ObjectManager,
            PdfDocument,
            Image,
            Format,
            Font,
            StandardPdfFont
        } from "@mescius/ds-pdf";
        import wasmUrl from "@mescius/ds-pdf/assets/DsPdf.wasm?url";
        DsPdfConfig.wasmUrl = wasmUrl;
        async function loadFileAsArray(url) {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`Failed to load file: ${url}`);
            }
            return new Uint8Array(await response.arrayBuffer());
        }
        async function generatePdf() {
            const connected = await connectDsPdf();
            if (!connected) {
                alert("Failed to initialize DsPdfJS.");
                return;
            }
            const om = new ObjectManager();
            try {
                const doc = new PdfDocument(om);
                /*
                 * Page 1: Hello World
                 */
                const page1 = doc.newPageContext();
                page1.drawText(
                    { text: "Hello from DsPdfJS!", fontSize: 24 },
                    50,
                    50
                );
                page1.drawText(
                    {
                        text: "This PDF was generated entirely in the browser using JavaScript and WebAssembly.",
                        fontSize: 12
                    },
                    50,
                    90
                );

                /*
                 * Page 2: Formatted Text Examples
                 */
                const page2 = doc.newPageContext();
                const inch = page2.resolution;
                const left = inch;
                const gap = inch / 4;
                const maxWidth = page2.width - inch * 2;
                const sampleText = "The quick brown fox jumps over the lazy dog. ".repeat(3);
                const timesBold = Font.getPdfFont(om, StandardPdfFont.TimesBold);
                const timesBoldItalic = Font.getPdfFont(om, StandardPdfFont.TimesBoldItalic);
                const courierBoldItalic = Font.getPdfFont(om, StandardPdfFont.CourierBoldItalic);
                let top = inch;
                page2.drawText(
                    { text: "Formatted Text Examples", fontSize: 20 },
                    left,
                    top
                );
                top += 40;
                const redFormat = new Format(om, {
                    font: timesBold,
                    fontSize: 16,
                    foreColor: "Red"
                });
                page2.drawText(sampleText, redFormat, left, top, maxWidth);
                top += page2.measureText(sampleText, redFormat, maxWidth).height + gap;
                page2.drawText(sampleText, timesBoldItalic, 16, "Green", left, top, maxWidth);
                top += page2.measureText(sampleText, timesBoldItalic, 16, maxWidth).height + gap;
                const run = {
                    text: sampleText,
                    font: courierBoldItalic,
                    fontSize: 16,
                    foreColor: "Blue"
                };
                page2.drawText(run, left, top, maxWidth);
                top += page2.measureText(run, maxWidth).height + gap;
                page2.drawText(
                    "The end.",
                    new Format(om, { fontSize: 12, foreColor: "Purple" }),
                    left,
                    top,
                    100
                );
                /*
                 * Page 3: Image Drawing Example
                 */
                const page3 = doc.newPageContext();
                const margin = page3.resolution / 2;
                page3.drawText(
                    { text: "Image Drawing Example", fontSize: 20 },
                    margin,
                    margin
                );
                const imageBytes = await loadFileAsArray("/images/sample.png");
                const img = Image.load(om, imageBytes);
                const scale = 0.2;
                const imageTop = margin + 40;
                page3.drawImage(
                    img,
                    margin,
                    imageTop,
                    img.width * scale,
                    img.height * scale,
                    {
                        keepAspectRatio: true,
                        opacity: 1
                    }
                );
                page3.drawImage(
                    img,
                    margin,
                    imageTop + img.height * scale + 24,
                    img.width * scale,
                    img.height * scale,
                    {
                        clipBounds: {
                            x: margin,
                            y: imageTop + img.height * scale + 24,
                            width: 200,
                            height: 150
                        },
                        keepAspectRatio: true
                    }
                );
                /*
                 * Save the PDF and create a browser download link.
                 */
                const pdfData = doc.savePdf();
                const buffer = pdfData.buffer || pdfData;
                const blob = new Blob([buffer], { type: "application/pdf" });
                const url = URL.createObjectURL(blob);
                const link = document.getElementById("downloadLink");
                link.href = url;
                link.download = "dspdf-browser-example.pdf";
                link.textContent = "Download generated PDF";
                link.style.display = "inline";
            } catch (error) {
                console.error(error);
                alert("Failed to generate PDF. Check the browser console for details.");
            } finally {
                om.dispose();
            }
        }
        document
            .getElementById("generateBtn")
            .addEventListener("click", generatePdf);
    </script>
</body>
</html>

Step 9: Run the Project

Start the Vite development server:

npm run dev

Then open the local development URL in your browser:

http://localhost:5173

Click Generate PDF.

Browser Based PDF Generation Sample | Developer Solution | JS PDF API

A download link should appear. When you open the generated PDF, it should contain three pages:

  1. Hello from DsPdfJS
  2. Formatted Text Examples
  3. Image Drawing Example

Troubleshooting Image Loading

If the PDF generation fails when loading the image, first test the image URL directly in the browser:

http://localhost:5173/images/sample.png

If the image does not load, confirm that the file is located here:

public/images/sample.png

and that the code loads it using:

await loadFileAsArray("/images/sample.png")

With Vite, the public folder is served from the root of the application, so you do not include public in the runtime URL.


Understanding ObjectManager in This Sample

DsPdfJS objects are JavaScript wrappers around WebAssembly-backed resources. Because JavaScript garbage collection does not automatically release those underlying WebAssembly resources, DsPdfJS uses ObjectManager to define object lifetimes. 

In this sample, the ObjectManager is created before the PDF document:

const om = new ObjectManager();
const doc = new PdfDocument(om);

The same object manager is also passed to other DsPdfJS object creation APIs, such as fonts, formats, and images:

const timesBold = Font.getPdfFont(om, StandardPdfFont.TimesBold);
const redFormat = new Format(om, { font: timesBold, fontSize: 16 });
const img = Image.load(om, imageBytes);

Finally, the object manager is disposed after the PDF is saved:

finally {
    om.dispose();
}

This keeps the sample explicit and makes resource cleanup easier to understand.


Next Steps

This tutorial introduced the basics of generating PDFs in the browser with DsPdfJS. You created a single document, added multiple pages, drew basic text, added formatted text, measured text layout, loaded an image, and drew both full and clipped image content. You can view more projects similar to the above by visiting the DsPdfJS demos, and you can learn more about the product by viewing the DsPdfJS documentation

Ready to dive deeper into Document Solutions for PDF JavaScript? Get started with a 30-day free trial here!

comments powered by Disqus