Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

PDF.js Administrator & Developer Guide

Generated June 2026 from mozilla/pdf.js at 9071f45

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is for the people who deploy, integrate, build, and extend PDF.js: web/application administrators embedding the viewer, platform engineers bundling pdfjs-dist, and contributors working on the engine itself. It covers the system architecture, the build pipeline, the complete configuration surface, security posture, deployment strategies, and the internals deep enough that an engineer (or an AI) could reason about — or reconstruct — the system.

For end-user and library-consumer documentation (how to call getDocument, render a page, use the viewer toolbar), see the companion User Guide.


Table of Contents

  1. Executive Summary
  2. System Architecture
  3. Repository Structure
  4. Requirements
  5. The Build System
  6. Configuration Reference
  7. Worker Configuration
  8. Deployment Strategies
  9. Bundler Integration
  10. Security
  11. Engine Internals (Deep Dive)
  12. Testing Strategy
  13. Operations & Maintenance
  14. Browser Extensions
  15. Troubleshooting (Operational)
  16. Potential Improvements & Tech Debt
  17. Quick Reference

1. Executive Summary

PDF.js is a pure-JavaScript PDF parsing and rendering engine. It takes raw PDF bytes and produces pixels on an HTML5 <canvas>, selectable text overlays, interactive form layers, and structured document metadata — without any native code, browser plugin, or server-side rendering.

Key architectural properties an administrator should know:

  • Two-thread design. A thin main-thread API (src/display/) drives a heavy Web Worker engine (src/core/). They communicate over a structured message protocol. This keeps host pages responsive during CPU-bound parsing.
  • Zero runtime dependencies. The shipped library bundles everything it needs. Every entry in package.json is build/test tooling. There is no runtime npm dependency tree to audit or patch.
  • Self-contained, static deployment. The distribution is static files (.mjs modules + data folders). It needs only an HTTP server. There is no application server, database, or background service.
  • WebAssembly acceleration, optional. Image codecs (JBIG2, OpenJPEG/JPEG 2000) and the embedded-JavaScript sandbox (QuickJS) ship as WASM and are loaded on demand.
  • Defense-in-depth for untrusted input. PDFs are untrusted by definition; the architecture isolates parsing on a worker and confines any embedded document JavaScript to a sandboxed engine.

Two consumption models:

Model What you ship Who configures it
Library (pdfjs-dist) pdf.mjs + pdf.worker.mjs (+ data folders) bundled into your app Application developers, via getDocument parameters and GlobalWorkerOptions.
Viewer (web/) The full prebuilt reader (viewer.html + assets) Administrators, via the viewer AppOptions/preferences surface.

2. System Architecture

2.1 The threads

            MAIN THREAD                         WORKER THREAD
 ┌───────────────────────────────┐   ┌──────────────────────────────────┐
 │ src/display/  (the public API)│   │ src/core/  (the parser + engine) │
 │                               │   │                                  │
 │  getDocument()                │   │  WorkerMessageHandler.setup()    │
 │  PDFDocumentProxy ───────────►│   │  PDFDocument / Catalog / XRef    │
 │  PDFPageProxy                 │   │  Parser / Lexer / evaluator      │
 │  RenderTask (canvas painting) │◄──┤  Fonts, images, CMaps, colors    │
 │  TextLayer / AnnotationLayer  │   │  OperatorList builder            │
 └───────────────┬───────────────┘   └─────────────────┬────────────────┘
                 │   structured postMessage protocol    │
                 └──────────  MessageHandler  ──────────┘
                       (src/shared/message_handler.js)

 ┌───────────────────────────────┐   ┌──────────────────────────────────┐
 │ web/  (viewer application)    │   │ QuickJS WASM sandbox             │
 │  PDFViewerApplication, tools  │   │ src/scripting_api/  (PDF JS)     │
 └───────────────────────────────┘   └──────────────────────────────────┘

Main thread (src/display/). Hosts the API surface consumers call. Its objects (PDFDocumentProxy, PDFPageProxy) are proxies — they don't hold parsed data; they marshal requests to the worker and apply results (drawing operator lists onto a canvas, building text/annotation layers in the DOM).

Worker thread (src/core/). The actual PDF engine. It parses the cross-reference table and object graph, decrypts, decodes fonts and images, evaluates content streams, and emits operator lists — compact arrays of drawing instructions — back to the main thread for painting. This is the largest and most complex module (≈1.85M chars of source).

