Export a searchable PDF.
Capture a live element, add selectable text and links, and paginate it into a PDF in your browser.
The page image plus a PDF text layer
The page image is SnapDOM's capture. The plugin places a second, invisible text layer over it using positions derived from SnapDOM's final serialized clone. No headless browser or separate layout engine rebuilds the page.
- Fidelity
- The visible result preserves SnapDOM's rendering of gradients, shadows, filters, web fonts and the rest of the captured element. The text layer adds document behaviour without replacing that image.
- Multilingual text
- CJK, Cyrillic, Greek, Arabic, Hebrew and emoji carry a /ToUnicode CMap, so selection, search and copy work for every script, whether or not a glyph travels with the file.
- Pagination
- Ten page sizes from A6 to tabloid, a custom [width, height] in points, or one page the size of the element. Breaks are pulled up off table rows, list items and figures, and break-before: page is honoured. Headers, footers and page numbers take their room out of the page rather than covering it. Text-form headers/footers and page numbers are real PDF text; Element-form bands are captured images.
- What reaches the layer
- Text nodes, input/textarea/select values, open shadow roots, and CSS transforms: rotation and skew go into the text matrix. Passwords are bulleted, never written.
- What does not
- Layout-hidden and transparent content is removed. Ambiguous clip, mask and filter geometry is omitted fail-safe. Arbitrary overlap is different: a rectangle or blur can cover pixels without removing the underlying semantic text, so it is not a redaction mechanism.
- Embedded fonts
- The faces the page painted with travel inside the file, subset to the glyphs the document actually drew. A ~320 kB face reaches a Latin page as a few kB. Turn on vectorText and eligible runs are painted as real vector glyphs instead of sitting invisible over the raster: crisp at any zoom, and an image-free export of the same page drops from ~420 kB to ~22 kB.
- Fillable forms
- A captured form is still a form. Inputs, textareas, checkboxes, radio groups and selects become real AcroForm fields (fillable, tabbable, extractable) carrying their name, value, options, checked state, required and read-only flags, and the accessible label as the tooltip a screen reader speaks. No JavaScript actions, no submit URLs, and a password field is written empty.
- Encrypted downloads
- A file that leaves the browser can be AES-256 encrypted on the way out, so the download itself is protected once it is attached and forwarded. Standard security handler revision 6, from the browser's own crypto.subtle, so no cipher code ships in the plugin and RC4 is not offered at any setting. A user password is required: the flavour of "encryption" that opens without asking is refused rather than produced.
- Identity and determinism
- Title, author, subject and keywords reach /Info and a mirrored XMP packet; a title also makes viewers show the document's name instead of its filename. Dates are never invented and the trailer /ID is a content hash, so the same unencrypted input produces the same bytes.
- Progress, cancellation, diagnostics
- Long exports report progress and take an AbortSignal. Every fidelity trade-off is also a structured diagnostic with a stable code, so a build can gate on "no annotations lost" without parsing console prose.
- Transparency
- A capture with alpha carries a Flate /SMask. Flatten it onto a colour to remove the mask. For every image, codec: auto compares JPEG with lossless Flate and keeps the smaller result. The lossless candidate is written in the narrowest colour space that reproduces the pixels exactly, which is one byte per pixel instead of three on the grey or few-coloured regions most page artwork turns out to be.
- Document features
- Headings become bookmarks and named destinations. Optional generated contents, repeated table headers, cover/back pages and watermarks make this a document exporter rather than a canvas wrapper.
- Accessibility structure
- Tagged output covers headings, prose, lists, tables, sections and figures. The PDF/UA-1 claim is earned, never configured: it is written only when every text operation rode an embedded face, the export is tagged, has a language and displays a title, and no painted image lacks alternative text. When a blocker remains the claim is withheld and the blocker is named.
- Ships as
- A minified ES module to ship, an unminified one to read, TypeScript declarations, versioned reference and MIT license. Browser use without a bundler requires an import map for the external SnapDOM peer.
- Verified by
- The full Chromium suite parses PDFs back with pdf.js, checks raw bytes (xref offsets, stream lengths, the lot), and one fixture compares the text layer against pixels. A separate gate imports and executes both plugin builds in Chromium, Firefox and WebKit. That is a three-engine API/artifact smoke, not three full layout suites.
Add PDF export to SnapDOM
Register the plugin once and every capture gains toPdf() alongside
toPng() and toCanvas(). It reuses the existing capture rather than
running a second capture engine.
import { snapdom } from '@zumer/snapdom'
import pdf from '@zumer/snapdom-plugins/pdf'
snapdom.plugins(pdf())
const shot = await snapdom(document.querySelector('#report'), {
scale: 2, dpr: 1
})
const blob = await shot.toPdf({
page: 'a4', // 'fit' | 'a4' | 'a5' | 'letter' | …
margin: 36, // points
header: { left: 'ACME', right: 'Q3 ledger' }, // real text, or pass an element
pageNumbers: true, // or { format, align, size, color }
codec: 'auto', // smaller JPEG or lossless Flate
tagged: true, // structure, not a PDF/UA badge
download: 'report.pdf' // also downloads it
})
On first PDF export, the plugin reads SnapDOM's final serialized clone, not the
original live DOM, so real node removal/replacement stays aligned. Page regions are rasterized
lazily and cached for later exports. Geometry-preserving final-artifact replacements stay aligned;
aspect-preserving width/height scaling is allowed, while incompatible viewBox, root paint/layout or
intrinsic-aspect changes fail instead of guessing coordinates. For secrets, remove or replace the
node/text; never treat an overlay or blur as redaction. Capture-model
options (formValues, shadow, breakAvoid, outline)
belong in pdf({ ... }).
- page
'fit'· one page the size of the element, ora3/a4/a5/a6/b4/b5/letter/legal/tabloid/executive, or[width, height]in points.- orientation
'portrait'or'landscape'.- margin
- Points.
24. - codec
autokeeps the smaller JPEG or lossless Flate image.- quality
- Quality of the JPEG candidate.
0.92. It changes nothing while the lossless candidate wins, which on page artwork is until roughly0.5. - deflate
- Deflate the content streams.
true. - text
- Emit the selectable layer.
true. Turn it off and you get an image-only PDF. - links
- Emit same-document destinations and safe
http:,https:,mailto:andtel:annotations.true. - image
- Draw the page image.
true. Setfalsefor an image-free PDF that keeps the enabled text, links and structure. Element headers, footers and watermarks are then omitted; cover/back pages keep semantics without their raster. - formValues
- Put
input/textarea/selectvalues in the layer.true. - shadow
- Walk open shadow roots.
true. - breakAvoid
- Keep page breaks off rows, list items and figures.
true. - outline
- Bookmarks from headings; capture-time.
true. - forms
- Measure form controls so the export can write fillable fields;
capture-time.
true. - tagged / lang
- Structure the document and set its spoken language.
- embedFonts
- Embed the capture's
@font-facebinaries, subset to the glyphs the document drew.true.'full'embeds unsubsetted;falseembeds nothing. - vectorText
- Paint eligible runs as real vector glyphs instead of leaving them
invisible over the raster.
false. RTL and complex scripts stay on the raster. - fields
- Write measured controls as real AcroForm fields.
true.falsekeeps their values in the invisible layer instead. - repeatHeaders
- Repeat a continuing table's
theadpixels and text. - header
- A running head across the top of every page. Give it a string or
three independent segments,
{ left, center, right }, each of which can be a function of the page number. Either way it is drawn as real text: selectable, searchable, crisp at any zoom. Pass an element instead and it is captured, for when you need a logo. Either way it takes its height out of the printable area rather than sitting over it. - footer
- The same, along the bottom.
- pageNumbers
true, or{ format, align, size, color, font }. Drawn as real PDF text rather than captured, so it stays crisp at any zoom and is searchable like the rest. It sits in the bottom margin, under a footer if there is one.- debugText
- Paint the invisible layer in red, so you can see where it sits.
false. - cover / toc / back
- Captured front/back pages and generated linked contents.
- watermark
- Text or an element, drawn over every page.
- encrypt
{userPassword, ownerPassword, permissions}. AES-256 (R6). The password is what protects the file; the permission flags are a request a viewer may honour. Needs https or localhost.- meta
{title, author, subject, keywords, creator, created, modified}/Infoand XMP. Dates are written only when you pass them.- onProgress
({phase, done, total})per step, for exports long enough to need a bar.- signal
- An
AbortSignal. Aborting rejects between pages withAbortErrorand produces nothing. - onReport
- The structured diagnostics report once the Blob is finished, with stable warning codes, counts, pages, bytes, fields and annotations.
- download
- A filename. Downloads the blob as well as returning it.
- backgroundColor
- Flatten at capture, plugin-default or export time. Resolution
remains a
snapdom(...)concern.
Included in the SnapDOM monorepo
The PDF plugin is available as MIT-licensed source in packages/plugins/pdf.
PDF and Vector are available on npm in @zumer/snapdom-plugins 4.x and require SnapDOM 3.x.
Questions
Can I use it in something I sell to a client?
Yes. The MIT license permits commercial use, modification and redistribution. Keep the license notice with redistributed copies.
Is the text really selectable, or is it OCR?
It is selectable without OCR. Eligible text is placed from browser geometry in SnapDOM's final serialized clone, so structural exclusions and geometry-preserving replacements stay aligned with the capture. Ambiguous paint effects are omitted fail-safe. Download a sample and copy a paragraph.
Is it open source?
Yes. The PDF plugin is part of the SnapDOM monorepo under the MIT license.
Can I read the code?
Yes. Its source, reference and browser test suites live in packages/plugins/pdf in the SnapDOM monorepo.
Does it need a server?
The exporter itself runs in the browser and returns a Blob; it does not upload
the PDF. SnapDOM may still fetch resources used by the page, or use a proxy if your application
configured one.
Why is the text raster and not vector?
By default the visible pixels preserve SnapDOM's own rendering instead of asking a PDF layout engine to reinterpret the page. Zoom far past the selected capture resolution and you will see pixels. The invisible layer adds selection, search and copy without repainting the type.
You can have the other model: vectorText: true paints eligible runs as real
glyphs from the embedded fonts, and with image: false the result is a small, fully
vector text PDF. It is opt-in because it is a different fidelity promise. RTL and complex
scripts are never painted this way, and they stay on the raster.
Can the PDF be password protected?
Yes. encrypt: { userPassword } writes an AES-256 file, so the download is
protected once it is shared. It uses the browser's own crypto, so no cipher code ships in the
plugin, and it needs a secure context: https or localhost.
Two things it will not do. It does not offer RC4 or the older PDF revisions, whose 40-bit key is broken. And it will not write an empty user password: that produces a file that is encrypted and still opens without asking anyone anything, which is worse than no protection because it looks like some. Permission flags (no printing, no copying) are written when you ask for them, but they are a request a viewer may honour, not a lock.
Which browsers?
The complete fixture suite is Chromium-only. Both plugin builds must also pass the release artifact smoke in Chromium, Firefox and WebKit. That supports the plugin/API contract in all three; it does not pretend every complex layout has the same three-engine coverage.