script📜

This script element enables WebPDF.pro.

<script src=//new.webpdf.pro/.js type=module ium></script>
new Attribute Type Description
src '//new.webpdf.pro/.js' The source is backed by our CDN.
type 'module' The module type is required.
ium ♊false | true Render pages with PDFium by default.
Advanced
new Attribute Type Description
🆕debug 👨🏻‍💻 false | true Trace events, phases and fetches to the console.
🆕quiet 🔇 false | true Keep the console quiet: no banner, no error logs.
🆕measure ⏳ false | true Write performance marks for every page phase.
🆕fonts 🔤 false | true Keep font data: SVG exports embed their fonts.
🆕workers 1 | 2 | ⋯ | 8 SVG pages built at once in the background worker.
event:prefix null | 'type' Emit type-prefixed events.
🆕break ❌ 'token ⋯' Turn built-in behaviours off.
🆕trust:stores trust:src trust:srcdocsee pdf-file Trust anchors for every pdf-file.
🆕signatures false | true Show signature cards on every page.
🆕 controls false | true Viewer controls on every page, like video controls.
🆕- 'text' 'anno' 'drop' 'controls' Turn layers, drag & drop or all controls off on every page.
🆕placeholder:aspect-ratio 'A4' | 'letter' | ⋯ | '16/9' | '-' The placeholder of every page while its PDF is on the way: a paper or a ratio.

performance.getEntriesByType('measure') ➡️ 🌐📄/p:r/.js.a-z.1, 🌐📄/p:v/wk.a-z.2, …

pdf-file ⚡ load ➡️ pdf-file-load,
pdf-page ⚡ load ➡️ pdf-page-load; the same for verify, render, signature, …

space-separated tokens, e.g. break="lazy hint":

page-wide anchors: they add up with each file's own; a file's value of - drops the script's, -name drops one store.

multi-homed: ium signatures controls event:prefix placeholder:aspect-ratio - trust:* work on the script, every pdf-file and (signatures controls event:prefix placeholder:aspect-ratio -) every pdf-page:
the nearest element that has one decides and a value of - turns it off; values and tokens add up, -token drops an inherited one, a bare - token drops them all.

a paper name of paper.rt.ht/.css in any case, e.g. A4 A5 B5 C6 DL JIS-B5 letter legal tabloid: a page keeps that box until its PDF arrives;
or a ratio, e.g. 16/9 1.5: the box keeps the area of the visitor's paper, so 297/210 is A4 landscape;
without one, the paper of the visitor's region (by browser language): letter in BZ CA CL CO CR GT MX NI PA PH PR SV US VE, A4 elsewhere; - reserves no box;
a size remembered from an earlier visit and pdf-page { --width: ⋯; --height: ⋯ } come first; break="hint" turns off the remembered sizes and the regional paper.

Token Turns off
svg-worker the background worker: pdf-page svg is built on the main thread
verify signature verification: no checks, hotspots, cards or verify events
revocation online revocation checks (OCSP, CRL, AIA); data embedded in the PDF is still used
content-visibility the built-in content-visibility: auto of pages
lazy loading=lazy: every page renders eagerly
hint remembered page sizes (localStorage) and the regional paper that reserve boxes before a PDF loads (an explicit placeholder:aspect-ratio still does)
prefetch idle prefetch of the neighbouring pages of pdf-page controls
fullscreen F F11 and toggleFullscreen()
aspect-ratio the aspect-ratio of pages (--aspect-ratio stays set)
image the image layer (text and annotations still render)
cleanup releasing PDF.js page resources after SVG builds
hwa PDF.js hardware-accelerated canvases
💾 FSA the File System Access API: pickers fall back to <input type=file> and downloads

Multi-homed attributes

These attributes work on the script, every pdf-file and every pdf-page: set one once for the whole document, then override it where a file or a page needs something else.

<script src=//new.webpdf.pro/.js type=module controls></script>
<pdf-file id=f src=a.pdf></pdf-file>
<pdf-page of=f           ></pdf-page> <!-- controls: from the script -->
<pdf-page of=f controls=-></pdf-page> <!-- none on this page   -->
<pdf-page of=f -=controls></pdf-page> <!-- none: an off-list token -->
Attribute script pdf-file pdf-page Rule
controls signatures + + + on/off: the nearest element that has it decides; - turns it off
ium + + on/off, the same way, per file
event:prefix placeholder:aspect-ratio + + + one value: the nearest wins; - turns it off
- + + + tokens 'text' 'anno' 'drop' 'controls' add up outward-in: -token drops an inherited one, a bare - drops them all; -=controls wins over any controls
trust:stores + + tokens add up the same way: -moz drops an inherited store
trust:src trust:srcdoc + + values add up; a file's - drops the script's

Properties give the effective value: p.controls is true on a page that inherits it, p.controls = false writes controls=- and p.controls = null inherits again.


pdf-file id=f📕

The pdf-file id=f element loads a PDF file into the document.
The pdf-page of=f element embeds a page of a pdf-file.