Shared (src/shared/). Code used by both threads: the MessageHandler transport, the constants/enums (OPS, AnnotationType, PermissionFlag, …), utility math, and the MurmurHash used for caching.

Scripting sandbox (src/scripting_api/). Implements the Acrobat-compatible JavaScript object model (app, doc, field, …) that PDFs can script. It runs inside a QuickJS WebAssembly engine, isolated from the host.

XFA (src/core/xfa/). A self-contained renderer for the legacy XML Forms Architecture (≈405K chars). Off by default; enabled with enableXfa.

2.2 The message protocol

src/shared/message_handler.js implements a small RPC layer over postMessage. Three call styles:

Method Semantics
send(action, data, transfers) Fire-and-forget message.
sendWithPromise(action, data) Request/response; resolves with the worker's reply.
sendWithStream(action, data, strategy) Backpressure-aware ReadableStream — used for operator lists and text content, which arrive in chunks.

Errors are marshalled across the boundary and re-thrown as the correct exception subclass (PasswordException, InvalidPDFException, ResponseException, AbortException, UnknownErrorException) via wrapReason().

Note

The streaming channel is what enables progressive rendering: a page's operator list streams from the worker so the main thread can begin painting before parsing finishes, and rendering can be cancelled mid-stream (the worker stops producing).

2.3 The rendering data flow

PDF bytes
  └─► XRef + Catalog parse        (worker)
        └─► Page content stream    (worker: Lexer → Parser)
              └─► PartialEvaluator  (worker: resolves fonts, images, color)
                    └─► OperatorList (worker → main, streamed)
                          └─► CanvasGraphics.executeOperatorList()  (main)
                                └─► pixels on <canvas>

Fonts are converted (where possible) to OpenType and loaded via the Font Loading API; when that's disabled (disableFontFace, the Node default), a built-in glyph-path renderer draws them directly.

2.4 The fake-worker fallback

If Web Workers are unavailable (Node.js, or a CSP/security failure creating the worker), PDFWorker transparently sets up a fake worker: the same engine runs on the main thread behind a LoopbackPort that mimics the message channel. Functionality is identical; only concurrency is lost. This is why the library "just works" in Node without a worker file.


3. Repository Structure

pdf.js/
├── src/                      Engine source (the library)
│   ├── pdf.js                Main-thread entry → builds globalThis.pdfjsLib
│   ├── pdf.worker.js         Worker entry → WorkerMessageHandler
│   ├── pdf.sandbox.js        Scripting sandbox entry (QuickJS bridge)
│   ├── pdf.scripting.js      Scripting initialization
│   ├── pdf.image_decoders.js Standalone image-decoder entry
│   ├── display/              Main-thread API (api.js, canvas.js, layers…)  (36 files)
│   ├── core/                 Parser + render engine (86 files, ~1.85M chars)
│   │   └── xfa/              XFA forms renderer (27 files)
│   ├── scripting_api/        Acrobat JS object model (18 files)
│   └── shared/               Cross-thread code: util.js, message_handler.js …
├── web/                      Prebuilt viewer application (101 files)
│   ├── app.js                PDFViewerApplication (viewer controller)
│   ├── app_options.js        AppOptions / defaultOptions (the config surface)
│   ├── viewer.html           Viewer markup
│   └── internal/             Internal viewer support modules
├── extensions/chromium/      Chrome/Chromium extension wrapper
├── examples/                 Integration examples (node, webpack, components…)
├── l10n/                     Localization — 130+ locales (Fluent .ftl)
├── test/                     Unit (jasmine), integration (puppeteer), reference
├── docs/                     Project website / generated API docs sources
├── gulpfile.mjs              The build orchestrator (all build tasks)
└── package.json             Dev/build tooling only — no runtime deps

Important

Three source folders dominate complexity and deserve the most review attention: src/core/ (the parser/engine), web/ (the viewer), and test/unit/ (the largest test corpus, ≈1.2M chars). The localization tree (l10n/, 130+ single-file locale folders) inflates the file count but is low-complexity.


4. Requirements

4.1 Build / development environment

Item Requirement
Node.js >= 22.13.0 or >= 24 (from package.json engines).
Package manager npm (a package-lock.json is maintained).
Build driver Gulp 5 (gulp / gulp-cli), invoked via npx gulp <task>.
Bundler Webpack 5 + Babel 7 + Terser (driven by gulp).
Browsers for testing Firefox and Chrome, automated through Puppeteer.
License Apache-2.0.

