runtime:pdf

Read, edit, encrypt and extract text from PDFs. PDF.open parses a document once and its reads reuse what was parsed. Edits are an immutable chain from doc.edit(), applied when it is saved, so the document itself never changes. All work runs off the event loop.

Capability: none for bytes

Bytes in and bytes out need nothing. A runtime:fs file() source needs FileRead and a file() destination FileWrite, checked and jailed as reading or writing that file would be. Status: Available.

Import

JavaScript
import { PDF } from "runtime:pdf";

// Or the default aggregate:
import pdf from "runtime:pdf";

PDF.open(input, options?)

input is a Uint8Array, an ArrayBuffer, any view, a Blob, or a runtime:fs file(). A path string is a TypeError: pass file(path). A SharedArrayBuffer is a TypeError. A damaged file is repaired the way viewers repair it, and each repair is listed in diagnostics.

OptionDefaultEffect
password—Tried as the user password, then the owner password. A file encrypted only to set permissions opens without one.
strictfalseRefuse a file that needs any repair, with ERR_PDF_MALFORMED.
maxDecodedSize67108864 (64 MiB)Bytes one stream may decode to. Over it is ERR_PDF_LIMIT with limit: "decodedSize".
signal—An AbortSignal. Aborting it stops work on this document: the open and every later read reject with the signal's reason.

The returned PDF has these properties, read when it was opened:

PropertyValue
pageCountThe number of pages.
versionThe version the file declares, such as "1.7", or null.
encryptedWhether the file is encrypted.
permissionsFor an encrypted file, what it allows a reader without the owner password: { print, modify, copy, annotate, fillForms, accessibility, assemble, printHighQuality }, each a boolean. null when not encrypted.
info{ title, author, subject, keywords, creator, producer, creationDate, modDate }; dates are Dates. A field the file does not state, or a date it states in a form that cannot be read, is absent.
diagnosticsEach repair opening needed: { kind, message, offset? }. Empty for a sound file.

Reads

MethodResolves to
pages()Per page: width and height as displayed (points, crop box, rotation applied), rotate, mediaBox and cropBox as [left, bottom, right, top].
text()Every page's text in content-stream order, as pdftotext -raw gives it, each page followed by a form feed (\f).
pageText(index, { chars? }){ text, lines }; each line is { text, bbox, words } and each word { text, bbox }, in displayed page coordinates with y up. With chars: true each line also has chars: { text, origin, end, size, font, invisible }, where origin and end are the baseline's [x, y]. An index outside the document is a RangeError.
outlines()The bookmarks: { title, page?, open, children }. page is the index of the page the bookmark opens, when it names a page in this document.
destinations()The named destinations: { name, page? }.
xmp()The XMP metadata packet as text, or undefined.
sanitizeReport(options?)What edit().sanitize(options) would remove, counted: { javascript, externalActions, links, attachments, xfa, multimedia, metadata }. The document is not changed.
attachments()Embedded files: { name, filename?, description?, type?, size? }.
attachment(name)That file's bytes, or undefined.

Text extraction reads Type 1, TrueType, Type 3 and CID fonts, ToUnicode maps and the Adobe CJK collections. It does not detect columns or reorder right-to-left text; clipped and invisible text is left out.

close() releases the document; a later read, or an edit of it not yet saved, is a TypeError. PDF implements Symbol.dispose, so using doc = await PDF.open(…) closes it at the end of the block. A document never closed is released when it is garbage-collected.

doc.edit()

Returns a PDFEdit. Each method returns a new PDFEdit and leaves the one it was called on unchanged, so one document can be saved several ways. Page indices are checked at the call against the pages the edit has at that point (pageCount); outside it is a RangeError.

MethodEffect
select(pages)Keep these pages, in this order. A page listed twice is duplicated.
delete(pages)Remove these pages, with the bookmarks and named destinations that point to them.
rotate(pages, degrees)Turn clockwise by a multiple of 90, added to the page's rotation.
crop(pages, [left, bottom, right, top])Set the visible region, in points.
setMediaBox(pages, [left, bottom, right, top])Set the physical page size, in points.
setOutlines(items)Replace the bookmarks with [{ title, page?, open?, children? }]; page is an index into the pages the edit has at that point. An empty array removes them.
append(other, pages?)Append the pages of another PDF or PDFEdit — all, or the indices given. Shared fonts and images are copied once.
setInfo(info)Set document information: strings for the text fields, Dates for creationDate and modDate, null to remove one. Fields not named are kept. The XMP metadata is rewritten to match.
addAttachment(name, data, { type?, description? })Embed a file, replacing one of the same name. data is copied at the call.
removeAttachment(name)Remove an embedded file.
sanitize(options?)Remove active and hidden content: javascript, externalActions (launch, submit-form, import), attachments, xfa and multimedia by default, and links and metadata when set to true. Each option is a boolean.

pages is an index or an array of indices. An unknown setInfo field or sanitize option is a TypeError.

Saving

bytes(options?) resolves to the PDF; blob(options?) to a Blob of type application/pdf; write(file(path), options?) writes it and resolves to the number of bytes written.

OptionDefaultEffect
incrementalfalseAppend the edits to the original bytes, which stay an exact prefix. Without it the file is rewritten, keeping only what is reachable.
version"1.7""1.7" or "2.0".
objectStreamstruePack objects into compressed object streams. false writes a classic cross-reference table.
compresstrueFlate-compress uncompressed streams.
encrypt—{ ownerPassword, userPassword?, permissions?, encryptMetadata? } encrypts with AES-256. ownerPassword must be non-empty; userPassword defaults to "", which opens without a password with permissions applied. A permission not given is allowed. false writes an encrypted source decrypted.
signal—An AbortSignal that stops this save.

A full save of a document that is encrypted, or that appends pages from one, must pass encrypt: an object to keep it encrypted or false to write it decrypted. Without either it is a TypeError. An incremental save keeps the file's encryption and cannot change it.

The encryption's random bytes come from the same source as crypto. The output is otherwise deterministic: the same document, edits and options give the same bytes.

Errors

codeWhen
ERR_PDF_INVALID_PASSWORDNo password was given, or it matched neither the user nor the owner password.
ERR_PDF_ENCRYPTION_UNSUPPORTEDThe file is encrypted to a certificate (public-key security) or with an unknown scheme.
ERR_PDF_LIMITA resource limit was reached. limit names it: decodedSize, compressionRatio, depth, objects, objectSize, xrefSections or pages.
ERR_PDF_MALFORMEDThe file is not a readable PDF, or strict is set and it needs repair.
ERR_PDF_INCREMENTAL_REFUSEDAn incremental save would be unsafe — the file needed repair, or uses RC4 or AES-128, which are read but never written. Save in full instead.

A bad argument is a TypeError or RangeError at the call. An aborted signal rejects with its reason. Reading or writing a file() fails with the runtime:fs codes.

Last updated on
Edit this page