Examples

Load from URL ▶️ try live

<pdf-file id=f src=//pdf.ist/form.pdf></pdf-file>
<pdf-page of=f></pdf-page>

or

<pdf-file id=f></pdf-file>
try {  await f.load('//pdf.ist/form.pdf'); }
catch (e /* : PDFLoadError */) { /*E*/ } /*S*/

or

f.addEventListener('error', e => { /*E*/ }, { once: true });
f.addEventListener('load',  e => { /*S*/ }, { once: true });
f.src = '//pdf.ist/form.pdf';

Save to System ▶️ try live

await f     .save(); // below export options
await f.XFDF.save(); // would apply here too
Export as Type
await f     .export();                                // : File
await f     .export({ name: 'a.pdf' });               // = File{name: 'a.pdf', …}
await f     .export(File);                            // : File
await f     .export(Blob);                            // : Blob
await f     .export({ type: Blob, noEdit: true });    // : Blob (without edits after load)

await f.XFDF.export();                                // : XMLDocument
await f.XFDF.export({ indent: false });               // : XMLDocument
await f.XFDF.export(XMLDocument);                     // : XMLDocument (also supports: File | Blob)
await f.XFDF.export(String);                          // : String      (via XMLDocument.toString())
await f.XFDF.export({ type: String, indent: false }); // : String

Load from System ▶️ try live

await f     .open();
await f.XFDF.open();

Load from Raw Data ▶️ try live

const  pdfData = await fetch('//pdf.ist/form.pdf' ).then(f => f.arrayBuffer());
const xfdfData = await fetch('//pdf.ist/form.xfdf').then(f => f.text());
await f     .load( pdfData);
await f.XFDF.load(xfdfData);

Load from File or ⋯FileHandles ▶️ try live

await f     .load(pdf       /* : File */); // Distinguished by
await f.XFDF.load(     xfdf /* : File */); // File.name extension.
await f     .load(pdf, xfdf);              // Load both at once.

Load from System by Drag & Drop ▶️ try live

Every pdf-page is a drop target for its file: drop a .pdf, an .xfdf, both, or a link on the page above.

<pdf-page of=f       ></pdf-page> <!-- drop here -->
<pdf-page of=f -=drop></pdf-page> <!-- not here  -->

🆕 Verify Signatures ▶️ try live

<pdf-file id=s src=signed.pdf trust:src=roots.pem signatures></pdf-file>
<pdf-page of=s></pdf-page>
const rows = await s.verify(); // [{ name, status, code, signerName, time, revocation, … }]

Properties

reflected: setting a property writes its attribute, so changes from code and controls show up in the markup.

<pdf-file id=f src=⋯ password=⋯ ium max:image-size=⋯ -=⋯></pdf-file>
             f.src
new Attribute Property Type Description Get Set
src src String The file URL (abs./rel.). + +
URL URL The file URL (abs.). + +
name String The file name. + +
data Uint8Array The file bytes at load. + +
password password String The file password. + +
pages [<pdf-page>] The linked pages: nested and of= pages. +
XFDF Object XML Forms Data Format. +
proxy Map Built-in CDN/CORS proxy. +
ium ium false | true Use high-fidelity rendering (PDFium). + +
🆕 max:image-size max.imageSize -1 | Number Images above width × height pixels are not drawn. + +
🆕 - off DOMTokenList 'text' 'anno' 'drop' 'controls' Turn layers, drag & drop or all controls off on its pages. +
🆕 controls controls false | true Viewer controls on its pages (multi-homed). + +
🆕 formData Object The form field values, by annotation ID. + +
🆕 js PDFDocumentProxy The PDF.js document. +
🆕 ready Boolean Connected with a src or data to load. +
🆕 event:prefix event.prefix null | 'type' Emit events as pdf-file-load, … + +
🆕 placeholder:aspect-ratio placeholder.aspectRatio '' | 'A4' | ⋯ | '16/9' | '-' The placeholder of its pages while the PDF is on the way. + +

If a direct fetch fails (CORS, network, HTTP), the file is fetched once more through the built-in proxy.

name ↔️ src update each other when src is set. Otherwise,
name is only used for f.*(), f.XFDF.*() IO methods.

data does not include the changes after load; see saveData(). await f.data: a promise for files loaded from src.

proxy : Map { src → URL }, e.g. 'form.pdf' → https://📕.pdf.ist/?t=form.pdf; set an entry before loading to choose your own.

multi-homed: inherited from the script; ium=- opts out, f.ium = false writes it; -="-text" keeps one inherited layer.

Properties (XFDF)

<pdf-file id=f xfdf:src=⋯></pdf-file>
             f.XFDF.src
new Attribute Property Type Description Get Set
xfdf:src src String The XFDF file URL (abs./rel.). + +
URL URL The XFDF file URL (abs.). + +
name String The XFDF file name. + +

fetched after the PDF loads (and after every reload), retried once through the built-in proxy.

