theseus/bundled-addons/pdf-editor/VENDOR.md
Local Dev 6b2c4b25c0 feat(pdf-editor): read, mark up and reshape a PDF without leaving the browser
A PDF that needs a signature, a highlight or a page removed currently sends
the user out to a desktop application or, worse, to a web service that wants
the document uploaded first. Both are poor answers for a browser whose point
is that nothing has to leave the machine. This is a full-tab editor that opens
a PDF, marks it up, fills its forms and saves a new copy, entirely locally.

Two engines, vendored rather than installed, because an add-on ships as a
self-contained folder over the signed update channel and nothing runs a
package manager on the way: pdf.js reads and renders, pdf-lib writes. They
share no state. Everything in between lives in PDF user space — points,
origin bottom-left — which is the one coordinate vocabulary both speak, so a
mark survives zooming, rotating and reordering with no conversion table and
save-time needs to know nothing about how a page happened to be displayed.

The page strip is built from pdf.js's PDFPageView components rather than its
PDFViewer, which renders pages in the file's own order and cannot hide,
reorder or individually rotate one — three of the features here. Text layers
are ours and stay attached for every page, drawn or not, because Theseus's
find bar is Chromium's findInPage over the live DOM and a torn-down text layer
is a page Ctrl+F cannot see. Canvases are virtualised; a letter page at 100%
is 3.4 MB of bitmap.

Redaction is the part worth being careful about. A black box over text hides
nothing — the text stays in the content stream and comes straight out of a
copy-paste — so the editor says so in a modal before the tool can be used,
and on save rebuilds each redacted page as an image, which genuinely removes
it. Pages that were not redacted are untouched. Form widgets and links are
kept, since they were never the leak.

Saving never writes over the original: every save reloads the source bytes and
replays the session onto a fresh copy, so a botched save cannot poison the
next one.

Out of scope for this first version: editing the text that is already in the
document, and writing XFA forms back (pdf-lib cannot, so those are fill-and-
print only, and the editor says so on open).
2026-09-20 20:58:21 +02:00

3 KiB

Vendored libraries

Both engines are copied in rather than installed, because Theseus's own node_modules is a build-time tree and an add-on has to be a self-contained folder: it is copied to <userData>/addons/pdf-editor/ on first run and shipped as a tarball over the signed update channel, neither of which runs a package manager.

Path Library Version Licence
vendor/pdfjs/ pdf.js (pdfjs-dist) 6.3.289 Apache-2.0
vendor/pdf-lib/ pdf-lib 1.17.1 MIT

Licence texts ship beside the code: vendor/pdfjs/LICENSE, vendor/pdfjs/wasm/LICENSE_* and vendor/pdf-lib/LICENSE.md.

pdf.js reads; pdf-lib writes. They never look at each other's state — the editor holds the document model in between, in PDF user-space coordinates, which is the only vocabulary both of them speak.

What was taken, and what was left behind

From pdfjs-dist:

  • build/pdf.min.mjs, build/pdf.worker.min.mjs — the API and its worker.
  • web/pdf_viewer.mjs + web/pdf_viewer.css + web/images/ — the viewer components, for PDFPageView, PDFLinkService and EventBus. Not the full viewer application. There is no minified build of this file upstream.
  • standard_fonts/ — the base-14 metrics. A PDF that names Helvetica without embedding it is extremely common, and without these it renders wrong.
  • wasm/*.wasm — JPEG 2000, JBIG2 and colour management.
  • iccs/ — ICC profiles for the colour management above.

Left out on purpose:

  • cmaps/ (~1.5 MB) — needed only for CJK documents that use a non-embedded CID font. Dropping it keeps the add-on at about 4 MB. If CJK support is wanted, copy cmaps/ in and pass cMapUrl: "vendor/pdfjs/cmaps/" to getDocument in editor.js; nothing else changes.
  • wasm/*_nowasm_fallback.js (~580 KB) — pure-JS fallbacks for engines without WebAssembly. Electron always has it.
  • wasm/quickjs-eval.* and build/pdf.sandbox.* (~525 KB) — the interpreter for JavaScript embedded in a PDF. The editor loads documents with enableScripting: false, so a document's own scripts never run.
  • Every .map file, the TypeScript definitions, the CommonJS and legacy builds, and image_decoders/.

From pdf-lib, only dist/pdf-lib.esm.min.js.

Upgrading

Re-run the extraction against a fresh tarball, keeping the list above, then run the verification suite in scratchpad/verify-pdf-editor/. Two things to watch, because both have bitten this add-on already:

  • web/pdf_viewer.mjs reads the core API off globalThis.pdfjsLib instead of importing it, so editor.js must keep assigning that global before it imports the components bundle.
  • pdf.js's AnnotationStorage has no stable public shape. getAll(), which older releases had, does not exist in 6.x — form values are read through has(id) and getRawValue(id) against the ids from getFieldObjects(). A silent change here looks like a form that saves nothing.