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:
- A basic getting started page
- A formatted text examples page
- 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:
- What Is DsPdfJS?
- What You’ll Build
- Prerequisites
- Getting Started with Browser PDF Generation Using JavaScript
- Build the PDF Generation Workflow
- Troubleshooting Image Loading
- Understanding ObjectManager
- 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.
Watch the Release Webinar to Learn More 🎥
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:
- Hello from DsPdfJS
- Formatted drawText examples
- Image drawing and clipping example
The sample uses:
- PdfDocument to create the PDF
- newPageContext() to add pages
- drawText() to add text
- measureText() to position formatted text blocks
- Image.load() and drawImage() to add an image
- ObjectManager to manage WebAssembly-backed DsPdfJS objects
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.
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.
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.
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.

A download link should appear. When you open the generated PDF, it should contain three pages:
- Hello from DsPdfJS
- Formatted Text Examples
- 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.