name ↔️ src update each other when src is set. Otherwise,
name is only used for f.*(), f.XFDF.*() IO methods.

Properties (Signatures)

<pdf-file id=s src=⋯ signatures trust:stores=⋯ trust:src=⋯ trust:srcdoc=⋯></pdf-file>
                              s.trust.stores.add('eutl');     // + the EU trusted lists
                              s.trust.stores.add('-moz');     // - the <script>'s Mozilla roots
new Attribute Property Type Description Get Set
🆕 signatures signatures false | true Show signature cards on every page. + +
🆕 signed 'pending' | 'verified' | ⋯ | 'invalid' The worst signature status (read-only). +
🆕 trust:stores trust.stores DOMTokenList 'aatl' 'eutl' 'ms' 'moz' Trust anchors, from well-known root stores by name. +
🆕 trust:src trust.src String (URL) Trust anchors, from one URL. + +
🆕 trust:srcdoc trust.srcdoc String (PEM | base64) Trust anchors, inline. + +

multi-homed: inherited from the script, signatures=- opts out, a page's own signatures wins; hotspots over signature fields are always there.

'pending' while checking, then the worst of 'verified' < 'unknown' < 'untrusted' < 'expired' < 'revoked' < 'invalid'; null when unsigned. CSS: pdf-file:state(verified), :state(pending), … (the element's attributes stay as written).

nothing is trusted by default. trust:* values add up with the script's: a value of - drops the script's, trust:stores="-name" one store, '- name' replaces them.
changing one re-verifies without reloading; a source that fails adds TRUST_SOURCE_FAILED to the warnings of every row.

fetched fresh from their publishers through the built-in proxy, cached for 5 minutes:

Store Roots
'aatl' the Adobe Approved Trust List (Acrobat)
'eutl' the EU trusted lists: qualified CAs, status granted
'ms' Microsoft roots trusted for Document Signing (CCADB)
'moz' Mozilla roots trusted for email (CCADB)

Methods (IO)

All work both on the PDF file and its annotation format.

await f.     open(); // 📕 PDF file
await f.XFDF.open(); // ✏️ annotation
new async Method Arguments Return FS Picker FS Read FS Write FS Handle
🔄 open () File ± ± ±
🔄 load ( data? ) data? ± ±
🔄 save ( opts? ) File ± ± ±
🔄 saveAs ( opts? ) File ± ± ±
🔄 saveCopy( opts? ) File ± ±
close () [⋯Handle] ±
🔄 download( opts? ) +
🔄 saveData ( opts? ) 📕 Uint8Array | ✏️ String
🔄 export ( opts? ) type

± + with the File System Access API; otherwise opening falls back to <input type=file> and saving to download().

data : 📕 (File|⋯Handle) | Blob | (URL|String) | ArrayBuffer | Uint8Array,
data : ✏️ File | Blob | String (XFDF XML) | XMLDocument.

opts = 📕 { name = f. name, noEdit = false, rewrite = false, startIn : WellKnownDirectory },
opts = ✏️ { name = f.XFDF.name, declare = true, indent = true, href, src = false }.

opts + 📕 { type = File | Blob },
opts + ✏️ { type = File | Blob | String | XMLDocument }.

f. load(f1, f2) can load 2 (File|⋯Handle)s as .pdf and .xfdf,
f.XFDF.load(a1) can only take 1 argument.

save*() may fall back to download(); see caniuse.com/?search=showSaveFilePicker.

download() may rename the copy: form.pdf → form (1).pdf; f.download({ name: 'form.xfdf' }) downloads the XFDF.

Methods (Signatures)

const rows = await s.verify();                // cached: runs again when trust:* changes
const rows = await s.verify({ force: true }); // or when forced
new async Method Arguments Return
🆕🔄 verify ( { force = false }? ) [row]

one row per signature, newest first; the same rows arrive with the verify event:

Field Type Meaning
name String the signature field's full name
fieldName String the field's own name (/T); '' without one
id String an opaque id: the field and the bytes it signs
kind 'signature' | 'document-timestamp' a signature, or an RFC 3161 document timestamp
page Number | null the page of its first widget
widgets Array the field's widgets; empty when the field cannot be found
widgets[].id String the widget's annotation id
widgets[].page Number | null its page
widgets[].rect [x1, y1, x2, y2] | null its rectangle, in PDF points
widgets[].visible Boolean shown: more than 1 pt wide and high, not hidden
status 'verified' | 'unknown' | 'untrusted' | 'expired' | 'revoked' | 'invalid' the verdict; see the codes below
code String | null why, e.g. 'EXPIRED'; null when verified
errorCode String | null the same reason as Firefox names it, e.g. 'SEC_ERROR_EXPIRED_CERTIFICATE'
message String | null the reason, in English
warnings Array what is worth knowing but does not change the verdict
warnings[].code String e.g. 'MODIFIED', 'REVOCATION_UNKNOWN', 'TRUST_SOURCE_FAILED', 'WEAK_SHA1'
warnings[].message String the warning, in English
signerName String | null the signer as the PDF names it (/Name), unverified; the verified name is certificate.subjectCN
reason String | null why the signer says they signed (/Reason)
location String | null where the signer says they signed (/Location)
contactInfo String | null how to reach the signer, in their words (/ContactInfo)
signingTime String | null the claimed signing time as written (/M), e.g. "D:20250101120000+01'00'"
signedAt Date | null the same time as a Date
filter String | null the signature handler (/Filter), e.g. 'Adobe.PPKLite'
subFilter String | null the format (/SubFilter): 'adbe.pkcs7.detached' 'adbe.pkcs7.sha1' 'ETSI.CAdES.detached' 'ETSI.RFC3161'
signatureType 0 | 1 | null PDF.js's number for the format: 0 adbe.pkcs7.detached, 1 adbe.pkcs7.sha1
byteRange [start, length, start, length] the bytes it signs (/ByteRange)
revisionIndex Number its rank, the newest 0
parentId String | null the id of the next newer signature
coversWholeDocument Boolean nothing but whitespace follows the bytes it signs
documentModifiedAfterSigning Boolean the document changed after it was signed: !coversWholeDocument
modificationsAfterSignature Number | null updates saved after it
laterSignatures Number signatures and document timestamps added after it
laterTimestamps Number the document timestamps among them
onlyLaterSignatures Boolean every later update added a signature (counted, not inspected)
time Object | null the time the certificates are checked at; null when the bytes do not check out
time.value String (ISO) that time
time.source 'timestamp' | 'signingTime' | 'M' | 'now' where it comes from: a trusted timestamp, the signer's clock, the PDF date, or now
time.trusted Boolean vouched for by a trusted time-stamp authority
time.claimed Object the times the signer claims
time.claimed.signingTime String (ISO) | null the signer's clock (the CMS signing-time attribute)
time.claimed.M String (ISO) | null the PDF date (/M)
timestamp Object | null its RFC 3161 timestamp; null without one
timestamp.status 'trusted' | 'untrusted' | 'invalid' | 'unknown' trusted: from a trusted authority, valid then · untrusted: valid, the authority not trusted · invalid: unreadable or not matching · unknown: an unsupported hash
timestamp.genTime String (ISO) | null when the authority stamped it
timestamp.accuracyMs Number | null its stated accuracy, in ms (0 when not stated)
timestamp.policy String | null the authority's policy OID
timestamp.serialNumber String | null the token's serial number, in colon hex
timestamp.imprintAlgorithm String | null the hash it stamps, e.g. 'SHA-256'
timestamp.tsa String | null the authority's common name
timestamp.certificate Object | null the authority's certificate, like certificate (no chain)
timestamp.errorCode String | null why it is not trusted
timestamp.message String | null the same, in English
integrity Object the checks of the signed bytes and the signature
integrity.status 'ok' | 'unknown' 'ok' once both check out; failures show in status
integrity.digestAlgorithm String | null e.g. 'SHA-256'
integrity.signatureAlgorithm String | null e.g. 'rsaEncryption', 'ecdsa-with-SHA256'
integrity.signedAttributes Boolean | null it signs CMS attributes too (time, certificate)
integrity.byteRange Object | null the check of byteRange
integrity.byteRange.gap Number bytes between the two signed ranges
integrity.byteRange.contentsLength Number bytes of the signature
integrity.byteRange.ok true | null the gap holds the signature and nothing else
certificate Object | null the signer's certificate (the time-stamp authority's for a document timestamp)
certificate.subject String its subject, e.g. 'CN=⋯, O=⋯, C=⋯'
certificate.issuer String its issuer, the same way
certificate.subjectCN String the subject's common name (else its organization, else its unit)
certificate.issuerCN String the issuer's common name
certificate.email String | null the subject's email address
certificate.organization String | null the subject's organization
certificate.notBefore String (ISO) valid from
certificate.notAfter String (ISO) valid until
certificate.serialNumber String in colon hex
certificate.fingerprintSha256 String the SHA-256 of the certificate, in colon hex
certificate.derBase64 String the certificate itself: DER, in base64
certificate.publicKey String e.g. 'RSA 3072', 'EC P-384', 'Ed25519'
certificate.signatureAlgorithm String how its issuer signed it, e.g. 'sha256WithRSAEncryption'
certificate.keyUsage [String] e.g. 'digitalSignature', 'nonRepudiation'
certificate.extKeyUsage [String] e.g. 'documentSigning', 'timeStamping'
certificate.isCA Boolean a certificate authority
certificate.selfSigned Boolean issued to itself: the same subject and issuer
certificate.policies [String] its policy OIDs
certificate.qc Object | null its EU qualified statements
certificate.qc.compliance Boolean an EU qualified certificate
certificate.qc.sscd Boolean its key is on a qualified signature creation device
certificate.chain Array the path to a trust anchor (when untrusted, the longest path found); each one has the fields of certificate but chain, and:
certificate.chain[].role 'signer' | 'tsa' | 'intermediate' | 'anchor' its place in the path
certificate.chain[].validAtT Boolean valid at time.value
certificate.chain[].revocation Object | null its revocation check: the same object as in revocation.checked
trust Object the path to a trust anchor
trust.status 'trusted' | 'untrusted' | 'unsupported' whether the path reaches a trust anchor
trust.anchor Object | null the anchor reached, like certificate
trust.errorCode String | null why it is not trusted
validity Object the validity periods along the path
validity.status 'ok' | 'expired' | 'notYetValid' | null null when not checked
validity.at String (ISO) | null the time checked: time.value
validity.cert Object | null the first certificate not valid then, like certificate
revocation Object the revocation checks
revocation.status 'good' | 'revoked' | 'unknown' | 'skipped' 'skipped' when the path is not trusted
revocation.checked Array one check per certificate of the path but the anchor
revocation.checked[].fingerprintSha256 String the certificate checked
revocation.checked[].subjectCN String its common name
revocation.checked[].status 'good' | 'revoked' | 'unknown' | 'not-checked' 'not-checked': an OCSP responder that needs no check
revocation.checked[].source 'embedded-ocsp' | 'embedded-crl' | 'ocsp' | 'crl' | null where the answer came from
revocation.checked[].url String | null the URL asked online
revocation.checked[].thisUpdate String (ISO) | null when the answer was issued
revocation.checked[].nextUpdate String (ISO) | null when the next one is due
revocation.checked[].producedAt String (ISO) | null when the OCSP responder signed it
revocation.checked[].revocationTime String (ISO) | null when it was revoked
revocation.checked[].reason String | null why, e.g. 'keyCompromise'
revocation.checked[].revokedLater Boolean revoked only after a trusted signing time, so still good
elapsedMs Number how long the check took, in ms