4.2 Runtime environment (deploying the viewer/library)

Item Requirement
Host Any static HTTP server (nginx, Apache, a CDN, an object store with web hosting).
Protocol Serve over http(s)://. The file:// protocol breaks workers and fetch.
MIME types .mjs → text/javascript; .wasm → application/wasm; .pdf → application/pdf.
Browser Modern evergreen browsers. Use the legacy/ build for older targets / Node.

Warning

Misconfigured MIME types are the most common deployment failure. If your server serves .mjs as text/plain or .wasm as application/octet-stream, module loading and WASM instantiation fail silently or with opaque errors. Verify these mappings before going live.


5. The Build System

PDF.js builds everything through a single gulpfile.mjs. Gulp 5 composes tasks as async functions with series() (sequential) and parallel() (concurrent); each task pipes source globs through Babel, PostCSS, and Webpack/Terser to an output folder.

5.1 Common tasks

# Install dev tooling first
npm install

# Discover every available task and its dependency tree
npx gulp --tasks

# Produce the generic (browser) build → build/generic/
npx gulp generic

# Produce the minified generic build
npx gulp minified

# Build the standalone components bundle
npx gulp components

# Build the image-decoders-only bundle
npx gulp image_decoders

# Build the Firefox extension / mozcentral integration
npx gulp mozcentral

# Build the Chromium extension → build/chromium/
npx gulp chromium

# Install a freshly built dist into ./node_modules (used by examples)
npx gulp dist-install

# Run the linters / formatters
npx gulp lint

# Run the test suites
npx gulp unittest
npx gulp test

Note

Task names above are the conventional PDF.js gulp targets. Always run npx gulp --tasks against your checked-out revision for the authoritative, version-exact list — the gulpfile is the single source of truth and evolves with the codebase.

5.2 Build-time configuration: PDFJSDev

The source is peppered with PDFJSDev.test("...") and PDFJSDev.eval("...") guards. PDFJSDev is a compile-time preprocessor (resolved by a Babel plugin during the build), not a runtime object. It selects code paths per build target:

Flag Build target
GENERIC The general-purpose browser/npm build.
MOZCENTRAL The build integrated into Firefox (mozilla-central).
CHROME The Chromium extension build.
LIB The library/components build.
TESTING Test builds (enables internal assertions and test hooks).
GECKOVIEW Firefox-for-Android (GeckoView) build.

Because these are resolved at build time, dead branches for other targets are eliminated from the output — one source tree, many tailored bundles. Constants like BUNDLE_VERSION and INTERNAL_EVT are injected via PDFJSDev.eval(...).

5.3 Build outputs

Output Contents
build/generic/build/pdf.mjs Main library.
build/generic/build/pdf.worker.mjs Worker engine.
build/generic/build/pdf.sandbox.mjs Scripting sandbox.
build/generic/web/ Prebuilt viewer + data folders (cmaps, standard_fonts, iccs, wasm).
build/image_decoders/ Image-decoder-only bundle.
build/chromium/ Packaged Chrome extension.

6. Configuration Reference

There are two distinct configuration surfaces. Don't confuse them.

  1. Library (getDocument) parameters — per-document options passed in code. Documented exhaustively in the User Guide → API Reference. These control parsing/rendering behavior for a single document.
  2. Viewer options (AppOptions) — settings for the prebuilt web/ viewer, defined in web/app_options.js. Documented below.

6.1 How viewer options work

Each option in web/app_options.js's defaultOptions is tagged with an OptionKind bitmask that determines where it applies and whether end users can change it:

OptionKind Meaning
BROWSER Provided by the host browser environment (capabilities, integration flags). Not user-settable.
VIEWER Controls the viewer UI/behavior.
API Forwarded to the underlying getDocument call.
WORKER Worker location (workerSrc, workerPort).
PREFERENCE A subset of VIEWER/API options that are exposed as user-persisted preferences.
EVENT_DISPATCH Changing it dispatches a runtime event.

An option marked VIEWER + PREFERENCE is a viewer setting that can be persisted as a user preference; one marked API is passed straight to the engine.

6.2 Viewer (UI) options

