@mescius/ds-pdf
v9.2.1
Published
Create, edit, and sign PDFs in the browser and Node.js. WebAssembly based library.
Downloads
161
Maintainers
Readme
Document Solutions for PDF JS (DsPdfJS)
DsPdfJS is a PDF library for JavaScript and TypeScript providing fast, memory-efficient PDF and image processing in browsers and on the server.
Powered by WebAssembly, it runs in modern browsers and in Node.js, Deno, and Bun, with no runtime dependencies. Its object model follows the PDF specification, giving programmatic access to document properties, pages, fonts, annotations, and forms, alongside high-level APIs for building documents with formatted text, graphics, and images.
Features
- Create, load, modify, save, and inspect PDF documents
- Support for modern PDF standards, including PDF/A and PDF/UA conformance levels
- E-signatures: PKCS#7 signing, trusted timestamps, and OCSP revocation responses
- Tagged PDF support via the document structure tree
- Export pages to raster or vector image formats
- Merge and split documents
- Draw text, shapes, paths, and images with high-level graphics APIs
- Text layout engine with wrapping, alignment, spacing, tabs, RTL (including Kashida), and vertical writing
- Font embedding and subsetting
- Raster and vector images: PNG, JPEG, SVG, SVGZ
- Create, edit, fill, and flatten AcroForm forms
- Annotations, links, text markup, and rich media
- Find and replace text with configurable search options and exact match positions
- Redaction that permanently removes sensitive content
- Encryption and security APIs
- Image processing: resizing, transforming, filtering, drawing, and bitmap generation
- TypeScript definitions included
Compatibility
- Environments: Modern browsers; Node.js, Deno, Bun, other Node-compatible runtimes
- Module formats: ES Modules, CommonJS, UMD
- TypeScript: Type definitions included
- Runtime dependencies: None
Installation
npm install @mescius/ds-pdfQuick Start
In Node.js, this is the whole thing - the Wasm module is located automatically:
import { connectDsPdf, ObjectManager, PdfDocument } from "@mescius/ds-pdf";
import { writeFileSync } from "node:fs";
if (!(await connectDsPdf())) throw new Error("Failed to initialize DsPdfJS.");
using om = new ObjectManager();
const doc = new PdfDocument(om);
const ctx = doc.newPageContext();
ctx.drawText({ text: "Hello, World!", fontSize: 40 }, 72, 72);
const pdfData = doc.savePdf();
writeFileSync("hello.pdf", pdfData);
const doc2 = PdfDocument.load(om, pdfData);
const page = doc2.pages.getAt(0);
page.context.drawText({ text: "JPEG export", fontSize: 30, foreColor: "Green" }, 72, 132);
page.context.drawRect(page.mediaBox, { lineWidth: 10, lineColor: "Red" });
const jpegData = page.saveAsJpeg();
writeFileSync("hello.jpeg", jpegData);Save the above code as quick-start.mjs and run:
node quick-start.mjsIn the browser, add one line telling the library where the Wasm module lives - see Loading the WebAssembly module for the options.
Evaluating without a license key
DsPdfJS runs without a license key in Node.js, Deno, and Bun, and in a browser served
from a local host name - localhost, 127.0.0.1, ::1, a *.local or other dot-less
host name, or file://. You do not need to contact sales to try it: install the package,
run the code above, and you will get a working PDF.
Keyless builds stamp a watermark on generated output.
A browser page served from any other host name requires a key. There is no watermarked
fallback there: connectDsPdf() rejects with License Not Found.
To remove the watermark, or to run DsPdfJS in a browser on a non-local host name, request a free 30-day trial key (see How to Get Trial Keys) and apply it before connecting:
import { DsPdfConfig } from "@mescius/ds-pdf";
await DsPdfConfig.setLicenseKey("YOUR_LICENSE_KEY");A commercial license is required for production use.
Loading the WebAssembly module
DsPdfJS ships the Wasm module in several forms so it can fit different build setups. Pick one of the following.
Option A: Embedded (simplest; recommended for bundlers)
The Wasm binary is inlined into a JavaScript module, so there is no separate asset to copy, host, or resolve. This is usually the right choice for Vite, webpack, Rollup, Next.js, and edge runtimes, where asset URLs are the most common source of friction.
import { DsPdfConfig, connectDsPdf } from "@mescius/ds-pdf";
import { embeddedWasmFactory } from "@mescius/ds-pdf/wasm-embedded";
DsPdfConfig.wasmFactory = embeddedWasmFactory;
await connectDsPdf();No wasmUrl needed. The trade-off is a larger JavaScript bundle, since the binary
travels base64-encoded inside it.
Importing the module registers the factory on its own too, but assign it explicitly as
shown above: the package is marked side-effect-free, so a bare
import "@mescius/ds-pdf/wasm-embedded"; may be dropped by tree-shaking and leave no
module registered.
Option B: External file with an explicit URL
Serve DsPdf.wasm as a static asset and point the library at it. This keeps your
JavaScript bundle small and lets the browser cache the binary separately.
import { DsPdfConfig, connectDsPdf } from "@mescius/ds-pdf";
DsPdfConfig.wasmUrl = "/assets/DsPdf.wasm";
await connectDsPdf();Copy the file into your served output as part of your build, for example from
node_modules/@mescius/ds-pdf/assets/DsPdf.wasm. A CDN or versioned URL works too:
DsPdfConfig.wasmUrl = "https://cdn.example.com/v9.2/DsPdf.wasm";Option C: Automatic detection (Node.js only)
If neither wasmUrl nor wasmFactory is set, DsPdfJS looks for the module under
node_modules/@mescius/ds-pdf/assets/ relative to the current working directory.
Convenient for scripts and tests; set the path explicitly for production, since the
lookup depends on the working directory. Auto-detection also prints a console.info
notice recommending an explicit path - set wasmUrl to silence it.
Option D: Custom factory
For full control over how the binary is fetched - a private CDN, an authenticated endpoint, a preloaded buffer:
import { DsPdfConfig } from "@mescius/ds-pdf";
import { externalWasmFactory } from "@mescius/ds-pdf/wasm-external";
DsPdfConfig.wasmFactory = async () => {
const wasmBinary = await (await fetch("https://cdn.example.com/DsPdf.wasm")).arrayBuffer();
return externalWasmFactory({ wasmBinary });
};externalWasmFactory takes either { wasmBinary } or { wasmUrl }. DsPdfJS calls
wasmFactory() without arguments, so fetch the binary inside your factory as shown.
One factory wins.
wasmFactorytakes precedence overwasmUrl, and importing either@mescius/ds-pdf/wasm-embeddedor@mescius/ds-pdf/wasm-externalsetswasmFactoryas a side effect of the import itself, not when the factory is first called. A stray import anywhere in the app therefore overrides an Option BwasmUrlset elsewhere:wasm-embeddedquietly loads the embedded binary and your URL is ignored, whilewasm-externalmakesconnectDsPdf()returnfalse(it is invoked with no arguments, soexternalWasmFactorythrows).
Set
DsPdfConfig.verbose = trueto log which module was resolved and from where.
Usage
Browser (ES Modules)
import {
DsPdfConfig, connectDsPdf, PdfDocument, pushObjectManager, popObjectManager
} from "@mescius/ds-pdf";
DsPdfConfig.wasmUrl = "/assets/DsPdf.wasm";
// required off localhost, see "Evaluating without a license key":
// await DsPdfConfig.setLicenseKey("...");
export async function createDocument() {
if (!(await connectDsPdf())) throw new Error("Failed to initialize DsPdfJS.");
pushObjectManager();
try {
const doc = new PdfDocument();
const page = doc.pages.addNew();
page.context.drawText({ text: "Hello, World!", fontSize: 20 }, 72, 72);
return doc.savePdf(); // Uint8Array
} finally {
popObjectManager();
}
}Browser (UMD / plain script tag)
<script src="node_modules/@mescius/ds-pdf/umd/ds-pdf.js"></script>
<script>
async function createDocument() {
// The UMD bundle exposes the global 'DocSol'
DocSol.DsPdfConfig.wasmUrl = "/assets/DsPdf.wasm";
// await DocSol.DsPdfConfig.setLicenseKey("..."); // required off localhost
if (!(await DocSol.connectDsPdf())) throw new Error("Failed to initialize DsPdfJS.");
const om = new DocSol.ObjectManager();
try {
const doc = new DocSol.PdfDocument(om);
const page = doc.pages.addNew();
page.context.drawText({ text: "Hello, World!", fontSize: 20 }, 72, 72);
return doc.savePdf();
} finally {
om.dispose();
}
}
createDocument()
.then(pdfData => console.log("PDF created:", pdfData.length, "bytes"))
.catch(err => console.error(err));
</script>Node.js (CommonJS)
const { connectDsPdf, PdfDocument, ObjectManager } = require("@mescius/ds-pdf");
const { writeFileSync } = require("node:fs");
(async () => {
if (!(await connectDsPdf())) throw new Error("Failed to initialize DsPdfJS.");
const om = new ObjectManager();
try {
const doc = new PdfDocument(om);
const page = doc.pages.addNew();
page.context.drawText({ text: "Hello, World!", fontSize: 20 }, 72, 72);
writeFileSync("hello.pdf", doc.savePdf());
} finally {
om.dispose();
}
})();Memory management: ObjectManager
Many objects are backed by Wasm memory, which is not reclaimed by the JavaScript garbage collector. Scope them with one of these patterns so intermediates are released:
// Push / pop - implicit, scoped
pushObjectManager();
try { /* ... */ } finally { popObjectManager(); }
// Explicit instance
const om = new ObjectManager();
try { /* ... */ } finally { om.dispose(); }
// Decorator
@withObjectManager
async buildReport() { /* ... */ }Objects created inside a scope are disposed when it ends. Do not hold references to them past that point.
Notes
- Coordinates in PDF drawing contexts are page units (points, 72 per inch) by default, and can be changed.
- Coordinates in image drawing contexts are pixels (96 per inch).
Latest changes
[9.2.1] - 10-Sep-2026
Added
- Added the
PdfPage.saveAsJpeg()method. (DOC-7829)
Changed
- Loading a PDF document is no longer limited to the first 5 pages when the library is used without a license. (DOC-7871)
Fixed
- Fixed JPEG2000 support issues. (DOC-7824)
- Fixed an issue where the TypeScript types were not resolved for the recommended wasm-embedded import, caused by the order of the
exportsconditions. (DOC-7828) - Fixed an issue where the Node.js WASM auto-detection never ran when the package was loaded through an ESM
import, soconnectDsPdf()failed withENOENT. (DOC-7837) - Fixed a crash when using non-canonical names for named colors. (DOC-7902)
- Fixed an issue where the appearance stream of a
FreeTextAnnotationwas not regenerated in certain scenarios. (DOC-7887) - Fixed an issue where half-width spaces were drawn incorrectly in vertical text when
Format.uprightInVerticalTextwas set totrue. (DOC-7903)
[9.2.0] - 14-Aug-2026
Added
- E-signature support:
- Signing documents with PKCS#7 signatures (the
Pkcs7SignerandPkcs7SignerBaseclasses). (DOC-7384) - Embedding trusted timestamps into signatures. (DOC-7411)
- Requesting and embedding OCSP revocation responses. (DOC-7412)
- Storing signature validation data in the Document Security Store (the
DocumentSecurityStoreclass). (DOC-7469) - The
Signatureclass, representing a signature already present in a document. (DOC-7470)
- Signing documents with PKCS#7 signatures (the
- Support for tagged PDFs: the document structure tree is now available through the
StructTreeRootandStructElementclasses. (DOC-7575) - Support for 3D annotations. (DOC-7681)
- Support for the PDF/A-4 conformance levels (
PdfAConformanceLevel.PdfA4,PdfA4e,PdfA4f) and for PDF/UA-2 (theMetadata.pdfUaandMetadata.pdfUaRevproperties). (DOC-7682) - The
PdfDocument.outputIntentsproperty and the relevant classes. (DOC-7512) - The
PdfDocument.viewerPreferencesproperty and theViewerPreferencesclass. (DOC-7471) - The
PdfDocument.pageModeandPdfDocument.pageLayoutproperties. (DOC-7455) - The
PdfDocument.pageLabelingRangesproperty and the related page labeling classes. (DOC-7624) - The
PdfDocument.getFonts()method, which enumerates the fonts used in a document. (DOC-7729) - The
PdfPage.annotationsTabsOrderproperty. (DOC-7511) - Support for saving linearized ("fast web view") PDFs via the new
SaveMode.Linearizedmember. (DOC-7655) - Support for Form XObjects, allowing reusable content streams to be created and drawn on pages. (DOC-7575)
- Support for custom annotation appearance streams. (DOC-7575)
- A JPEG2000 image decoder, so JPEG2000 images in PDF documents are now decoded. (DOC-7656)
- The missing properties of the classes derived from
ActionBase, such asActionLaunch,ActionHide,ActionImportDataandActionSound. (DOC-7654) - The missing
CheckBoxFieldmembers:hasRadioButtonBehavior,getCheckedAppearanceStreamName(),getCheckedAppearanceStreamNames(),setCheckedAppearanceStreamName()andsetCheckedAppearanceStreamNames(). (DOC-7628) - The missing
IccProfilemembers: thensetter, therangeproperty and themetadataproperty. (DOC-7675, DOC-7676) - The
LineBreakingRules.WhiteSpacemode, which breaks lines only at white spaces or mandatory break characters. (DOC-7651)
See CHANGELOG.md inside the package for the full change history.
Resources
- Demos
- Getting Started Guide
- Documentation
- API Reference
- Licensing information and FAQ
- How to get trial keys
- Technical Support and Bug Reports
- DsPdfJS on GitHub
License
Free to evaluate, see Evaluating without a license key.
A commercial license is required for production use, and each purchase includes one year of updates and support. Contact MESCIUS Sales for pricing and licensing options.
Other Document Solutions products
- Document Solutions for PDF .NET
- Document Solutions for Word .NET
- Document Solutions for Excel .NET
- Document Solutions for Excel Java
- Document Solutions for Imaging .NET
JavaScript viewers: PDF Viewer and Editor · Data Viewer · Image Viewer and Editor