🪪 trust.anchor, validity.cert, timestamp.certificate and each certificate.chain entry are certificates with the fields of certificate.

⚠️ when the check itself fails (status 'unknown', code 'INTERNAL_ERROR'), a row has only the PDF's fields, status, code, message and warnings.

Status Codes
'invalid' MALFORMED_SIGNATURE BYTE_RANGE_MISMATCH DIGEST_MISMATCH SIGNATURE_MISMATCH ESS_CERT_MISMATCH ALG_PROTECTION_MISMATCH ENVELOPED_CONTENT NO_SIGNER ALGORITHM_DISABLED
'revoked' REVOKED
'expired' EXPIRED NOT_YET_VALID EXPIRED_ISSUER NOT_YET_VALID_ISSUER
'untrusted' NO_ANCHORS UNKNOWN_ISSUER SELF_SIGNED UNTRUSTED_ISSUER BAD_CERT_SIGNATURE CA_INVALID KEY_USAGE PATH_LEN UNKNOWN_CRITICAL_EXTENSION NAME_CONSTRAINTS CERT_ALG_DISABLED ALG_MISMATCH_CERT INADEQUATE_CERT_TYPE WEAK_KEY
'unknown' WEBCRYPTO_UNAVAILABLE SUBFILTER_NOT_SUPPORTED UNSUPPORTED_ALGORITHM NOCERT INTERNAL_ERROR
'verified' null