Option Default Effect
annotationMode 2 (ENABLE_FORMS) Which annotations render in the viewer.
annotationEditorMode 0 Initial annotation-editor mode (-1 disables editing).
cursorToolOnLoad 0 0 = select tool, 1 = hand tool.
defaultZoomValue "" Initial zoom (e.g. "page-width", "auto", "150"); empty = automatic.
defaultZoomDelay 400 Debounce (ms) before applying zoom changes.
scrollModeOnLoad -1 Scroll mode (-1 = use document/default).
spreadModeOnLoad -1 Page-spread mode at load.
sidebarViewOnLoad -1 Which sidebar panel opens initially.
viewOnLoad 0 Whether to restore the last view position.
textLayerMode 1 Text layer: 0 off, 1 on, 2 on + enhanced.
viewerCssTheme 0 UI theme (0 automatic, light, or dark per build).
toolbarDensity 0 0 normal, 1 compact, 2 touch.
externalLinkTarget 0 Where external links open (new tab, same, etc.).
externalLinkRel "noopener noreferrer nofollow" rel applied to external links.
disablePageLabels false Ignore custom page labels.
disableHistory false Don't update browser history on navigation.
historyUpdateUrl false Reflect the current page/position in the URL.
printResolution 150 Print rasterization DPI.
enablePrintAutoRotate true Auto-rotate landscape pages when printing.
maxCanvasPixels 2**25 Per-canvas pixel ceiling (prevents huge allocations).
maxCanvasDim 32767 Maximum canvas dimension.
imageResourcesPath "./images/" Path to viewer UI images.

6.3 Editor / feature-flag options

Option Default Effect
enableScripting true (non-Chrome) Allow embedded PDF JavaScript execution.
enableComment dev-only Enable the comment editor.
enableSignatureEditor test/dev Enable the signature editor.
enableAltText false Image alt-text authoring.
enableGuessAltText true ML-assisted alt-text suggestions.
enableAltTextModelDownload true Allow downloading the alt-text ML model.
enableAutoLinking true Detect and linkify URLs in text.
enableHighlightFloatingButton dev/test Floating highlight button.
enablePermissions false Honor the PDF's declared permission flags in the UI.
enableHWA true Hardware-accelerated canvas (API option).
enableWebGPU true Attempt WebGPU acceleration.
highlightEditorColors yellow/green/blue/pink/red set Highlighter palette (incl. high-contrast variants).
forcePageColors / pageColorsBackground / pageColorsForeground false / Canvas / CanvasText High-contrast color overrides.

6.4 API-forwarded options (viewer → engine)

These viewer options are passed through to getDocument:

Option Default
cMapUrl build-relative cmaps/ path
cMapPacked true
standardFontDataUrl build-relative standard_fonts/ path
iccUrl build-relative iccs/ path
wasmUrl build-relative wasm/ path
disableAutoFetch false
disableFontFace false
disableRange false
disableStream false
enableXfa true (in the viewer)
maxImageSize -1 (no limit)
useSystemFonts environment-dependent
verbosity 1 (WARNINGS)
pdfBug false
docBaseUrl current document URL

Tip

To harden a kiosk/embedded viewer, the highest-leverage changes are: enableScripting: false (no embedded JS), enablePermissions: true (respect author restrictions), a fixed defaultZoomValue, and disabling editing via annotationEditorMode: -1.


7. Worker Configuration

The worker location is mandatory for the library and is set through GlobalWorkerOptions (one of these two):

// Option A: point at the worker file (most common)
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.min.mjs";

// Option B: provide a preconstructed Worker/port (advanced; e.g. preloaded)
pdfjsLib.GlobalWorkerOptions.workerPort = new Worker("/pdfjs/build/pdf.worker.min.mjs", { type: "module" });

Setter validation (from source): workerSrc must be a string, workerPort must be a Worker instance (or null) — otherwise the setters throw Invalid \workerSrc` type./Invalid `workerPort` type.`

Same-origin handling. When workerSrc is cross-origin (e.g. a CDN), the GENERIC build automatically wraps it in a Blob URL (_createCDNWrapper) so the worker still satisfies the browser's same-origin worker rules. In MOZCENTRAL builds, the worker is preloaded by the host (workerPort via pdfjsPreloadedWorker).

Caution

A Content-Security-Policy that forbids worker-src/blob: will block real workers and silently force the fake-worker fallback (everything runs on the main thread). If you see Warning: Setting up fake worker. in production, inspect your CSP first.


8. Deployment Strategies

8.1 Static viewer deployment (simplest)

  1. Build (npx gulp generic) or download the prebuilt package.
  2. Copy build/generic/web/ and build/generic/build/ to your web root.
  3. Ensure correct MIME types for .mjs and .wasm.
  4. Open web/viewer.html?file=<same-origin-pdf-url>.