Events

const                         h = e => console.warn(e.type, e.detail);
f.   addEventListener('load', h);
f.removeEventListener('load', h);
new Event Detail Fired When Bubbles Cancelable Composed
load File has loaded. + - ±
error Error File has failed IO. + - ±
🆕 verify { signatures, status } Signatures have been checked. + - ±

pdf-page id=p📄

The pdf-page of=f element embeds a page of a pdf-file.
The pdf-file id=f element loads a PDF file into the document.

Examples

<pdf-file id=f src=//pdf.ist/web.pdf></pdf-file>

Embed First Page ▶️ try live

<pdf-page of=f></pdf-page>

Embed Last and First Pages ▶️ try live

<pdf-page of=f no=-1 controls scale=.2></pdf-page>
<pdf-page of=f no=+1 controls scale=.2></pdf-page>

Responsive Transparent Pages ▶️ try live

Pages are responsive to context. Set background-color (or --pdf-page-background-color) for transparency.

<div checkerboard    style=display:flex re-size=h>
  <pdf-page of=f svg style=background-color:transparent></pdf-page>
  <pdf-page of=f     style=background-color:transparent></pdf-page>
</div>
CSS [checkerboard]
[checkerboard] { background: repeating-conic-gradient(oklch(88% 0 0) 0 25%, transparent 0 50%) 0 0 / 1em 1em; }
CSS [re-size]
[re-size]    { display: flex; overflow: hidden; max-width: 100%; outline: 1px dashed; }
[re-size]    { resize:       both; }
[re-size=v]  { resize:   vertical; }
[re-size=h]  { resize: horizontal; }

Scale as Resolution ▶️ try live

With extrinsic sizing, scale can be used just for resolution.

<div style=display:flex;height:20rem;max-width:min-content re-size=v>
  <pdf-page of=f controls scale=.1 style=height:100%;max-height:none></pdf-page>
</div>
<div style=display:flex;width:20rem;max-width:min-content  re-size=h>
  <pdf-page of=f controls scale=.1 style=width:100%;max-width:none></pdf-page>
</div>

🆕 Lazy Pages ▶️ try live

A lazy page loads its size at once and renders when it comes within one viewport of the visible area (or is found, focused or printed). A lazy PDFium page also waits until then to load PDFium.

<pdf-page of=f no=1 loading=lazy></pdf-page>
<pdf-page of=f no=2 loading=lazy></pdf-page>
⋯

🆕 Fullscreen ▶️ try live

Focus a page with controls (like the two above) and press F; Esc leaves.

<pdf-page of=f id=p controls></pdf-page>
await p.toggleFullscreen(); // true | false

🆕 Signature Cards ▶️ try live

<pdf-file id=s src=signed.pdf trust:src=roots.pem signatures></pdf-file>
<pdf-page of=s             ></pdf-page> <!-- compact cards; click one for the full card -->
<pdf-page of=s signatures=-></pdf-page> <!-- hotspots only -->

Properties

These determine the content and the intrinsic size for layout.

<pdf-page of=f id=p no=⋯ scale=⋯ rotation=⋯ offset:x=⋯ offset:y=⋯></pdf-page>
                  p.no++;
new Attribute Property Type Description Get Set
of of <pdf-file id> The linked file ID. + +
file <pdf-file> The linked file. +
no no ⋯ | -1 | +1 | ⋯ The page number. + +
scale scale ⋯ | 0.9 | 1.0 | 1.1 | ⋯ The page scale. + +
rotation rotation - 90 | 0 | + 90
+270 | ±180 | -270
The page rotation. + +
orientation 'portrait' | 'landscape' | 'square' ⬅️ file no rotation +
🆕 offset:x offset:y offset.x offset.y 0 | Number Shift the content (CSS px); the box keeps its size. + +
🆕 image.metrics { width, height, aspectRatio, orientation } The rendered image size. +
🆕 fullscreen Boolean Shown fullscreen; see toggleFullscreen(). +
🆕 event:prefix event.prefix null | 'type' Emit events as pdf-page-load, … (multi-homed). + +
🆕 placeholder:aspect-ratio placeholder.aspectRatio '' | 'A4' | ⋯ | '16/9' | '-' The placeholder while the PDF is on the way. + +

without of, a page shows its closest ancestor: <pdf-file src=⋯><pdf-page></pdf-page></pdf-file>.

negative numbers count from the end: -1 is the last page; +Infinity stays on the last page.

scale sets the read-only CSS properties --width, --height, --scale;
scale does not affect the CSS properties --w (paper width), --h, --u (unit).

rotation is absolute and, if specified, overrides the rotation stored in the file,
rotation must be an integer multiple of ±90 and can swap --width 🔄 --height.

multi-homed: '' follows the file, then the script, then the paper of the visitor's region; a ratio keeps that paper's area; - reserves no box; the box turns and scales with the page.

Properties (Render)

These determine how/which layers are rendered.

<pdf-page of=f id=p canvas=⋯ svg -="text anno" loading=⋯ watermark=⋯></pdf-page>
                               p.off.add('text');
new Attribute Property Type Description Layer
canvas canvas '.js' | 'ium' Select the canvas renderer. 🖼️
svg svg false | true Use the svg renderer. 🖼️
-=text off DOMTokenList 'text' Omit the text layer. 🔠
-=anno off DOMTokenList 'anno' Omit the annotation layer. ✏️
🆕 loading loading 'eager' | 'lazy' Render when near the visible area. 🖼️
🆕 watermark watermark null | '' | 'host' Preview the evaluation watermark. ⚠️

'.js' ➡️ 🦊 PDF.js canvas renderer,
'ium' ➡️ ♊ PDFium canvas renderer (WASM, in a worker),
is the default when enabled: script ⋯ ium
pdf-file ⋯ ium.

svg forces the 💎 PDF.js svg renderer: real vector SVG, built in a background worker, exported and copied as image/svg+xml.

Annotation layer is used for links and forms; without it, forms are drawn into the image.

a lazy page fires load once sized and render once drawn; see Lazy Pages.
a file whose pages are all lazy verifies its signatures once one of them nears the viewport (verify() runs at once): trust lists load only when needed.

-="text anno drop controls" is multi-homed: the script's and the file's tokens add up; -="-anno" keeps an inherited layer.

licensed domains only: a preview of the evaluation watermark, naming 'host' ('' ➡️ this domain); elsewhere it is always there and names the real domain; exports and copies never include it.

Properties (Controls)

These make a single pdf-page instance as powerful as a viewer.