# nginx: serve PDF.js assets with correct types
types {
    text/javascript   mjs;
    application/wasm   wasm;
    application/pdf    pdf;
}
location /pdfjs/ {
    alias /var/www/pdfjs/;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

8.2 Library deployment (embed in your app)

Install pdfjs-dist, bundle pdf.mjs, ensure pdf.worker.mjs and the data folders ship to your output, and set workerSrc + the asset URLs at runtime. See Bundler Integration.

8.3 CDN deployment

Reference both library and worker from the same pinned version:

const V = "5.x.y"; // pin one version for both
import * as pdfjsLib from `https://cdn.jsdelivr.net/npm/pdfjs-dist@${V}/build/pdf.min.mjs`;
pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${V}/build/pdf.worker.min.mjs`;

Warning

Version skew between pdf.mjs and pdf.worker.mjs is a top production incident. The API checks its version against the worker's and throws if they differ. Pin both to one version; never mix a CDN worker with a locally bundled library.

8.4 Verification checklist

  • [ ] .mjs served as text/javascript, .wasm as application/wasm.
  • [ ] pdf.worker.mjs loads directly in the browser (returns JS, not 404).
  • [ ] Library and worker are the same version.
  • [ ] Asset folders (cmaps/, standard_fonts/, iccs/, wasm/) reachable and pointed at by the four URLs.
  • [ ] Cross-origin PDFs have CORS headers (or are proxied same-origin).
  • [ ] CSP permits the worker (and blob: if cross-origin worker).
  • [ ] enableScripting set deliberately for your threat model.

9. Bundler Integration

The repository ships a reference webpack setup at examples/webpack/. The essential concerns when bundling pdfjs-dist:

  1. Worker reachability. The worker is a separate file. Either set workerSrc to a path your bundler emits, or use the bundler's worker support. Webpack 5 understands new Worker(new URL(...), { type: "module" }) natively and will code-split the worker.

  2. Copy the data folders. Use copy-webpack-plugin to emit cmaps/, standard_fonts/, iccs/, and wasm/ into your output, then set cMapUrl, standardFontDataUrl, iccUrl, wasmUrl to those output paths.

// webpack.config.js (essentials)
const CopyPlugin = require("copy-webpack-plugin");
const path = require("path");

module.exports = {
  // …
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: "node_modules/pdfjs-dist/cmaps/", to: "cmaps/" },
        { from: "node_modules/pdfjs-dist/standard_fonts/", to: "standard_fonts/" },
        { from: "node_modules/pdfjs-dist/wasm/", to: "wasm/" },
      ],
    }),
  ],
};
// app code
import * as pdfjsLib from "pdfjs-dist";
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.mjs", import.meta.url
).toString();

const pdf = await pdfjsLib.getDocument({
  url, cMapUrl: "/cmaps/", cMapPacked: true,
  standardFontDataUrl: "/standard_fonts/", wasmUrl: "/wasm/",
}).promise;

Note

Framework starters (React, Vue, Angular, Vite, Next.js) follow the same two rules — emit the worker, copy the data folders. The mechanics differ per bundler, but examples/webpack/ and examples/components/ are the canonical references.


10. Security

PDF.js treats every PDF as untrusted input and is engineered accordingly. Administrators should understand the layers.

10.1 Threat model

A malicious PDF may attempt: malformed structures to trigger parser bugs, hostile embedded JavaScript, abusive external links, decompression bombs (huge images), or data exfiltration via form-submit actions. PDF.js mitigates each.

10.2 Isolation layers

Layer Mechanism
Parsing isolation All parsing/decoding runs on a Web Worker, separate from the host page's main context.
Embedded-JS sandbox PDF JavaScript runs in a QuickJS WebAssembly engine (src/scripting_api + pdf.sandbox.js). The sandbox communicates only through a narrow string-serialized bridge (SandboxSupportBase) exposing a fixed allowlist: timers, alert/confirm/prompt, URL parsing, and a send event channel. It has no direct DOM, network, or filesystem access.
Recovery over crash By default (stopAtErrors: false) the engine recovers from malformed data rather than aborting, but it caps work (e.g. maxImageSize, maxCanvasPixels) to resist resource-exhaustion attacks.
  • External links carry rel="noopener noreferrer nofollow" by default (externalLinkRel), preventing reverse-tabnabbing and SEO abuse.
  • externalLinkTarget controls where links open.
  • Form-submit and similar actions are gated by the scripting layer; disable enableScripting to neutralize document-driven actions entirely.

10.4 Permissions

PDFDocumentProxy.getPermissions() exposes the document's declared permission flags (PermissionFlag: PRINT, COPY, MODIFY_CONTENTS, …). These are advisory — PDF.js surfaces them so integrators can enforce policy. The viewer honors them when enablePermissions: true.

10.5 Administrator hardening recommendations

Important

For untrusted-document deployments (e.g. user uploads viewed by others):

  • Set enableScripting: false unless you specifically need PDF JavaScript.
  • Serve the viewer/library from your own origin under a strict CSP that still permits the worker.
  • Keep maxImageSize / maxCanvasPixels at sane limits to bound memory.
  • Serve user PDFs from a separate origin/sandbox where feasible.
  • Keep PDF.js current — parser hardening fixes land continuously.

10.6 Reporting vulnerabilities

PDF.js is a Mozilla project; security issues should be reported through Mozilla's responsible-disclosure process rather than as public issues.


11. Engine Internals (Deep Dive)

This section is for engine contributors and anyone reconstructing the system.

11.1 Document model (worker side, src/core/)

  • XRef parses the cross-reference table/streams to locate every indirect object; handles incremental updates and rebuilds a broken table when needed.
  • Catalog reads the document root: page tree, named destinations, outlines, page labels, attachments, optional content groups, viewer preferences, and the open action.
  • PDFDocument ties it together and answers high-level requests (numPages, fingerprints, metadata, permissions).
  • Parser / Lexer tokenize content streams and object data.
  • Decryption handles standard security handlers (RC4/AES) keyed by the user/owner password.

11.2 Content evaluation → operator lists

PartialEvaluator walks a page's content stream and produces an OperatorList: parallel fnArray (operator codes from the OPS enum) and argsArray (their arguments). Operators cover paths (moveTo, lineTo, curveTo, fill, stroke, clip), text (beginText, showText, setFont, setTextMatrix), color (setFillRGBColor, setStrokeCMYKColor, …), images (paintImageXObject, paintImageMaskXObject, inline images), shadings, marked content, and annotation boundaries. The list streams to the main thread in chunks.

Note

The OPS enum is deliberately append-only — removed operators stay commented out and numbers are never reused — so third-party tools that read operator lists don't break across versions. A parallel compact DrawOPS enum exists for hot-path path construction.

11.3 Canvas execution (main side, src/display/canvas.js)

CanvasGraphics consumes the operator list and issues the corresponding HTML5 Canvas 2D calls, managing the graphics-state stack, clipping, soft masks (beginSMaskMode/endSMaskMode), patterns/shadings, transforms, and image painting. InternalRenderTask drives this incrementally via requestAnimationFrame (so rendering yields to the browser) and supports cancellation with a deferred timeout to avoid thrashing on rapid re-renders.

11.4 Fonts

The font subsystem (in src/core/) parses embedded font programs (TrueType, CFF/Type1/Type2, Type3, CIDFonts) and, where possible, repackages them as OpenType for the browser's Font Loading API. FontLoader (display side) installs them. When font-face loading is disabled, glyphs are rasterized from primitive path commands. The standard-14 fonts are filled from the standard_fonts/ data folder when not embedded.

11.5 Images and color

Image XObjects and inline images are decoded on the worker. Codecs include JBIG2, CCITT fax, JPEG (built-in), and JPEG 2000 (OpenJPEG via WebAssembly, gated by useWasm). OffscreenCanvas/ImageDecoder are used when available to offload conversion. Color conversion utilities (ColorConverters in src/shared/scripting_utils.js) map between Gray/RGB/CMYK/HTML. image_utils.js provides fast bit-twiddling paths for 1-bpp and RGB→RGBA conversion with endianness awareness.

11.6 Annotations and forms

Annotations are parsed on the worker into data objects; the display side renders them either onto the canvas (appearance streams) or as interactive HTML (AnnotationLayer) for links and widgets. Form field state lives in an AnnotationStorage; saveDocument() serializes it back into a new PDF, and ENABLE_STORAGE rendering reflects live values (used for printing/saving).

11.7 The scripting sandbox

pdf.scripting.js exposes pdfjsScripting.initSandbox inside QuickJS. pdf.sandbox.js (SandboxSupport extends SandboxSupportBase) JSON-serializes values across the WASM boundary and wires the externals (timers, dialogs, URL parsing, the updatefromsandbox event). The Acrobat object model lives in src/scripting_api/ (≈132K chars): app, doc, field, color, event, and AForm calculation functions, plus the date/time format tables.

11.8 XFA

src/core/xfa/ is an independent renderer for XFA (XML Forms Architecture) documents, producing a fake-DOM tree that the display-side XfaLayer turns into HTML. It's substantial (27 files, ≈405K chars) and disabled unless enableXfa is set. PDFDocumentProxy.isPureXfa indicates a document with only an XFA form.


12. Testing Strategy

Suite Tooling Location What it covers
Unit tests Jasmine test/unit/ (≈1.2M chars, 69 files) Parser, fonts, CMaps, primitives, API surface, utilities.
Integration / browser Puppeteer test/integration/ End-to-end behavior in real Firefox/Chrome (viewer, editor, forms).
Reference (regression) tests Custom harness test/pdfs/, test/stats/ Pixel-comparison rendering against a large PDF corpus (456 test PDFs) to catch rendering regressions.
Font tests Jasmine test/font/ Font parsing/conversion correctness.
Bidi tests — test/bidi/ Bidirectional (RTL) text handling.
Type tests TypeScript test/types/ The generated pdfjs-dist type declarations compile and resolve.
npx gulp unittest      # Jasmine unit tests (also runnable in-browser & via CLI)
npx gulp test          # Full reference/regression run
npx gulp lint          # ESLint + Stylelint + Prettier + svglint

The unit tests can run both in a browser harness and headless via a CLI helper (test/unit/clitests_helper.js), with coverage output controlled by UNITTESTCLI_COVERAGE_FILE.

Tip

The reference-test corpus is the project's safety net: any rendering change is validated against hundreds of real PDFs by image diff. When contributing engine changes, expect to update or add reference baselines deliberately, never blindly.


13. Operations & Maintenance

13.1 Observability

PDF.js logs through a verbosity gate (VerbosityLevel: ERRORS/WARNINGS/INFOS). Set verbosity per document (library) or via the verbosity option (viewer). Warning:-prefixed lines are non-fatal; Info: lines are dev-oriented. Enable pdfBug: true to expose internal debugging hooks (web/debugger.js) — for development only.

13.2 Performance levers

Lever Effect
Progressive loading (disableRange/Stream/AutoFetch = false) Render early pages before full download; cuts time-to-first-page on large remote PDFs.
enableHWA / enableWebGPU Offload canvas work to the GPU.
useWasm WASM image decoding (faster JPEG 2000/JBIG2).
OffscreenCanvas / ImageDecoder Off-main-thread image conversion when supported.
page.cleanup() / pdf.cleanup() Reclaim memory between pages/documents.
maxCanvasPixels / maxImageSize Bound memory for very large pages/images.
Shared PDFWorker Avoid spawning a worker per document when handling many.

13.3 Upgrades

Because there are no runtime dependencies, upgrading is replacing the static assets. The two rules: (1) upgrade pdf.mjs and pdf.worker.mjs together, and (2) re-copy the data folders (cmaps/, standard_fonts/, iccs/, wasm/) so they match the new engine. Review release notes for viewer-option changes if you persist preferences.

13.4 Localization

The viewer is localized via Fluent (@fluent/bundle, @fluent/dom) with .ftl files under l10n/ (130+ locales). localeProperties / the host locale selects the active translation. Custom builds can prune locales to reduce size.


14. Browser Extensions

extensions/chromium/ packages PDF.js as a Chrome/Chromium extension that replaces the browser's native PDF handling with the PDF.js viewer. It is built with npx gulp chromium and carries its own manifest, options page (extensions/chromium/options/), and content scripts. The CHROME PDFJSDev flag tailors the engine for this target (e.g. different default viewerCssTheme, telemetry handling, and enableScripting defaults). The Firefox integration is produced by the mozcentral build and lives in mozilla-central rather than as a separate extension.


15. Troubleshooting (Operational)

Issue: Viewer/library forces the fake worker in production

Symptom. Warning: Setting up fake worker.; UI sluggish under load. Causes & fixes. (1) workerSrc unset or 404 — verify the path. (2) CSP blocks worker-src/blob: — relax the policy for the worker. (3) Cross-origin worker without the Blob wrapper — use the GENERIC build or host the worker same-origin.


Issue: The API version does not match the Worker version

Cause. Mixed versions of pdf.mjs and pdf.worker.mjs. Fix. Serve both from one pdfjs-dist version; re-copy assets on upgrade.


Issue: WASM features fail (slow/incorrect JPEG 2000 or JBIG2 images)

Cause. wasm/ folder not deployed, wrong wasmUrl, or .wasm served with the wrong MIME type. Fix. Copy wasm/, set wasmUrl, ensure application/wasm. As a fallback the engine can decode without WASM but more slowly.


Issue: CJK / non-embedded-standard-font documents render with missing glyphs

Cause. cmaps/ or standard_fonts/ not deployed / not configured. Fix. Deploy both folders and set cMapUrl (+ cMapPacked: true) and standardFontDataUrl.


Issue: Cross-origin PDFs fail to load

Cause. Same-origin policy / missing CORS. Fix. Enable CORS on the file host, proxy same-origin, and set withCredentials when auth cookies are required.


Issue: High memory usage / tab crashes on large PDFs

Cause. Many uncleaned pages, or oversized pages/images. Fix. Call page.cleanup()/pdf.cleanup(), lower maxCanvasPixels/maxImageSize, and keep progressive loading on.


Issue: Module scripts don't load at all

Cause. .mjs served as text/plain/application/octet-stream. Fix. Map .mjs → text/javascript on the server.


16. Potential Improvements & Tech Debt

Observations from the source, framed for maintainers:

  • src/core/ concentration. The engine module is very large (≈1.85M chars across 86 files). Continued decomposition (parser vs. evaluator vs. codecs) eases review and onboarding.
  • Compile-time forking via PDFJSDev. The many PDFJSDev.test(...) branches are powerful but increase cognitive load; the build-target matrix (GENERIC/MOZCENTRAL/CHROME/LIB/GECKOVIEW/TESTING) must be kept in mind when reading any file.
  • MathClamp shim. src/shared/math_clamp.js carries a TODO to be removed once the TC39 Math.clamp proposal ships — a deliberate, tracked piece of forward-looking debt.
  • Feature flags awaiting graduation. Several editor features (comments, signatures, split/merge, updated add-image, new alt-text) are gated to dev/test builds pending Firefox-release experiments; their enable* defaults are explicitly marked temporary in app_options.js.
  • Known open issues. The community tracker reports active rendering edge cases — font rendering at shape intersections (#21353), Edge bullet rendering (#21303), toolbar dismissal after page deletion (#21241), RU-keyboard Ctrl+Z (#18856), Romanian-tax-authority XFA documents (#17510), and Firefox form submit/export actions (#13266). These are good orientation points for the parser/viewer/XFA subsystems respectively.
  • Unused-l10n / Babel cleanups. Tracked housekeeping (#21270 unused l10n ids, #21337 remove unused top-level constants via the Babel plugin) — low-risk maintenance.

17. Quick Reference

17.1 Build commands

npm install                 # install dev tooling
npx gulp --tasks            # list all tasks (authoritative)
npx gulp generic            # general browser/npm build
npx gulp minified           # minified build
npx gulp chromium           # Chrome extension
npx gulp mozcentral         # Firefox integration
npx gulp dist-install       # install built dist into node_modules
npx gulp lint               # linters/formatters
npx gulp unittest           # Jasmine unit tests
npx gulp test               # reference/regression tests

17.2 Critical runtime setup (library)

pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.min.mjs";
// + cMapUrl, standardFontDataUrl, iccUrl, wasmUrl for full fidelity

17.3 Build targets (PDFJSDev)

GENERIC · MOZCENTRAL · CHROME · LIB · GECKOVIEW · TESTING

17.4 Hardening defaults for untrusted PDFs

Option Hardened value
enableScripting false
enablePermissions true
annotationEditorMode -1 (disable editing)
maxImageSize a finite cap (not -1)
externalLinkRel keep noopener noreferrer nofollow

17.5 Asset folders & their URL options

Folder Option Default packed?
cmaps/ cMapUrl cMapPacked: true
standard_fonts/ standardFontDataUrl —
iccs/ iccUrl —
wasm/ wasmUrl —

17.6 Module map

Path Role
src/display/ Main-thread API (consumed by apps).
src/core/ Worker engine (parser, evaluator, codecs).
src/core/xfa/ XFA forms renderer.
src/scripting_api/ Acrobat JS object model (sandboxed).
src/shared/ Cross-thread transport, constants, utils.
web/ Prebuilt viewer + app_options.js.
extensions/chromium/ Chrome extension wrapper.