<pdf-page of=f id=p controls -=drop signatures=⋯></pdf-page>
new Attribute Property Type Description ⌨️ 🖱️ 👆🏻 Get Set
controls controls false | true Enable controls, like video controls. ✔️ 🚧 🚧 + +
🆕 -=drop off DOMTokenList 'drop' Ignore dropped files and links. ✔️ + +
🆕 -=controls off DOMTokenList 'controls' Turn all controls off. ✔️ ✔️ ✔️ + +
🆕 signatures signatures false | true Show this page's signature cards. ✔️ ✔️ ✔️ + +

keys work while the page is focused: click it or Tab to it. ⌘ works as Ctrl; Ctrl S | O work on every page (see below).

multi-homed: absent follows the pdf-file's (controls, signatures), then the script's; controls=- (p.controls = false) turns it off, null inherits again;
-=controls anywhere up the chain turns all controls off, whatever controls says; -="-controls" on a page brings an inherited one back.

new Keys Code
A | ← p.no--
D | → p.no++
Home p.no = 1
End p.no = p.file.js.numPages
🆕Alt ← | Alt → p.noUnclamped-- | p.noUnclamped++ (past the ends: last ↔ first)
🆕Alt End p.no = +Infinity
🆕Ctrl ← | Ctrl →p.no = -1 | p.no = +1
Q p.rotation -= 90
E p.rotation += 90
S p.rotation += 180
J p.canvas = '.js'; p.svg = false
I p.canvas = 'ium'; p.svg = false
V p.svg = true
Alt V p.svg = !p.svg
Alt T p.off.toggle('text') (or -text over an inherited one)
Alt A p.off.toggle('anno') (or -anno over an inherited one)
🆕Alt S p.signatures = !p.signatures
🆕Alt Shift S p.file.signatures = !p.file.signatures (and every page follows)
🆕F | F11 await p.toggleFullscreen()
🆕Esc leave the page (or fullscreen)
Ctrl C nav⋯.clip⋯.write (await p.image.export(Clip⋯Item))
🆕Alt Ctrl C nav⋯.clip⋯.write (await p.image.export(Clip⋯Item, { format: 'jpeg', quality: .1 }))
Ctrl D nav⋯.clip⋯.writeText(await p.image.export(URL))
Ctrl - p.scale -= 0.1
Ctrl 1 p.scale = 1.0
🆕Ctrl 2 | 3 | 4 p.scale = 2.0 | 3.0 | 4.0
Ctrl + p.scale += 0.1
Ctrl S p.file.save()
Ctrl O p.file.open()
Alt Ctrl S p.file.XFDF.save()
Alt Ctrl O p.file.XFDF.open()

Ctrl C copy image layer as image/png (image/svg+xml for pdf-page svg) to clipboard;
Ctrl C ⚠️ does not apply pdf-page style=background-color:value: the image is transparent.

Ctrl D copy image layer as a data: URL to clipboard;
Ctrl D ⚠️ does not apply pdf-page style=background-color:value.

from anywhere inside every page, a form field too, with or without controls: they never reach the browser's own save/open.

Methods (IO)

await p.image.export();
new async Method Arguments Return
🔄 image.export ( type? opts? ) type
🆕🔄 toggleFullscreen ( on? ) Boolean
🆕🔄 update () re-renders; resolves when done
🆕 anno.sync () shows the current form values

opts 🖼️ { type = Blob | URL | Clip⋯Item, format = 'png' | 'webp' | 'jpeg' | 'avif' | 'svg', quality = 0.0 ⋯ 1.0 }
opts 💎 format = 'svg' for pdf-page svg, the only engine that exports 'svg' (with fonts: script ⋯ fonts); URL is a data: URL.

fullscreen needs a user gesture (and allow=fullscreen in iframes); false when refused.

Events

const                         h = e => console.warn(e.type, e.detail);
p.   addEventListener('load', h);
p.removeEventListener('load', h);
new Event Detail Fired When Bubbles Cancelable Composed
load Page has loaded. + - ±
error Error Page has failed IO. + - ±
render object Page has rendered. + - ±
🆕 signature { signatures, open } A signature was clicked. + + ±

Can be prefixed with: script ⋯ event:prefix=type (or on the file or page),
load ➡️ pdf-page-load.

object : { reason, touched },
touched : { all, image, text, anno }.

a lazy page fires load once sized, before it renders.

preventDefault() keeps the built-in card closed.

CSS (Parts)

The layers, links, fields and signature cards of a page are parts.

pdf-page::part(link)      { background: oklch(62% .24 27 / .1); }
pdf-page::part(\<input\>) { background: oklch(96% .04 95); }
pdf-page::part(sig-card)  { font-size: .9rem; }
new Part Element Is
.js | ium canvas raster image layer canvas the page drawn by 🦊 PDF.js or ♊ PDFium; no engine token while it waits for the first draw
svg vector image layer svg the page drawn as 💎 SVG
scale layer scale the overlay that holds the text, the annotations and the signatures
text text the selectable text; empty with -=text
anno anno the links and form fields; empty with -=anno
sect section one annotation
<input> [name=name] subanno input a text field, checkbox or radio button
<select> [name=name] subanno select a list or combo box
<textarea> [name=name] subanno textarea a multi-line text field
<button> [name=name] subanno button a button
🆕 signatures sigs the page's signature hotspots and cards
🆕 sig sig-state button a hotspot over a signature field: always there, outlined while the cards show
🆕 sig-card sig-card-state article a card: compact in the page while the cards show, full as a popover
🆕 sig-corner div the cards of signatures without a visible field, top right
🆕 sig-card-status span a card's status word
🆕 sig-card-signer bdi a card's signer
🆕 sig-card-close button a card's ×
🆕 sig-card-when p a compact card's date, or its worst check
🆕 sig-card-checks ul a card's checks
🆕 sig-check sig-check-ok|warn|bad|none li one check: integrity, signer, time or revocation
🆕 sig-card-statement p the signer's own words: reason, location, contact
🆕 sig-card-notes ul further warnings
🆕 sig-card-details details field, format and algorithm
🆕 sig-card-chain ol the certificate path
🆕 sig-card-cer button a certificate's Save .cer
🆕 sig-card-fingerprint code a certificate's SHA-256 fingerprint

tokens that are not identifiers are escaped: pdf-page::part(\.js), pdf-page::part(\<input\>), pdf-page::part(\[name\=Text1\]).

state: pending verified unknown untrusted expired revoked invalid, each signature's own; the file's worst is signed.

CSS (States)

Custom states: the elements' attributes stay as written.

pdf-page:state(pending)           { opacity: .5; }
pdf-page:state(drop-target)       { outline: 2px dashed; }
body:has(pdf-file:state(invalid)) { background: oklch(95% .03 27); }
new State Element When
🆕 :state(pending) pdf-page sized, not drawn yet: a lazy page until it nears the viewport
🆕 :state(controls) pdf-page controls are on: its own, the file's or the script's; never with -=controls
🆕 :state(drop-target) pdf-page a file or a link is dragged over it; never with -=drop
🆕 :state(svg-fallback) pdf-page svg asked for, drawn on a canvas: the SVG module did not load
🆕 :state(pending) pdf-file its signatures are being checked
🆕 :state(verified) | ⋯ | :state(invalid) pdf-file the worst signature status, as signed: one at a time, none when unsigned
🆕 :fullscreen ::backdrop pdf-page shown fullscreen (F, toggleFullscreen()), and what is behind it

verified < unknown < untrusted < expired < revoked < invalid: the worst signature decides.

CSS (Custom Properties)

pdf-page { --pdf-page-background-color: transparent; --pdf-page-focus-outline: 2px solid; }
new Custom Property Default Styles
--pdf-page-background-color oklch(99% 0 0) the paper (pages are drawn transparent)
--pdf-page-outline-color light-dark(oklch(87% 0 0), oklch(38% 0 0)) the page outline
--pdf-page-focus-outline 2px solid oklch(62% .24 27) a focused pdf-page controls
🆕 --pdf-page-fullscreen-background-color oklch(12% 0 0) around the page in fullscreen
--pdf-page-text-selection-background-color oklch(62% .24 27 / .2) selected text
--pdf-page-text-highlight-radius 4px find-in-page highlights
🆕 --pdf-page-signature-verified-color light-dark(oklch(50% .14 150), oklch(78% .14 150)) verified signatures and passed checks
🆕 --pdf-page-signature-warn-color light-dark(oklch(58% .14 70), oklch(80% .13 80)) untrusted or expired signatures and checks that warn
🆕 --pdf-page-signature-invalid-color light-dark(oklch(54% .2 27), oklch(72% .17 25)) invalid or revoked signatures and failed checks
🆕 --pdf-page-signature-unknown-color light-dark(oklch(55% 0 0), oklch(70% 0 0)) unknown or pending signatures and checks not made
🆕 --pdf-page-signature-card-color CanvasText signature card text
🆕 --pdf-page-signature-card-background-color Canvas signature card background
🆕 --pdf-page-signature-card-border-color color-mix(in oklch, card-color 14%, card-background-color) signature card border and separators
🆕 --pdf-page-signature-outline 2px solid status-color a signature field on hover, on focus and while its card is open
🆕 --width --height 0px the box before the first render, e.g. 210mm 297mm

plain lengths only (px in cm mm pt pc q), both above 0, used as given: not multiplied by scale.

🙈 content-visibility: auto is built in: off-screen pages skip layout and paint, keeping their size; opt out with pdf-page { content-visibility: visible } or break="content-visibility".

💾 page sizes are remembered (localStorage), so a reload reserves every box before the PDF arrives; break="hint" opts out.

new Set by the page Value Holds
--width --height ⋯px the page box: the placeholder, then the page
🆕 --aspect-ratio 612 / 792 the page's aspect ratio
--scale 1 the scale
--w --h --u 210 297 '1mm' the paper size at scale 1, unrotated
--N '12' the file's page count
--name 'report.pdf' the file name
--re '🔲' '🔄' '🦊' '♊' '💎' the renderer: none yet, rendering, PDF.js, PDFium, SVG