Skip to content
Genom
API reference

@genomdev/core

Genom core: byte sources, ZIP, streaming XML, format detection, the plugin registry, addressing and the content model

390 exported symbols across 6 entry points

@genomdev/core

Classes

BlobByteSource
class BlobByteSource implements ByteSource

A source backed by a browser `Blob`/`File`, read lazily in chunks.

name
string | undefined
File name when known. Used as a hint for format detection.
mimeType
string | undefined
MIME type when the source reports one.
byteLength
number
Total size of the source in bytes.
slice
(start: number, end?: number) => Promise<Uint8Array>
Reads the `[start, end)` range. Implementations must return exactly the requested number of bytes or throw {@link OutOfBoundsError}. Short reads are not allowed, otherwise every parser would have to re-check the length after each call.
ByteReader
class ByteReader

A synchronous cursor-based reader over a `Uint8Array`. Binary formats (ZIP headers, OLE2/CFB, PDF xref tables, TIFF IFDs) are read as sequences of fixed-width fields, and tracking the offset by hand in every parser is a reliable way to introduce bugs. The reader does it for us and bounds-checks every step.

offset
number
offset
number
byteLength
number
remaining
number
eof
boolean
skip
(count: number) => this
seek
(offset: number) => this
u8
() => number
u16
(littleEndian?: boolean) => number
u32
(littleEndian?: boolean) => number
u64
(littleEndian?: boolean) => bigint
u64AsNumber
(littleEndian?: boolean) => number
Reads a 64-bit value as a `number`. ZIP64 and PDF use 64-bit offsets, but real files never exceed 2^53 bytes and `number` is far more convenient for offset arithmetic.
i8
() => number
i16
(littleEndian?: boolean) => number
i32
(littleEndian?: boolean) => number
f32
(littleEndian?: boolean) => number
f64
(littleEndian?: boolean) => number
bytes
(count: number) => Uint8Array
Returns a view onto the underlying buffer without copying.
peek
(count: number) => Uint8Array
Reads without advancing the cursor.
matches
(signature: readonly number[]) => boolean
Checks a signature at the current position without advancing the cursor.
CancelledError
class CancelledError extends GenomError

The operation was aborted through an AbortSignal.

CorruptFileError
class CorruptFileError extends GenomError

The file was identified, but its contents violate the format specification.

offset
number | undefined
Byte offset where the violation was found, when known.
EncryptedFileError
class EncryptedFileError extends GenomError

The document is encrypted and no usable password was supplied.

FormatRegistry
class FormatRegistry
register
(...entries: readonly FormatEntry[]) => this
Registers formats. Returns `this`, so a registry is one expression.
formats
() => FormatId[]
Every format id the registry could open, without loading anything.
supports
(format: FormatId) => boolean
Whether a format id is registered.
resolve
(source: ByteSource) => Promise<ResolvedFormat | undefined>
Finds the module for a file, loading exactly the ones it has to. A descriptor whose rules answer outright is fetched and used. Where several only shortlist themselves — the OLE2 case — they are fetched in order and asked, which is the one path that can cost more than one chunk.
open
(source: ByteSource, options?: OpenOptions) => Promise<GenomDocument>
Detects the format and opens the document with the matching module.
moduleFor
(format: FormatId) => Promise<FormatModule | undefined>
The module for a format id, loading it if necessary.
GenomError
class GenomError extends Error

Base class for every error raised by Genom. A common ancestor lets consumers distinguish "the file failed to open" from a genuine bug in their own code with a single `instanceof` check.

code
string
Stable machine-readable code; unaffected by message wording changes.
HttpByteSource
class HttpByteSource implements ByteSource

A source backed by HTTP range requests. Lets a document be opened from a URL without downloading it in full: a few kilobytes of tail data are enough to list the contents of a 200 MB file. If the server does not support ranges, the source downloads the file once and serves subsequent reads from memory.

name
string | undefined
File name when known. Used as a hint for format detection.
mimeType
string | undefined
MIME type when the source reports one.
create
(url: string, init?: RequestInit) => Promise<HttpByteSource>
byteLength
number
Total size of the source in bytes.
slice
(start: number, end?: number) => Promise<Uint8Array>
Reads the `[start, end)` range. Implementations must return exactly the requested number of bytes or throw {@link OutOfBoundsError}. Short reads are not allowed, otherwise every parser would have to re-check the length after each call.
dispose
() => void
Releases retained resources such as caches or network connections.
MemoryByteSource
class MemoryByteSource implements ByteSource

A source backed by a buffer already held in memory.

name
string | undefined
File name when known. Used as a hint for format detection.
mimeType
string | undefined
MIME type when the source reports one.
byteLength
number
Total size of the source in bytes.
slice
(start: number, end?: number) => Promise<Uint8Array>
Reads the `[start, end)` range. Implementations must return exactly the requested number of bytes or throw {@link OutOfBoundsError}. Short reads are not allowed, otherwise every parser would have to re-check the length after each call.
bytes
() => Uint8Array
Synchronous access to the whole buffer; for internal parser use only.
NotImplementedError
class NotImplementedError extends GenomError

A format feature that has not been implemented yet. A dedicated type lets the viewer show "this part of the document is not supported yet" instead of a generic read failure.

OutOfBoundsError
class OutOfBoundsError extends GenomError

A read ran past the end of the byte source.

Rc4
class Rc4

RC4 as a keystream generator: the cipher is symmetric, so one direction.

next
() => number
The next keystream byte. XOR it with a byte to encipher or decipher.
UnsupportedFormatError
class UnsupportedFormatError extends GenomError

The file format was not recognised, or no plugin is registered for it.

detectedFormat
string | undefined
UnsupportedMarkupError
class UnsupportedMarkupError extends GenomError

Markup the reader walked past that nothing has declared it may walk past. Only ever raised in strict mode, which no viewer turns on: a reader that stops at the first unknown attribute is useless against real files, where every generator writes something nobody has seen. What it is for is the opposite situation — a development run over a corpus, where an element the parser silently ignores is indistinguishable from one it handles, and a gap therefore survives for as long as nobody happens to look at the right page. Strict mode makes the ignoring explicit: everything the parser passes over must be named in the registry of markup we have decided draws nothing, with the reason. Anything else stops the parse and names itself.

markup
string
The markup that was not accounted for, as `w:element` or `w:element@w:attr`.
context
string
The element the markup was found in, when it has one.
XmlPullParser
class XmlPullParser

A streaming (pull) XML parser. This is the foundation of the whole OOXML layer and the reason large documents stay fast. Building a full node tree for a 40 MB `document.xml` costs several hundred megabytes and a second of allocation before any useful work starts. A pull parser lets the consumer walk the file once and build only the domain model it actually needs, allocating nothing per element it chooses to skip. Design notes that matter for performance: - Element and namespace names are interned. A document contains millions of `w:t`/`w:r`/`w:p` tags but only a few dozen distinct names, so interning turns name comparison into pointer comparison and removes almost all string allocation. - Attributes are parsed lazily. Most elements are visited without their attributes ever being read, so they are only materialised on demand. - Text is decoded lazily. Whitespace-only text between tags is extremely common and is skipped without ever becoming a JavaScript string. The parser deliberately supports no DTD or external entities: office files never use them, and processing them is a well-known vulnerability class (XXE).

intern
(value: string) => string
Interns a string so repeated names share one allocation.
event
XmlEvent
localName
string
Local name of the current element, without its prefix. Interned.
namespace
string
Namespace URI of the current element. Interned.
prefix
string
depth
number
Nesting depth; the root element sits at depth 1.
isWhitespace
boolean
Whether the current `Text` event holds only whitespace. Lets a caller that materialises a tree drop indentation without having to inspect the text, and without the parser deciding that whitespace is never content — which for OOXML is false.
text
string
Text of the current `Text` event, decoded on first access.
next
() => boolean
Advances to the next event. Returns `false` once the document has been fully consumed.
tagStart
number
Where the current element's tag begins, as an offset into the source text. See {@link captureElement}, which is what this exists for.
offset
number
Where the parser stands: one past the last character it has consumed.
source
string
The decoded document, for whoever holds an offset into it.
rawAttributes
string
The attribute region of the current start tag, exactly as written. What the root element of a part needs to survive a rewrite: its namespace declarations, in its own prefixes, in its own order — and there are usually a dozen of them, several `mc:Ignorable`, and one the file invented. Every fragment preserved from below depends on them, so they are carried whole rather than rebuilt from the prefixes this parser happened to resolve.
captureElement
() => { readonly start: number; readonly end: number; }
Skips the current element and reports the span of source it occupied. The preserving reader's one move: an element the model has no shape for is walked past and its position written down, so that saving the document can put those characters back untouched, in their place, with their own namespace prefixes and their own attribute order. Must be called on a `StartElement`; leaves the parser exactly where {@link skipElement} does.
onSkip
((namespace: string, localName: string, parent: string) => void) | undefined
Called for every element a reader passes over. A skipped element is an element the viewer draws nothing for, whether it was skipped because nobody has written the reader yet or because the element carries nothing a page can show. The two are indistinguishable from here and deliberately so: the point of the hook is an inventory of what a corpus contains and this parser walks past, measured rather than remembered, so that a gap is a line in a report instead of a note in a document saying it should be looked at one day. Static, and off unless something sets it: the readers are hot loops and a per-instance option would have to be threaded through every one of them.
onUnusedAttribute
((namespace: string, localName: string, attribute: string, parent: string) => void) | undefined
Called for every attribute of a start tag that no reader asked for. The element-level hook alone tells half the truth. A reader that takes `w:line` and `w:lineRule` from `w:spacing` and walks past `w:beforeAutospacing` has read the element, so nothing is reported — and the attribute that changes the spacing above every paragraph is invisible to the inventory. Attributes are where most of the remaining specification lives, so they are counted the same way elements are. `attribute` is the key as the attribute map holds it: a bare name for an unprefixed attribute, `namespace|local` for a qualified one. Costs a full attribute parse per element, so it is only paid when someone installs the hook — which is the coverage report and the strict mode, never a reader.
consumeElement
() => void
Passes over an element the reader has already acted on. The same walk as `skipElement`, without the report: an element with no attributes and no children — a tab, a soft hyphen, a footnote mark — is read by its name alone and then stepped over, and counting that as something the parser does not know buries the real gaps under the parser's own idiom. Five thousand tabs stood at the head of the list that way.
skipElement
() => void
Skips the entire subtree of the current element. The single most valuable operation for performance: the document parser can decide from the element name alone that a branch is irrelevant (revision metadata, spell-check state, rendering hints) and discard it without allocating anything. Must be called while positioned on a `StartElement`.
attr
(name: string, namespace?: string) => string | undefined
Value of an attribute of the current start tag, or `undefined`.
attributes
() => ReadonlyMap<string, string>
All attributes of the current start tag, keyed as `name` or `ns|name`.
readElementText
() => string
Reads the concatenated text content of the current element and consumes it. Positioned on a `StartElement`, it returns everything up to the matching end tag, ignoring nested markup. This is the common case for `<w:t>`, `<a:t>` and similar leaf elements.
namespaceFor
(prefix: string) => string
Resolves a namespace prefix against the declarations currently in scope. Needed wherever markup names a namespace by prefix in an attribute value rather than on an element — `mc:Choice/@Requires` being the case that matters, since deciding whether a document's newer markup can be read at all means turning those prefixes into namespaces.
XmlWriter
class XmlWriter
depth
number
How deep the writer stands; the root element sits at depth 1.
start
(name: string, attributes?: Readonly<Record<string, string | undefined>>) => this
Opens an element. The name is written as given, prefix and all. Attributes may be passed here or added with {@link attr} until the first child is written.
attr
(name: string, value: string | number | undefined) => this
Adds an attribute to the start tag being written.
attrs
(attributes: Readonly<Record<string, string | number | undefined>>) => this
Adds every attribute of a record whose value is not `undefined`.
rawAttributes
(text: string) => this
Writes an attribute region exactly as it was read. How the root element of a preserved part keeps its own namespace declarations: they are the contract every spliced fragment below depends on, and re-deriving them would mean deciding which prefix stands for which namespace — a decision the document has already made.
end
() => this
Closes the innermost open element.
element
(name: string, attributes?: Readonly<Record<string, string | undefined>>) => this
Opens and immediately closes an element: `<w:b/>` with its attributes.
text
(value: string) => this
Writes character content, escaped.
raw
(markup: string) => this
Writes markup through, unescaped and unexamined. The whole point of the preserving writer: a span of the original part is put back as it stood. Nothing here checks that it is well formed, because it came out of a parse of the same document and therefore is.
toString
() => string
The finished document. Every element must have been closed.
toBytes
() => Uint8Array
ZipArchive
class ZipArchive

Random-access ZIP archive reader. Works on top of a {@link ByteSource} rather than an in-memory buffer: listing the contents of a 100 MB xlsx only requires reading a few kilobytes of central directory at the end of the file. Individual entries are inflated on demand, which is essential for large workbooks where most of the weight sits in one or two sheets that may never be opened.

open
(source: ByteSource, options?: ZipArchiveOptions) => Promise<ZipArchive>
source
ByteSource
The bytes the archive reads from. Exposed for one caller: a document that wants to stop depending on the file it was opened from reads the whole source once and reopens itself over the copy. See `DocxDocument.detach`.
entries
() => ZipEntry[]
All entries, in central directory order.
names
() => string[]
File names, excluding directories.
has
(name: string) => boolean
entry
(name: string) => ZipEntry | undefined
An entry by name, and by name in any case if that finds nothing. Part names are case-sensitive in the specification and not in practice: a workbook whose shared strings are stored as `xl/SharedStrings.xml` opens in Excel and would lose every text cell here. The exact match is tried first, so a package that really does hold two names differing only in case still resolves each of them to itself.
totalUncompressedSize
() => number
Total uncompressed size of every entry; used for progress reporting.
read
(name: string) => Promise<Uint8Array>
Reads and inflates the contents of an entry. The result is cached, bounded by {@link ZipArchiveOptions.maxCacheBytes}.
readText
(name: string) => Promise<string>
Reads an entry and decodes it as UTF-8; every OOXML part uses that encoding.
readUncached
(name: string) => Promise<Uint8Array>
Reads an entry without touching the cache. Used for one-shot reads of large parts — media files that are immediately turned into a blob URL, for instance — where caching would only waste memory.
readStored
(name: string) => Promise<{ readonly compressed: Uint8Array; readonly method: number; readonly crc32: number; readonly uncompressedSize: number; readonly lastModified: Date | undefined; readonly flags: number; readonly versionMadeBy: number; readonly versionNeeded: number; readonly externalAttributes: number; readonly internalAttributes: number; readonly extra: Uint8Array; readonly localExtra: Uint8Array; readonly dataDescriptor: Uint8Array | undefined; readonly dosTime: number; readonly dosDate: number; readonly zip64?: ZipEntry["zip64"]; readonly local: { readonly versionNeeded: number; readonly flags: number; readonly method: number; readonly dosTime: number; readonly dosDate: number; readonly crc32: number; readonly compressedSize: number; readonly uncompressedSize: number; }; }>
An entry's bytes exactly as this archive stores them, deflated and all. What lets a package be saved without touching the parts nobody edited: the writer takes these through unchanged, with the same method and the same checksum, so a picture or a macro project is copied rather than inflated and recompressed. Never cached — these bytes are written once and dropped, and holding a hundred megabytes of images to write them one at a time is exactly the memory this avoids.
clearCache
() => void
Drops every inflated entry held in the cache.
cachedBytes
number
Bytes currently held by the inflate cache; exposed for diagnostics.
ZipWriter
class ZipWriter
names
() => readonly string[]
The names written so far, in the order they were added.
has
(name: string) => boolean
add
(name: string, content: Uint8Array | string, options?: ZipWriteOptions) => Promise<void>
Adds a part, deflating it unless told otherwise. Text is accepted as well as bytes because every part of an OOXML package that this library produces is UTF-8 XML, and encoding it at the boundary is one copy fewer than encoding it at every call site.
addRaw
(entry: RawZipEntry) => void
Adds an entry copied from another archive, compressed bytes and all. Nothing is inflated and nothing is recompressed: what the source archive held is what this one holds, with the same checksum and the same method.
toBytes
(options?: { readonly directoryOrder?: readonly string[]; }) => Uint8Array
The finished archive. `directoryOrder` names the order the central directory should list entries in, where that is not the order they physically stand in. A ZIP has two orders and nothing requires them to agree — see the note in the OPC writer about the document that lists them differently — and a copy that is to be byte for byte has to reproduce both. Names it does not mention keep their position; names it mentions that were never added are ignored.

Functions

advancesOf
function advancesOf(tables: TrueTypeTables): AdvanceTable | undefined

Reads `hhea` and `hmtx` into a table of advances. `hmtx` is not a plain array: it holds `numberOfHMetrics` pairs of advance and left side bearing, and then *bearings alone* for every glyph after that, all of which share the last advance. That tail is how a font of ten thousand fixed-width glyphs costs four bytes each instead of eight, and a reader that misses it returns nothing for most of a CJK font. Nothing is returned where the font does not say: a CFF-based OpenType file still carries `hhea` and `hmtx`, but a file missing either has no horizontal metrics to give and a caller must measure instead of guessing.

advanceTableFor
function advanceTableFor(family: string): Promise<AdvanceCuts | undefined>

The advances of a family, or nothing where none were written.

aesCbcDecrypt
function aesCbcDecrypt(key: Uint8Array, data: Uint8Array, options?: AesOptions): Uint8Array

AES-CBC. By default the first sixteen bytes of `data` are the initialisation vector, which is how PDF stores it. Padding is PKCS#7 and is stripped unless asked otherwise — a padding byte out of range is treated as no padding rather than as an error, because a stream whose last block is damaged is still a stream.

aesCbcEncryptNoPadding
function aesCbcEncryptNoPadding(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Uint8Array

AES-CBC encryption with an explicit IV and no padding.

aesEcbDecrypt
function aesEcbDecrypt(key: Uint8Array, data: Uint8Array): Uint8Array

AES-ECB, one block at a time and no padding.

attr
function attr(element: XmlElement, name: string, namespace?: string): string | undefined

Attribute value. Unprefixed attributes are looked up with an empty namespace.

attrBoolean
function attrBoolean(element: XmlElement, name: string, namespace?: string): boolean | undefined

Boolean attribute in the OOXML sense. In the `ST_OnOff` schema `1`, `true` and `on` mean true; the absence of the attribute on a flag element (such as `<w:b/>`) also means true.

attrNumber
function attrNumber(element: XmlElement, name: string, namespace?: string): number | undefined

Numeric attribute value; `undefined` when absent or not a number.

breakOpportunitiesOf
function breakOpportunitiesOf(text: string, options?: LineBreakOptions): number[]

The offsets at which a line may end, for a caller that wants a list. The end of the text is not among them: every line ends there and no caller has to be told so.

canDeflate
function canDeflate(): boolean

Whether this runtime can deflate at all; see the note above.

child
function child(element: XmlElement, namespace: string, name: string): XmlElement | undefined

First direct child with the given name.

childElements
function childElements(element: XmlElement): XmlElement[]

Direct child elements; text nodes are dropped.

children
function children(element: XmlElement, namespace: string, name: string): XmlElement[]

All direct children with the given name.

classicalMathConstants
function classicalMathConstants(unitsPerEm: number): MathConstants

The classical constants, in the units of an em of `unitsPerEm`.

compareLocators
function compareLocators(a: Locator, b: Locator): number

Orders two locators the way the document reads. Needed wherever a range has to be normalised — a user selects backwards about half the time — and wherever highlights are merged. Flows are ordered by their kind and index so that a body address always precedes a footnote's, which is arbitrary but stable, and stable is the property that matters.

compileHyphenation
function compileHyphenation(data: HyphenationData): HyphenationTable

The parsed form of a language's patterns, parsed once per module.

contentHash
function contentHash(bytes: Uint8Array): string

Eight hex digits identifying the bytes a locator was made against. FNV-1a over the whole buffer, which is not a cryptographic hash and is not meant to be. The question it answers is "is this the same file as the one the address came from", where the alternative to a wrong answer is a wrong highlight, not a security breach. Sixty-four bits folded to thirty-two make an accidental collision a once-in-four-billion event between two files a user has open at the same moment, and a real hash would cost a megabyte of bookkeeping and a dependency for it.

contrastingTextColor
function contrastingTextColor(background: Rgba): Rgba

Picks a readable text colour for a given background. Needed wherever a format specifies only a fill: a table header with a dark shade and default black text would be unreadable. The 0.5 threshold is on WCAG relative luminance.

crc32
function crc32(bytes: Uint8Array): number

The CRC-32 of a run of bytes, as an unsigned 32-bit number.

cutOf
function cutOf(cuts: { readonly r?: AdvanceRanges; readonly b?: AdvanceRanges; readonly i?: AdvanceRanges; readonly z?: AdvanceRanges; }, bold: boolean, italic: boolean): AdvanceRanges | undefined

The cut of a family a run wants. A missing cut falls back to the regular rather than to nothing: a family that files one file for all four is written down once, and the browser synthesises the other three from it — which changes the shape of the letters and not their advances, so the regular's widths are the right answer for all four.

decodeAdvances
function decodeAdvances(ranges: AdvanceRanges): FaceAdvances

Turns the encoded ranges into something that can be asked about a character. Kept per table rather than per call: a document asks this for every character of every line, several times over as lines are tried and abandoned, and decoding a face on each of those would cost more than the layout.

decodeCp1251
function decodeCp1251(bytes: Uint8Array): string

CP1251: Cyrillic text in legacy Microsoft Office files.

decodeEntities
function decodeEntities(text: string): string

Expands predefined entities and numeric character references.

decodeLatin1
function decodeLatin1(bytes: Uint8Array): string

Latin-1 (ISO-8859-1): used for legacy ZIP entry names and PDF strings.

decodeUtf16le
function decodeUtf16le(bytes: Uint8Array): string

UTF-16LE: the native string encoding of OLE2/CFB and many Windows structures.

decodeUtf8
function decodeUtf8(bytes: Uint8Array, fatal?: boolean): string
decodeXml
function decodeXml(bytes: Uint8Array): string

Decodes an XML part, honouring the byte order mark it may start with. Nearly every OOXML part is UTF-8, and this exists for the ones that are not: XML permits UTF-16, a writer occasionally uses it, and Excel opens such a file without comment. Decoding those bytes as UTF-8 yields a string of NULs with no root element — a whole workbook lost to two bytes at the front.

deflate
function deflate(bytes: Uint8Array): Promise<Uint8Array>

Zlib-wrapped DEFLATE: the same stream with two bytes of header and a checksum.

deflateRaw
function deflateRaw(bytes: Uint8Array): Promise<Uint8Array>
descendants
function descendants(element: XmlElement, namespace: string, name: string): XmlElement[]

All descendants at any depth with the given name.

describeFormat
function describeFormat(id: FormatId): FormatDescriptor | undefined
detectFormat
function detectFormat(source: ByteSource): Promise<DetectionResult>

Determines the file format from its contents, name and MIME type. Signature, name and MIME type only — this is the cheap pass. For a ZIP the result is `probable` and says so: telling docx, xlsx, pptx and odt apart means reading `[Content_Types].xml`, which `probe()` does in the next step. Keeping the two apart is what lets the expensive one be skipped for the formats that announce themselves in their first five bytes.

encodePng
function encodePng(width: number, height: number, rgba: Uint8Array): Uint8Array

Encodes 8-bit RGBA pixels, top row first, as a PNG.

encodeUtf8
function encodeUtf8(text: string): Uint8Array
escapeAttribute
function escapeAttribute(value: string): string

Escapes an attribute value: text, plus the quote that delimits it.

escapeText
function escapeText(value: string): string

Escapes text content. `&` and `<` are required; `>` is escaped because Word escapes it, and a comparison against Word's own output should not turn on that. Characters XML cannot express at all — the C0 controls other than tab, newline and return — are dropped rather than written, because writing them produces a part no parser will read back, this one included. Tested by scanning rather than by a regular expression: the fast path runs over every character of every run of a document, and a scan that stops at the first character needing work is both quicker and legible.

firstDescendant
function firstDescendant(element: XmlElement, namespace: string, name: string): XmlElement | undefined

First descendant at any depth with the given name.

formatByExtension
function formatByExtension(value: string): FormatDescriptor | undefined

Looks up a format by extension. Accepts `docx`, `.docx` or a whole file name.

formatByMimeType
function formatByMimeType(mimeType: string): FormatDescriptor | undefined
formatLocator
function formatLocator(parts: { version?: number; hash?: string; flow: LocatorFlow; steps?: readonly LocatorStep[]; offset?: number | undefined; cell?: string | undefined; }): Locator

Builds a locator string. The inverse of {@link parseLocator} for every input it accepts; the pair is covered by a round-trip test rather than by inspection, because the grammar is small enough to be exhaustively generated and too fiddly to eyeball.

formsOf
function formsOf(codes: readonly number[]): Array<JoiningForm | undefined>

The shape each code point takes, in the string's own order. `undefined` where the character has no shape to take — a space, a digit, a vowel mark — so that a caller can tell "the character as written" from "the letter, isolated", which reach the same glyph but are not the same question. The rule reads a letter's two neighbours *through* the transparent marks between: a beh followed by a fatha and then a reh is medial, because as far as the join is concerned the fatha is not there.

getLicenseKey
function getLicenseKey(): string | null

The key most recently passed to {@link setLicenseKey}, or `null`. Returned verbatim and unparsed. Nothing in the library calls this yet; it exists so that an application can confirm its own bootstrap ran, and so that the eventual check has somewhere to read from.

hasAdvances
function hasAdvances(family: string): boolean

Whether this project has written the advances of a family down.

hasPatterns
function hasPatterns(tag: string): boolean

Whether a language tag has patterns at all; asked before anything is loaded.

hslToRgb
function hslToRgb(hsl: Hsl, alpha?: number): Rgba
hyphenationOf
function hyphenationOf(languages: Iterable<string>): Promise<Hyphenation>

The hyphenation of one document: its languages, resolved. Asked for every `w:lang` the document states — Word hyphenates each run by its own language, and a Ukrainian paper whose body is English is hyphenated as English. A tag nobody wrote patterns for is simply absent, and the word comes out whole.

hyphenationPointsOf
function hyphenationPointsOf(word: string, table: HyphenationTable, limits?: { readonly left?: number; readonly right?: number; }): readonly number[]

The places a line may end inside this word, counted in characters kept. `hyphenationPointsOf('Silbentrennung', german)` is `[3, 6, 9]` — `Sil-`, `Silben-`, `Silbentren-` — and the caller decides which of them the column has room for. Nothing is returned for a word the minima leave no room in, so a caller can ask of every word and pay a map lookup for the short ones.

inflate
function inflate(compressed: Uint8Array, expectedSize?: number): Promise<Uint8Array>

Zlib-wrapped DEFLATE: the same compression with two bytes of header. Needed because the two are not interchangeable and nothing says which is which. A ZIP entry is raw; the picture inside a Word document says "DEFLATE" in its header and is a zlib stream — a fact discoverable only by looking at the bytes, where `78 01` at the front is the giveaway. Feeding one to the other's decoder fails immediately rather than producing rubbish, which is the one mercy of it.

inflateRaw
function inflateRaw(compressed: Uint8Array, expectedSize?: number): Promise<Uint8Array>
inflateRawSync
function inflateRawSync(compressed: Uint8Array, expectedSize?: number): Uint8Array

Raw DEFLATE with no wrapper, decompressed synchronously.

inflateSync
function inflateSync(compressed: Uint8Array, expectedSize?: number): Uint8Array

DEFLATE with or without its zlib wrapper, whichever the bytes turn out to be. Deliberately not two functions. Files lie about this constantly — a PDF stream marked `/FlateDecode` is supposed to be zlib-wrapped and a good number are raw, and the header check is two bytes — so the tolerant reading is the only one worth having at a call site that has just been handed a document.

isElement
function isElement(node: XmlNode): node is XmlElement
isLazy
function isLazy(entry: FormatEntry): entry is LazyFormat
isLocator
function isLocator(value: unknown): value is Locator

True when the string is shaped like a locator. Does not validate the path.

isOnOffTrue
function isOnOffTrue(value: string | undefined): boolean

Interprets an `ST_OnOff` value.

isText
function isText(node: XmlNode): node is XmlText
isZlib
function isZlib(bytes: Uint8Array): boolean

Whether the bytes begin with a zlib header rather than raw DEFLATE.

joiningTypeOf
function joiningTypeOf(code: number): JoiningType

The joining type of one code point. The two zero-width controls and the bidirectional marks live outside the Arabic block and are answered before it is searched: `ZWJ` joins where no letter does, `ZWNJ` refuses to, and the left-to-right and right-to-left marks are invisible to the join in the way a vowel mark is. A document that sets Persian writes them by the hundred, and reading one as a letter would break every word it stands in.

joins
function joins(text: string): boolean

Whether a string holds a letter whose shape depends on its neighbours.

kerningOf
function kerningOf(kern: Uint8Array | undefined, unitsPerEm: number): KernTable | undefined
lengthToCss
function lengthToCss(length: Length | undefined): string | undefined

Converts a {@link Length} to a CSS string, or `undefined` when it is `auto`.

lengthToPixels
function lengthToPixels(length: Length | undefined, reference?: number): number

Converts a {@link Length} to pixels; percentages need a reference size.

lineBreakActions
function lineBreakActions(text: string, options?: LineBreakOptions): Uint8Array

The break action after every UTF-16 code unit of `text`. The last unit is always {@link BREAK_MANDATORY}: LB3 says a line ends at the end of the text, and a caller that wants to join two runs must therefore ask about them together rather than concatenate two answers.

lineBreakClassOf
function lineBreakClassOf(point: number): LineBreakClass

The Line_Break class of a code point, before LB1 resolves it.

locatorFlow
function locatorFlow(locator: Locator): LocatorFlow

The flow a locator addresses, without parsing the rest of it.

locatorSteps
function locatorSteps(locator: Locator): readonly LocatorStep[]

The path steps of a locator.

locatorWithOffset
function locatorWithOffset(locator: Locator, offset: number | undefined): Locator

The same locator with a different character offset.

macGlyphName
function macGlyphName(index: number): string | undefined

The name at an index of the standard Macintosh ordering, if it has one. Exported for the other reader of this list: an unembedded composite font whose CIDs are glyph indices into a font nobody has. The core Microsoft faces are all built in this order, so the ordering is the only statement there is about what those indices meant — see `unicodeOf`.

matchRule
function matchRule(rule: DetectRule, probe: ProbeResult): MatchStrength

Applies one rule to what the probe found out.

matchStrength
function matchStrength(rules: readonly DetectRule[] | undefined, probe: ProbeResult): MatchStrength

The best any of a module's rules could do on this file.

mathConstantsOf
function mathConstantsOf(math: Uint8Array | undefined): MathConstants | undefined

Reads the constants out of a `MATH` table. Nothing is returned for a font without one, which is nearly every font: a caller wanting to set an equation in such a face should fall back to {@link classicalMathConstants}, not refuse.

md5
function md5(message: Uint8Array): Uint8Array

MD5, sixteen bytes out.

normalizeForMatch
function normalizeForMatch(source: string, options?: NormalizeOptions): NormalizedText

Normalises text for quote matching and records where every character came from. The map back is the whole point, and the reason this is not three chained `replace` calls: a match found in normalised space has to become a range in the document, and every transformation that changes a length has to be accounted for as it happens. Unicode normalisation is applied per cluster rather than to the finished string. Running NFC over the assembled result would silently shorten the text out from under the offsets just recorded — the map would be right for every document without a diacritic and wrong for every document with one, which is the worst failure mode on offer. Per *character* would be no better in the other direction: NFC composes a base and its combining mark into one character, and a character examined alone has nothing to compose with, so decomposed text would stay decomposed and a quote stored precomposed would never match it. So a base character and the marks that follow it are normalised together, and every character that comes out is mapped to where the base came from.

parseHexColor
function parseHexColor(value: string | undefined): Rgba | undefined

Parses `ST_HexColor`: six or eight hex digits, with or without a leading hash. The special value `auto` means "the application picks the colour", so it returns `undefined` and lets the caller apply its own contextual rule (usually black text on a light background).

parseLocator
function parseLocator(locator: Locator): LocatorParts

Parses a locator, or throws. Throwing rather than returning `undefined` because a malformed locator is a programming error on the caller's side — locators are produced by this library, not typed by hand — and a silent `undefined` here surfaces three layers away as a highlight that does not appear.

parseTrueType
function parseTrueType(bytes: Uint8Array, index?: number): TrueTypeTables | undefined

Reads a font program's table directory, and the tables worth reading. Takes the bytes of a `.ttf`, `.otf` or `.ttc`, and the first font of a collection where it is one. Nothing is returned for a file whose sfnt version is not one of the four in use — which is the check that keeps a `.pfb`, a bitmap font or a truncated download from being read as if the numbers at its head meant offsets. Every bounds check here is load-bearing rather than defensive: a font embedded in a document is arbitrary bytes from a stranger, and half of them have been through a subsetter.

parseXml
function parseXml(input: string | Uint8Array, options?: XmlPullParserOptions): XmlElement

Parses XML into a node tree. Built on top of {@link XmlPullParser} so there is exactly one lexer in the codebase. The tree form is the right tool for the small configuration parts of an OOXML package — `styles.xml`, `numbering.xml`, `theme1.xml`, `.rels` — which are read in full, revisited repeatedly, and small enough that the convenience of random access outweighs the allocation cost. For `document.xml`, worksheets and slides, use the pull parser directly: those parts are read once, sequentially, and can be several tens of megabytes. The options are the pull parser's own — in practice the namespace canonicalisation rule, which a tree parse needs for exactly the reason a streaming one does. Without it a Strict package reads into a tree whose every element is in a namespace no constant names, and every lookup against it silently returns nothing.

patternsFor
function patternsFor(tag: string): Promise<{ default: HyphenationData; }> | undefined

The patterns of a language tag, or nothing where none are written down.

percent
function percent(value: number): Length
pixelsToPoints
function pixelsToPoints(pixels: number): number
points
function points(value: number): Length
pointsToPixels
function pointsToPixels(points: number): number
presentationFormsOf
function presentationFormsOf(text: string, covers?: (code: number) => boolean): string

The string with every letter that has a presentation form replaced by it, and lam–alef by its ligature; everything else as it was. `covers` is asked of each replacement, and a face that has not got one keeps the letter as written — a measurement in the wrong shape is still nearer than one of a missing glyph.

probe
function probe(source: ByteSource): Promise<ProbeResult>
pt
function pt(value: number): string

Formats a value as a CSS point string.

px
function px(value: number): string

Formats a value as a CSS pixel string.

rc4
function rc4(key: Uint8Array, data: Uint8Array): Uint8Array

One buffer through RC4 with a fresh key schedule.

relativeLuminance
function relativeLuminance(color: Rgba): number

WCAG 2.1 relative luminance, 0..1.

rgbToHsl
function rgbToHsl(color: Rgba): Hsl
setLicenseKey
function setLicenseKey(key: string | null | undefined): void

Records the licence key for this process or page. Call it once, anywhere before the first viewer is created — the key is not a secret and belongs in your source, committed, alongside the rest of your bootstrap. Calling it again replaces the previous value; calling it with an empty string, `null` or `undefined` clears it. Nothing observable happens as a result. The library renders identically with a key, without one, and with a key that is complete nonsense.

sha256
function sha256(message: Uint8Array): Uint8Array
sha384
function sha384(message: Uint8Array): Uint8Array
sha512
function sha512(message: Uint8Array): Uint8Array
substitutionsOf
function substitutionsOf(gsub: Uint8Array | undefined): Substitutions | undefined

Reads the substitutions of the joining features out of a font's `GSUB`. Nothing is returned for a font without the table, which is most Latin ones and every bitmap font; an empty map for a font that has it and states none of these features. Both mean the same to a caller — measure what `cmap` reaches — and are distinguished only so that "asked and there is none" can be cached.

textContent
function textContent(node: XmlNode): string

All text in the subtree, concatenated in document order.

throwIfAborted
function throwIfAborted(signal: AbortSignal | undefined, what?: string): void

Throws {@link CancelledError} if the signal has already been aborted.

toByteSource
function toByteSource(input: ByteSourceInput): Promise<ByteSource>

Normalises any supported input into a {@link ByteSource}. Strings are URLs.

toCssColor
function toCssColor(color: Rgba | undefined): string | undefined
trimNulls
function trimNulls(text: string): string

Strips the trailing NUL padding of a fixed-length string field.

walk
function walk(element: XmlElement): Generator<XmlElement>

Depth-first traversal of the subtree, including the element itself.

Interfaces

AdvanceCuts
interface AdvanceCuts

The cuts of a family: regular, bold, italic, bold italic. A cut is absent when it resolved to the same file as one already written — a family filing one file for all four asks the browser to synthesise the other three, and synthesis does not change an advance.

r?
AdvanceRanges | undefined
b?
AdvanceRanges | undefined
i?
AdvanceRanges | undefined
z?
AdvanceRanges | undefined
AdvanceRanges
interface AdvanceRanges

One cut of a family, encoded. `em` is the design grid the rest is stated in; `a`, `d` and `g` are the ascent, the descent (positive) and the line gap as fractions of it; `n` is the advance of a code point the face does not cover, in font units. The advances themselves are ranges, encoded the way `line-break-classes.ts` encodes Unicode's: `s` holds the first code point of each range as a base-36 delta from the range before it, `l` how many code points the range covers, `v` the advance in font units — all three in the same order. The length is not redundant with the next range's start, and the first version of this file left it out and was wrong. A range ends where its run of *covered* code points ends, which is not where the next one begins: the gap between them is code points the face has no glyph for at all. Without `l` every gap read as "covered, at the width of the character before it", and a face asked for a character it does not have would answer with a width instead of admitting it cannot draw it — which is the signal Word uses to substitute.

em
number
a
number
d
number
g
number
n
number
m?
Readonly<Record<string, number>> | undefined
The face's `MATH` table, divided by the em, where it carries one. Nearly no face does. One that does places an equation by its own numbers, and `layout/math-box.ts` falls back to the classical set for the rest.
s
string
l
string
v
string
AdvanceTable
interface AdvanceTable

How wide each glyph of a font is, in units of the em. The whole of a string's width, for a face that is neither kerned nor ligated — which is Word's default, and which the renderer's own stylesheet already asks the browser for with `font-variant-ligatures: none`. Word kerns only above the point size `w:kern` names, and the corpus almost never names one; `w:ligatures` is rarer still. So a run's width is the sum of its characters' advances, and the advances can be read once and reused. That is what makes computing a layout cheaper than measuring one. `canvas.measureText` costs a call per distinct string; this costs a table per font and an addition per character, and it answers on a machine with no browser and no such font installed. ## What it is not Not shaping. Arabic, the Indic scripts, Thai and Hebrew reorder and substitute glyphs before any width is decided, and a sum of per-character advances is simply wrong for them. A caller that may meet such text has to ask something that shapes; this is for the scripts where a character is a glyph.

advanceOf
(glyph: number) => number
Advance of a glyph index, in em units; missing glyphs take `.notdef`.
covers
(code: number) => boolean
Whether the font has a glyph for a code point at all. A face that has not is drawn by Word in a substitute, and measured here in `.notdef` — half an em in Calibri, which is how `zh-98a5dfd0b39a` came to hold twice the text a line of it should. See where the walk asks it.
advanceOfCharacter
(code: number) => number
Advance of a Unicode code point, in em units, through the font's `cmap`.
widthOf
(text: string, size: number) => number
The width of a string at a given type size, in the same units as the size.
kernedWidthOf
(text: string, size: number) => number
The same width with the font's own kerning pairs applied. Word kerns above the size `w:kern` names and 369 of the corpus's 1672 documents name one, so this is not an ornament: unkerned text is wider than Word's, and a line this engine cannot fit is one Word never had to squeeze. Returns the unkerned width where the face ships no `kern` table.
kernBetween
(left: number, right: number) => number
What the font's `kern` table puts between two characters, in em units.
ByteSource
interface ByteSource

A random-access source of bytes. This is the central abstraction of the project: parsers never touch `File`, `Blob` or the network directly. That makes it possible to read a ZIP central directory at the end of a file, or a PDF xref table, without pulling the whole document into memory, and to run the same parser in a browser, in Node, or on top of HTTP range requests.

byteLength
number
Total size of the source in bytes.
name?
string | undefined
File name when known. Used as a hint for format detection.
mimeType?
string | undefined
MIME type when the source reports one.
slice
(start: number, end?: number) => Promise<Uint8Array>
Reads the `[start, end)` range. Implementations must return exactly the requested number of bytes or throw {@link OutOfBoundsError}. Short reads are not allowed, otherwise every parser would have to re-check the length after each call.
dispose?
(() => void) | undefined
Releases retained resources such as caches or network connections.
DetectionResult
interface DetectionResult

Result of format detection.

format
FormatId
container
ContainerKind
confidence
"certain" | "probable" | "guess"
How much the detector can be trusted. `certain` — an unambiguous signature matched; `probable` — the container was identified and the concrete format was inferred from the extension or MIME type and still needs confirmation from the contents; `guess` — no signature matched and the decision rests on the file name alone.
reason
string
What led to the decision — useful when debugging third-party files.
DocumentMetadata
interface DocumentMetadata

Metadata common to every format.

title?
string | undefined
author?
string | undefined
subject?
string | undefined
keywords?
readonly string[] | undefined
description?
string | undefined
producer?
string | undefined
The application that produced the file.
createdAt?
Date | undefined
modifiedAt?
Date | undefined
language?
string | undefined
Language of the main content, BCP 47.
custom?
Readonly<Record<string, string | number | boolean | Date>> | undefined
Format-specific fields that do not fit the common schema.
FaceAdvances
interface FaceAdvances

What a decoded table can answer. Deliberately the same shape as the useful half of `AdvanceTable` in `font-file.ts`: whoever consumes widths should not have to know which of the two sources answered, and the two are interchangeable for everything except the kerned width.

ascent
number
Ascent above the baseline, as a fraction of the em.
descent
number
Descent below it, positive, as a fraction of the em.
lineGap
number
The gap the face asks for above the ascent, as a fraction of the em.
covers
(code: number) => boolean
Whether the face has a glyph for a code point at all.
advanceOfCharacter
(code: number) => number
The advance of a code point, in em units; `.notdef` where uncovered.
widthOf
(text: string, size: number) => number
The width of a string at a type size, in the same units as the size.
math?
Readonly<Record<string, number>> | undefined
What the face states about setting mathematics, divided by its em. Absent for nearly every face, which is why `layout/math-box.ts` carries the classical numbers: an equation set in Times is still an equation.
FontMetrics
interface FontMetrics

Type metrics for the families a document is most likely to ask for and a browser least likely to have, as fractions of the em. Generated by `node tools/fonts/generate.mjs` from the fonts themselves — `head` for the design grid, `OS/2`/`hhea` for the vertical extents, and the average advance over a sample of English text. Do not edit by hand. What they are for: a viewer cannot draw a font it has not got, and the substitute the browser picks is not the one the authoring application picked. A third of the presentation corpus's line mass is set in a family the browser cannot resolve — Aptos above all, because Office keeps its cloud fonts in a private directory it never registers — and where a font is substituted the lines wrap **later** than PowerPoint's on 472 slides against 280. The stand-in is narrower, so a line that should have broken carries one more word, and from there neither side has the same lines at all. With these an `@font-face` can make a font the machine *does* have take the missing one's measurements: `size-adjust` for the width of the letters, `ascent-override` and `descent-override` for the height of the line.

ascent
number
Ascent above the baseline, as a fraction of the em.
descent
number
Descent below it, positive.
lineGap
number
boldAverageWidth?
number | undefined
Mean advance of the family's **bold** cut, where the machine had one. A stand-in built from the regular’s widths and then synthesised bold by the browser is four per cent wide of the real cut — Roboto Bold draws a line 1117.8 px wide where a synthesised Arial with Roboto’s regular width draws it 1176.8. Bold words are titles, so the error is where it shows.
averageWidth
number
Mean advance over a sample of English text, as a fraction of the em.
FormatDescriptor
interface FormatDescriptor

Format description: its name, how to open it, how to recognise it.

id
FormatId
label
string
Human-readable name for the UI.
kind
DocumentKind
container
ContainerKind
extensions
readonly string[]
Extensions without the leading dot, lower-case.
mimeTypes
readonly string[]
FormatModule
interface FormatModule<TDocument extends GenomDocument = GenomDocument>

A format: everything one file type needs, in one value. `@genomdev/docx` exports one of these for Word, covering both generations. Nothing else has to know that `.doc` exists.

id
string
Stable identifier, used for options and events: `docx`, `pdf`.
formats
readonly FormatId[]
Every format id this module opens.
detect?
readonly DetectRule[] | undefined
How to recognise the format from the file.
open
(source: ByteSource, options?: OpenOptions) => Promise<TDocument>
canOpen?
((source: ByteSource, detection: DetectionResult) => Promise<boolean>) | undefined
Confirms that the source really is this format. Only needed where a rule cannot decide — an OLE2 file is a `.doc`, an `.xls` or a `.ppt`, and telling them apart means reading the directory.
walk?
((document: TDocument, hash: string, options: ResolvedOptions) => Promise<WalkResult>) | undefined
Turns the parsed model into the content tree. Declared as a method rather than as a property on purpose: TypeScript checks method parameters bivariantly, and without that a `FormatModule<DocxDocument>` could not be put in a list beside a `FormatModule<PdfDocument>` — which is the only thing a registry ever does with them.
GenomDocument
interface GenomDocument

An opened document: the contract shared by every parser. Deliberately narrow — it only carries what is meaningful for any format. Everything else (docx sections, xlsx sheets, pptx slides) lives in subtypes inside the format packages. The viewer works against this interface so that it can still show a title and a page count for a format whose renderer is not registered.

format
FormatId
kind
DocumentKind
metadata
DocumentMetadata
pageCount?
number | undefined
Number of pages/sheets/slides, when the format exposes it cheaply. For docx this is `undefined` until the document has been laid out: splitting a text flow into pages depends on fonts, hyphenation and the printable area.
extractText
() => Promise<string>
Extracts the whole document text for search, indexing and previews. A dedicated method because this is the one operation every format needs in the same way and which requires no rendering.
dispose
() => void
Releases retained resources. Calling it twice is safe.
Hsl
interface Hsl
h
number
0..1
s
number
0..1
l
number
0..1
Hyphenation
interface Hyphenation

What the engine holds: the languages it may hyphenate, and the answer.

pointsOf
(word: string, language: string | undefined) => readonly number[]
Where a line may end inside this word; empty for a language nobody wrote down.
any
boolean
Whether any language of this document can be hyphenated at all.
HyphenationData
interface HyphenationData

A language's hyphenation patterns, as the generated modules write them. The strings are the pattern file's own words, space separated: a pattern is letters with digits between them (`a1bc2d`), and an exception is a word with hyphens at the points it may break (`as-so-ciate`). Kept as text rather than as a parsed structure because that is what a module can hold without paying for it at load time — see `compileHyphenation`, which parses once, lazily.

tag
string
The pattern file's own name: `de-1996`, `en-gb`, `ru`.
left
number
`\lefthyphenmin`: letters a break may not cut off the head of a word.
right
number
`\righthyphenmin`: the same at its tail.
patterns
string
exceptions
string
HyphenationTable
interface HyphenationTable

The same, parsed, which is what the algorithm walks.

tag
string
left
number
right
number
patterns
ReadonlyMap<string, readonly number[]>
Pattern letters to the values between them, `.` marking a word edge.
exceptions
ReadonlyMap<string, readonly number[]>
The words the language spells out, lower-cased, to their break points.
KernTable
interface KernTable

Kerning pairs, in units of the em.

between
(left: number, right: number) => number
What to add between two glyphs, in em units; nought where there is no pair.
size
number
How many pairs the font declares, for a caller deciding whether to bother.
pairs
() => Iterable<readonly [number, number, number]>
The pairs themselves: left glyph, right glyph, and the em units between. Exposed because a caller that wants to *write the table down* — so that a browser with no font file can still kern — has no other way to reach it. Asking `between` for every pair of glyphs is thirteen million calls on an ordinary face, which is not a reading of a table but a search of one.
LazyFormat
interface LazyFormat

A format that has not been loaded yet. The descriptor carries the recognition rules, so the registry can decide whether this is the module it needs before paying for it. `load` resolves to the module itself, which in a bundler is a chunk of its own.

id
string
formats
readonly FormatId[]
detect?
readonly DetectRule[] | undefined
load
() => Promise<FormatModule>
Length
interface Length

A length as stored in the file, together with the unit it was stored in. Keeping the unit lets the renderer decide how to emit it: some measurements are better expressed in `pt` so the browser can round them itself, others must be pixels because they take part in layout arithmetic.

value
number
unit
"auto" | "pt" | "px" | "percent"
LineBreakOptions
interface LineBreakOptions
eastAsianContext?
boolean | undefined
Whether class `AI` resolves to `ID` rather than `AL`. The choice UAX #14 hands to the caller: an ambiguous-width character is ideographic in East Asian text and alphabetic everywhere else. Word makes the same choice by the run's `w:lang/@w:eastAsia`.
conditionalJapaneseStarter?
"strict" | "loose" | undefined
How class `CJ` — the small kana and the prolonged sound mark — resolves. `strict` is the standard's own default (`NS`, so they may not begin a line); `loose` lets them begin one, which is what CSS calls `line-break: loose` and what a narrow column wants.
quotationRules?
boolean | undefined
Whether a quotation mark holds to the character beside it: LB19 and LB19a. The pair that binds a quote to its immediate neighbours — LB19 by which end of a quotation it is, LB19a on both sides unless East Asian text surrounds it. UAX #14 §8 lists them among the tailorable rules; the corpus keeps them, at 46 048 lines against 45 481.
spacedQuotationRules?
boolean | undefined
Whether a quotation mark holds across the spaces beside it: LB15a, LB15b. The newest and most opinionated part of the algorithm: an opening quote may not end a line *even after spaces*, and a closing one may not begin one. A word processor that predates the rules does not follow them, and the corpus says Word is such a processor — see the note in `docx/src/layout/line-break.ts`.
LocatorParts
interface LocatorParts
version
number
hash
string
Eight hex digits of the document hash, or `''` when unbound.
flow
LocatorFlow
steps
readonly LocatorStep[]
offset
number | undefined
Offset into the text of the addressed node, in UTF-16 code units. UTF-16 rather than code points because the other end of every offset in this system is a DOM `Range`, which counts UTF-16 code units, and a conversion at the boundary is a conversion that can be forgotten.
cell
string | undefined
A cell reference, for the one format that has a native address. `Sheet!C14` is the address an Excel user already knows, already types into a formula and already sees in the name box. Encoding it as `c3/w14` would be a private language for a public fact.
LocatorSelector
interface LocatorSelector

Exact: a range between two locators. Valid while the file's bytes are.

type
"GenomLocator"
start
string
end
string
LocatorStep
interface LocatorStep

One step down the tree. `kind` is a single letter so the whole path stays short — a locator is stored per chunk, and a corpus of a million chunks pays for every character. The letters are mnemonic rather than clever: `b` block, `t` table, `w` row (`r` was taken), `c` cell, `p` paragraph, `r` run, `s` shape, `i` inline object.

kind
LocatorStepKind
index
number
MathConstants
interface MathConstants

The constants, in font units. Named as the specification names them, because every one of them is quoted by that name in the rules that use it and a friendlier name would only need translating back. Divide by the em to use them.

scriptPercentScaleDown
number
Per cent, not em units: how much smaller a script is than its base.
scriptScriptPercentScaleDown
number
The same for a script of a script.
axisHeight
number
Where the bar of a fraction sits, above the baseline.
mathLeading
number
Extra leading between the lines of a stack.
subscriptShiftDown
number
subscriptTopMax
number
subscriptBaselineDropMin
number
superscriptShiftUp
number
superscriptShiftUpCramped
number
superscriptBottomMin
number
superscriptBaselineDropMax
number
subSuperscriptGapMin
number
superscriptBottomMaxWithSubscript
number
spaceAfterScript
number
upperLimitGapMin
number
upperLimitBaselineRiseMin
number
lowerLimitGapMin
number
lowerLimitBaselineDropMin
number
stackTopShiftUp
number
stackTopDisplayStyleShiftUp
number
stackBottomShiftDown
number
stackBottomDisplayStyleShiftDown
number
stackGapMin
number
stackDisplayStyleGapMin
number
fractionNumeratorShiftUp
number
fractionNumeratorDisplayStyleShiftUp
number
fractionDenominatorShiftDown
number
fractionDenominatorDisplayStyleShiftDown
number
fractionNumeratorGapMin
number
fractionNumDisplayStyleGapMin
number
fractionRuleThickness
number
fractionDenominatorGapMin
number
fractionDenomDisplayStyleGapMin
number
skewedFractionHorizontalGap
number
skewedFractionVerticalGap
number
overbarVerticalGap
number
overbarRuleThickness
number
overbarExtraAscender
number
underbarVerticalGap
number
underbarRuleThickness
number
underbarExtraDescender
number
radicalVerticalGap
number
radicalDisplayStyleVerticalGap
number
radicalRuleThickness
number
radicalExtraAscender
number
radicalKernBeforeDegree
number
radicalKernAfterDegree
number
radicalDegreeBottomRaisePercent
number
Per cent: how far up the radical the degree sits.
ModelRange
interface ModelRange

A resolved range in the document model.

start
string
end
string
NormalizedText
interface NormalizedText

Text prepared for matching, with a way back to the original offsets. Quote matching has to ignore differences no reader would call a difference: a non-breaking space against a space, a soft hyphen left over from justification, the zero-width joiners a copy-paste through a word processor leaves behind, and the two spellings Unicode allows for any accented letter. Normalising all of that changes the offsets, so the map back is built at the same time — without it a match in normalised space cannot be turned into a range in the document.

text
string
sourceOffsets
Int32Array<ArrayBufferLike>
For each character of `text`, its offset in the source string.
sourceLength
number
OpenOptions
interface OpenOptions

Options shared by every parser.

signal?
AbortSignal | undefined
Aborts parsing of large files.
tolerant?
boolean | undefined
Keep parsing when the file locally violates the specification. Defaults to `true`: real files produced by office suites break the standard routinely, and failing the whole document where a single paragraph could be dropped is a bad trade.
password?
string | undefined
Password for encrypted documents.
onProgress?
((fraction: number) => void) | undefined
Progress callback, 0..1.
ProbeResult
interface ProbeResult
detection
DetectionResult
contentType?
string | undefined
Content type of the main part, when the source is an OPC package.
entries?
readonly string[] | undefined
Entry names of the archive, when the source is a ZIP.
head
Uint8Array<ArrayBufferLike>
First bytes, for magic rules.
name?
string | undefined
mimeType?
string | undefined
RawZipEntry
interface RawZipEntry

An entry copied from another archive, exactly as that archive stored it. Every header field the source stated is carried, not only the content. The flags, the two version words, the DOS timestamp and the extra field mean nothing to a reader of an OOXML package — and they are bytes of the local header, so an entry rewritten without them is an entry that differs from the one it was copied from. "Open it and save it gives you your file back" is a claim about bytes, and it is only true if the copy is one.

name
string
compressed
Uint8Array<ArrayBufferLike>
The stored bytes: deflated where {@link method} says so, never touched.
method
number
crc32
number
uncompressedSize
number
lastModified?
Date | undefined
extra?
Uint8Array<ArrayBufferLike> | undefined
localExtra?
Uint8Array<ArrayBufferLike> | undefined
The local header's own extra field, where it differs from the central one.
externalAttributes?
number | undefined
internalAttributes?
number | undefined
`internal file attributes`; see the reader's note on it.
flags?
number | undefined
versionMadeBy?
number | undefined
versionNeeded?
number | undefined
dosTime?
number | undefined
The timestamp as the two DOS words, which beats {@link lastModified}.
dosDate?
number | undefined
dataDescriptor?
Uint8Array<ArrayBufferLike> | undefined
The data descriptor that followed the entry, where it had one. Written after the content, exactly as it stood, and the local header then states zeros for the checksum and the two sizes — which is what general purpose bit 3 means and what the source file did. See `reader.ts`.
zip64?
{ readonly compressedSize: boolean; readonly uncompressedSize: boolean; readonly localHeaderOffset: number | undefined; } | undefined
Which central-directory fields the entry wrote as the ZIP64 sentinel. `0xffffffff` in the field and the real number in the extra block beside it. The extra block is carried through untouched like every other, so writing the sentinel back reproduces the archive exactly; writing the resolved number instead produces one that says the same thing in a different form, and is therefore a different file. See the reader's note.
local?
{ readonly versionNeeded: number; readonly flags: number; readonly method: number; readonly dosTime: number; readonly dosDate: number; readonly crc32: number; readonly compressedSize: number; readonly uncompressedSize: number; } | undefined
The local header's own fields, where they differ from the directory's. See the reader's note: the two are supposed to agree and in real files they do not, so a copy reproduces each where it stood.
ResolvedFormat
interface ResolvedFormat

What resolving a file produced, and how sure the registry is.

module
FormatModule<GenomDocument>
probe
ProbeResult
ResolvedRange
interface ResolvedRange

A resolution, and how much to trust it. `resolvedBy` is not decoration. An application that watches it can see its documents drifting — the day the exact selectors stop matching and everything falls through to quotes is the day somebody started editing the corpus — and it can see that before a user reports a highlight in the wrong place.

range
ModelRange
resolvedBy
"locator" | "position" | "quote" | "native"
confidence
number
1 for an exact match, lower for a fuzzy one.
Rgba
interface Rgba

Colour arithmetic, in the form every format needs it. A colour as a number, as CSS, as HSL, and as the luminance that decides whether text on it should be black or white. Nothing here belongs to a particular format — the transforms DrawingML defines on top of this (`tint`, `shade`, `lumMod`) live in `@genomdev/office-core`, because they are elements of a schema rather than facts about colour.

r
number
0..255
g
number
0..255
b
number
0..255
a
number
0..1
SheetSelector
interface SheetSelector

Native: the address an Excel user already knows.

type
"Sheet"
sheet
string
ref
string
`C14` or `C14:E20`.
SlideSelector
interface SlideSelector

Native: a slide, and optionally one shape on it.

type
"Slide"
index
number
Zero-based.
shape?
number | undefined
Substitution
interface Substitution

The substitutions one feature asks for, by the glyph they replace.

single
Map<number, number>
Glyph → glyph, from the single-substitution lookups.
ligatures
Map<number, { rest: number[]; glyph: number; }[]>
Glyph → the ligatures beginning with it. `rest` is the components after the first, so a two-glyph ligature has one. Longest first, because a font may hold both lam-alef and lam-lam-alef and the longer is the one that wins.
TextPositionSelector
interface TextPositionSelector

Portable: offsets into extracted text. For everyone who chunked the text with something else. LangChain and its cousins keep a start and an end and nothing else, and this is the selector that lets those chunks come back. `profile` is a hash of the extraction options that produced the offsets. Markdown with GFM tables and markdown with HTML tables are different strings, and without the profile the difference would show up as a highlight that is forty characters off rather than as an error.

type
"TextPosition"
start
number
end
number
profile?
string | undefined
TextQuoteSelector
interface TextQuoteSelector

Robust: the text with enough of its neighbours to be unambiguous. `prefix` and `suffix` are what separate the fourteenth "Total" in a workbook from the fifteenth. Thirty-two characters each is the figure the annotation community converged on: enough to disambiguate ordinary prose, short enough that storing it per chunk is free next to the chunk itself.

type
"TextQuote"
exact
string
prefix?
string | undefined
suffix?
string | undefined
TrueTypeTables
interface TrueTypeTables

One font program, read. Everything a caller can learn about a font without rasterising it: how many glyphs it has, how big its em is, which character reaches which glyph, what the glyphs are called — and the raw bytes of every table, so that a question this does not answer can still be asked of the file rather than of a second copy of it.

numGlyphs
number
How many glyphs the font holds, from `maxp`; the bound on every index.
unitsPerEm
number
The em, in the font's own units, from `head`. Everything else the file states about size — an advance, an ascent, a bearing — is in these units, so nothing here means anything until it is divided by this. Two thousand and forty-eight for most TrueType faces and a thousand for most CFF ones; a reader that assumes either is wrong about half the world's fonts.
cmap
Map<number, number> | undefined
Character code (in the font's own cmap) → glyph index.
cmapKind
"symbol" | "unicode" | "mac" | undefined
Which cmap subtable was taken, because the answer changes the meaning.
unicodeOfGlyph
Map<number, number> | undefined
Glyph index → Unicode, from the Unicode subtable when there is one.
glyphNames
string[] | undefined
Glyph names from the `post` table, when it carries them.
hasCff
boolean
True when the file is really a CFF wrapped in an OpenType shell.
cff
Uint8Array<ArrayBufferLike> | undefined
The CFF table's bytes, for the wrapped case.
tables
Map<string, Uint8Array<ArrayBufferLike>>
Every table, by tag. Kept because rebuilding the font for a browser means keeping most of them and replacing one — the character map — and a rebuild that had to re-parse the directory would be a second reading of the same bytes.
XmlAttribute
interface XmlAttribute

An XML attribute with its namespace resolved.

name
string
Local name without the prefix.
namespace
string
Namespace URI; an empty string means the attribute has no namespace.
value
string
XmlElement
interface XmlElement
type
"element"
name
string
Local name without the prefix, e.g. `p` for `<w:p>`.
namespace
string
Resolved namespace URI.
attributes
readonly XmlAttribute[]
children
readonly XmlNode[]
XmlPullParserOptions
interface XmlPullParserOptions

Kind of the event the pull parser is currently positioned on. Numeric rather than string-valued, so the hot dispatch loop in the document parser compares integers. A plain enum rather than a `const enum` because the values cross package boundaries, which ambient const enums cannot do under `verbatimModuleSyntax`.

canonicalizeNamespace?
((uri: string) => string) | undefined
Приводит URI пространства имён к каноническому виду перед сравнением. По умолчанию URI остаётся собой. OOXML передаёт сюда правило, переводящее пространства класса Strict в Transitional.
XmlText
interface XmlText
type
"text"
text
string
XmlWriterOptions
interface XmlWriterOptions

XML, written. The counterpart of `pull.ts`, and deliberately as small. It knows nothing about namespaces beyond the fact that a name may carry a prefix: OOXML declares its prefixes once on the root element and uses them everywhere below, so a writer that resolved namespaces per element would be solving a problem the format does not have — and would break the one thing that makes lossless saving possible, which is splicing a run of the *original* characters back into the output. Those characters carry the document's own prefixes. Declare the document's own prefixes on the root and they are valid wherever they land; invent new ones and every preserved fragment is broken. Two properties everything else rests on: - **It appends and never revisits.** The output is a list of strings joined once at the end, so writing a forty-megabyte part is linear rather than quadratic. - **It writes no whitespace of its own.** Word writes `document.xml` as one line, and so does this. Indentation inside `w:t` is content — a space between two runs is a space in the document — and a pretty-printer that cannot tell the difference welds words together or pulls them apart.

declaration?
boolean | undefined
Write the XML declaration Word writes; on by default.
standalone?
boolean | undefined
`standalone="yes"`, which every OOXML part carries.
prolog?
string | undefined
The prolog to write instead of the standard declaration. What a part rewritten from a file it was read from puts back: producers differ in the declaration they write — single quotes rather than double, a bare newline rather than a carriage return and one — and none of it means anything, which is exactly why a rewrite should not change it. A part that comes back differing only in its punctuation is a part whose diff cannot be read.
ZipArchiveOptions
interface ZipArchiveOptions

Options controlling how the archive caches inflated entries.

maxCacheBytes?
number | undefined
Maximum total size of cached inflated entries, in bytes. Caching matters because OOXML parts are read repeatedly (styles.xml is needed both when parsing the document and when rendering it), but an unbounded cache would hold a fully inflated 500 MB document in memory. Defaults to 64 MB, past which the least recently used entries are dropped.
ZipEntry
interface ZipEntry

One entry of the ZIP central directory.

name
string
Path inside the archive, always with forward slashes.
compressedSize
number
uncompressedSize
number
compressionMethod
number
crc32
number
localHeaderOffset
number
Offset of the local file header from the start of the file.
isDirectory
boolean
lastModified
Date | undefined
flags
number
The general-purpose bit flags, and the two versions, as written. Kept so that an entry copied into another archive can be copied *exactly*. They mean almost nothing to a reader — bit 11 says the name is UTF-8 and the rest are about encryption and streaming, neither of which occurs in an OOXML package — and they are two bytes of the local header, so an entry rewritten with different flags is an entry that differs from the one it was copied from. Which defeats the point of copying it.
versionMadeBy
number
versionNeeded
number
externalAttributes
number
`external file attributes`, which on a package written by Word are zero.
internalAttributes
number
`internal file attributes`: one bit, meaning "this entry is text". Nothing reads it and every producer sets it differently — LibreOffice marks its XML parts, Word marks nothing. It is two bytes of the central directory, so a copy that did not carry it would differ from the file it copied, which is the only reason it is here.
extra
Uint8Array<ArrayBufferLike>
The entry's extra field, carried through a copy like everything else.
dosTime
number
The two DOS words the timestamp is written in, kept exactly.
dosDate
number
zip64?
{ readonly compressedSize: boolean; readonly uncompressedSize: boolean; readonly localHeaderOffset: number | undefined; } | undefined
Which central-directory fields the entry wrote as the ZIP64 sentinel. A ZIP64 archive puts `0xffffffff` in the field and the real number in the extra block beside it. The reader resolves the two into one answer, which is what every caller wants — and a *copy* that writes the resolved number back inline is a different archive from the one it copied, even though both say the same thing and both open. `tdf82984_zip64XLSXImport` is 4.7 KB and uses the form for every one of its eight entries. Absent for the ordinary case, which is nearly every entry ever written.
ZipWriteOptions
interface ZipWriteOptions
compress?
boolean | undefined
Deflate the content; stored if the runtime cannot, or if this is false.
lastModified?
Date | undefined
extra?
Uint8Array<ArrayBufferLike> | undefined
The entry's extra field, kept where one is being carried over.
externalAttributes?
number | undefined
`external file attributes`, kept where one is being carried over.

Type aliases

ByteSourceInput
type ByteSourceInput = ByteSource | Blob | ArrayBuffer | Uint8Array | string

Everything Genom can turn into a {@link ByteSource}.

ContainerKind
type ContainerKind = 'zip' | 'ole2' | 'pdf' | 'plain-text' | 'binary-image' | 'unknown'

Container kind: what can be determined from the first bytes of a file. This intermediate layer exists because many formats share one signature: docx, xlsx, pptx, odt and epub all start with `PK\x03\x04`. The core detector identifies the container, and the package that knows how to read that container narrows it down to a specific format.

DetectRule
type DetectRule = | { readonly magic: readonly (number | null)[]; readonly offset?: number } /** The content type of the main part of an OPC package, read from the ZIP. */ | { readonly container: 'zip'; readonly contentType: string } /** A ZIP entry that must be present, for containers with no content types. */ | { readonly container: 'zip'; readonly entry: string } /** The container alone: a candidate, to be confirmed by `canOpen`. */ | { readonly container: ContainerKind } /** File extension, without the dot. The weakest evidence there is. */ | { readonly extension: readonly string[] } | { readonly mimeType: readonly string[] }

How to recognise a format without running its code. Rules are tried in order and the first match wins. A rule that names bytes or a content type answers outright; a rule that names only a container narrows the field and leaves the decision to `canOpen`.

DocumentKind
type DocumentKind = | 'text-document' /** A grid of cells: xlsx, ods, csv. */ | 'spreadsheet' /** A sequence of slides: pptx, odp. */ | 'presentation' /** Fixed page layout: pdf. */ | 'paged-document' /** A raster or vector image. */ | 'image'

Broad document category; determines which viewer applies.

FormatEntry
type FormatEntry = FormatModule | LazyFormat

Either an already-loaded format or a promise of one.

FormatId
type FormatId = | 'docx' | 'xlsx' | 'pptx' // Legacy Microsoft Office (OLE2/CFB) — planned | 'doc' | 'xls' | 'ppt' // Fixed-layout documents — planned | 'pdf' // OpenDocument — planned | 'odt' | 'ods' | 'odp' // Plain formats — planned | 'txt' | 'md' | 'csv' | 'json' | 'xml' | 'html' // Images — planned | 'png' | 'jpeg' | 'gif' | 'webp' | 'bmp' | 'tiff' | 'svg' // Internal | 'unknown'

Identifier of a concrete file format. A string literal union rather than an enum: the values are part of the public API, get serialised to JSON, and are used as registry keys.

JoiningForm
type JoiningForm = 'isol' | 'init' | 'medi' | 'fina'

The shape a letter takes, named as OpenType names its features.

JoiningType
type JoiningType = 'D' | 'R' | 'T' | 'C' | 'U'

What a character does to the letters beside it.

LineBreakClass
type LineBreakClass = (typeof CLASSES)[number]
Locator
type Locator = string

The opaque wire form. Parse it with {@link parseLocator}.

LocatorFlow
type LocatorFlow = | { readonly kind: 'body' } /** A header or footer part, named by the relationship that reaches it. */ | { readonly kind: 'header' | 'footer'; readonly id: string } /** A note, comment or thread, named by its own id in the part. */ | { readonly kind: 'footnote' | 'endnote' | 'comment'; readonly id: string } /** A slide, its speaker notes, or a worksheet. */ | { readonly kind: 'slide' | 'notes'; readonly index: number } | { readonly kind: 'sheet'; readonly name: string } /** * A page of a fixed-layout document. * * Its own flow rather than a step inside the body, and the distinction is * the whole difference between PDF and everything else here: a page of a * `.docx` is a *result* — it exists once the document has been laid out and * moves when the margins change — while a page of a PDF is where the content * lives. There is no body flow to be a step of. */ | { readonly kind: 'page'; readonly index: number }

The independent content streams of a document. A document is not one sequence of blocks. Headers, footers, footnotes, endnotes, comments and speaker notes are separate flows that interleave with the body only once it is laid out, and addressing them as if they were part of the body would make every address after the first footnote wrong.

MatchStrength
type MatchStrength = 'certain' | 'probable' | 'candidate' | 'none'

How well a rule matched: enough to decide, or only enough to shortlist.

Selector
type Selector = LocatorSelector | TextPositionSelector | TextQuoteSelector | SheetSelector | SlideSelector
Substitutions
type Substitutions = Map<string, Substitution>

Every substitution a font states, by the feature that asks for it.

XmlNode
type XmlNode = XmlElement | XmlText

Values

AUTO_LENGTH
AUTO_LENGTH: Length
BLACK
BLACK: Rgba
BREAK_MANDATORY
BREAK_MANDATORY: 2

A line **must** end here: a hard break, or the end of the text.

BREAK_OPPORTUNITY
BREAK_OPPORTUNITY: 1

A line may end here.

BREAK_PROHIBITED
BREAK_PROHIBITED: 0

No break may be taken here.

FAMILIES
FAMILIES: readonly string[]

Every family a table is written for, in the case the file names them.

FONT_METRICS
FONT_METRICS: Readonly<Record<string, FontMetrics>>
FORMATS
FORMATS: readonly FormatDescriptor[]

Catalogue of known formats. It also lists formats that have no parser yet: the catalogue answers "what is this file", not "can we open it". That lets the viewer say "this is a PowerPoint 97-2003 presentation, support is planned" instead of a bare "unknown format".

isEastAsianWidth
function isEastAsianWidth(point: number): boolean

Whether a code point is East Asian by width: `ea ∈ {F, W, H}`.

isFinalPunctuation
function isFinalPunctuation(point: number): boolean

Whether a code point is final punctuation, `\p{Pf}`.

isInitialPunctuation
function isInitialPunctuation(point: number): boolean

Whether a code point is initial punctuation, `\p{Pi}`.

isPictographic
function isPictographic(point: number): boolean

Whether a code point is `Extended_Pictographic`.

isUnassignedPictographic
function isUnassignedPictographic(point: number): boolean

Whether a code point is reserved for an emoji Unicode has not assigned.

LINE_BREAK_CLASSES
CLASSES: readonly ["XX", "AI", "AK", "AL", "AP", "AS", "B2", "BA", "BB", "BK", "CB", "CJ", "CL", "CM", "CP", "CR", "EB", "EM", "EX", "GL", "H2", "H3", "HH", "HL", "HY", "ID", "IN", "IS", "JL", "JT", "JV", "LF", "NL", "NS", "NU", "OP", "PO", "PR", "QU", "RI", "SA", "SG", "SP", "SY", "VF", "VI", "WJ", "ZW", "ZWJ"]

The Line_Break classes, in the order `VALUES` indexes them.

LOCATOR_SCHEME
LOCATOR_SCHEME: "genom"
METHOD_DEFLATE
METHOD_DEFLATE: 8
METHOD_STORED
METHOD_STORED: 0
NO_HYPHENATION
NO_HYPHENATION: Hyphenation

A hyphenation that never breaks a word, for a document that asks for none.

PX_PER_INCH
PX_PER_INCH: 96

CSS pixels per inch — 96, as is conventional on the web.

PX_PER_POINT
PX_PER_POINT: number
WHITE
WHITE: Rgba

Enums

XmlEvent
XmlEvent: typeof XmlEvent

@genomdev/core/content

Classes

ContentDocument
class ContentDocument

An extracted document: the tree, and every way of writing it down. The serialisers are methods rather than free functions for one reason: the addresses. A caller who has the tree and reaches for a `render` from somewhere else gets text with no way back into the document, which is the thing this package exists to prevent. Everything that produces text from here can also produce the map beside it.

format
FormatId
hash
string
metadata
DocumentMetadata
blocks
readonly Block[]
annotations
readonly Annotation[]
profile
string
walk
() => Generator<Block>
Every block in reading order, parents before children.
sections
readonly Block[]
The top-level sections: slides, sheets, or the parts of a document.
toPlainText
{ (options?: TextOptions & { withMap?: false; }): string; (options: TextOptions & { withMap: true; }): { text: string; map: OffsetMap; }; }
toPlainText
{ (options?: TextOptions & { withMap?: false; }): string; (options: TextOptions & { withMap: true; }): { text: string; map: OffsetMap; }; }
toPlainText
{ (options?: TextOptions & { withMap?: false; }): string; (options: TextOptions & { withMap: true; }): { text: string; map: OffsetMap; }; }
toMarkdown
{ (options?: MarkdownOptions & { withMap?: false; }): string; (options: MarkdownOptions & { withMap: true; }): { markdown: string; map: OffsetMap; }; }
toMarkdown
{ (options?: MarkdownOptions & { withMap?: false; }): string; (options: MarkdownOptions & { withMap: true; }): { markdown: string; map: OffsetMap; }; }
toMarkdown
{ (options?: MarkdownOptions & { withMap?: false; }): string; (options: MarkdownOptions & { withMap: true; }): { markdown: string; map: OffsetMap; }; }
toHtml
{ (options?: HtmlOptions & { withMap?: false; }): string; (options: HtmlOptions & { withMap: true; }): { html: string; map: OffsetMap; }; }
toHtml
{ (options?: HtmlOptions & { withMap?: false; }): string; (options: HtmlOptions & { withMap: true; }): { html: string; map: OffsetMap; }; }
toHtml
{ (options?: HtmlOptions & { withMap?: false; }): string; (options: HtmlOptions & { withMap: true; }): { html: string; map: OffsetMap; }; }
toJSON
() => ContentDocumentJson
chunks
(options?: ChunkOptions) => Chunk[]
Structure-aware chunks, each carrying the selectors to find it again.
selectorsFor
(text: string, map: OffsetMap, start: number, end: number, output?: OutputKind) => Selector[]
Selectors for a range of one of this document's text outputs. The bridge for everybody who chunked the text themselves. Give it the offsets a splitter reported, the map that produced them and which output they came from, and it gives back the anchor triple: exact, portable and robust.
OffsetMap
class OffsetMap

The finished map: a sorted list of segments and two ways to search it. Sorted and searched by bisection rather than kept in a hash: the queries are range queries, a thousand-page document produces a few hundred thousand segments, and an index that answers "which segment contains offset 41 322" has to be ordered anyway.

segments
readonly Segment[]
locate
(start: number, end: number) => LocatedRange | undefined
The address of a range of the output. A range that begins inside `syntax` is pulled forward to the first source character at or after it, and one that ends inside `syntax` is pulled back. There is nothing else honest to do: `##` is not in the document, so no address points at it, and a highlight over it would have to point somewhere else anyway. Returns `undefined` when the range holds no source characters at all — a selection of nothing but a table's rule line, say. Better than an address that is merely nearby.
offsetOf
(locator: Locator) => { start: number; end: number; } | undefined
Where a node landed in the output. The direction people forget to ask for and then need: highlighting the markdown preview for the block somebody clicked in the document, scrolling a side-by-side view, showing which part of the extraction a citation came from. Returns the span from the first character of that node to the last, including anything nested inside it.
positionOf
(locator: Locator, prefer?: "start" | "end") => number | undefined
The exact output offset an address points at. The inverse of {@link locate} for a single address rather than a range, and the piece that closes the loop: with it, a stored anchor can be turned back into the characters it was made from, which is how a round-trip test proves the whole scheme rather than merely exercising it. The viewer needs the same operation to know which text node to put a highlight in. Falls back to the start of the node when the address carries no character offset, which is what a block-level address means.
textOf
(text: string, range: { start: Locator; end: Locator; }) => string | undefined
The text a resolved range covers, given the output it was measured in.
segmentsIn
(start: number, end: number) => Generator<Segment>
Every segment overlapping a range of the output.
inDocumentOrder
readonly Segment[]
Segments in document order, for building position selectors.
OutputBuilder
class OutputBuilder

Builds output and its map at the same time. One pass, not two. Every serialiser writes through this, so the map costs a counter and an array push per fragment rather than a second traversal — and, more to the point, it cannot fall out of step with the text, because there is no second traversal to fall out of step with.

length
number
last
string
The last character written, or `''` at the start.
text
string
endsWith
(suffix: string) => boolean
syntax
(text: string) => this
Markdown, HTML tags, separators: characters that are in no document.
derived
(text: string, locator?: Locator) => this
Text computed from the model rather than read from it. A list number, a footnote marker, a cell reference printed as a heading. Real content, and belonging to no text node — so it is searchable but anchors to the block, never to a character inside a run that does not exist. Passing the block's locator is what makes that work.
source
(text: string, locator?: Locator, nodeOffset?: number) => this
Characters from a text node, with the offset they start at inside it. Text with no address is recorded as `derived` instead, because `source` without a locator is a contradiction: the category means "these characters came from a node of the document", and a run with no node is by definition something the walk computed. Left as `source`, it also breaks addressing outright — a range whose boundary lands on such a segment has no address to report, so `locate` gives up and a chunk silently loses its exact selector. That is how a tab character between two table-of-contents entries cost a document its locators.
blankLine
(count?: number) => this
Trims trailing blank lines and guarantees exactly `count` newlines. Every block-level serialiser needs this and every one of them gets it subtly wrong on its own, which is how markdown ends up with four blank lines before a table and none after a heading.
build
() => { text: string; map: OffsetMap; }
embed
(sub: OutputBuilder, options?: { linePrefix?: string; }) => this
Appends another builder's output, optionally prefixing every line. What a blockquote needs, and the reason there is no "rewrite what I just wrote" on this class. A quote's children produce an unknown number of lines and each needs a marker, which is only knowable after they are written — and editing the finished string would leave every segment after the edit pointing at the wrong characters. Writing the children into their own builder and splicing the result keeps the map exact, because the segments are re-emitted rather than patched.

Functions

applyHandlers
function applyHandlers(blocks: readonly Block[], options: ResolvedOptions, readMedia: HandlerContext["readMedia"]): Promise<Block[]>

The slow pass, run separately. Handlers are the extension point the whole design was arranged around: OCR for a scanned figure, a vision model for a chart nobody stored the data for, a describer for a photograph, a LaTeX converter for an equation. Every one of them is a network round trip, and every one is optional. So they run here, over the finished tree, and not inside the parse. The parse stays synchronous and fast; this pass can be skipped entirely, retried, rate-limited, or cancelled halfway without leaving a half-parsed document behind. A hook inside the parser would have made the whole pipeline asynchronous to accommodate the OCR of one logo.

applyTransforms
function applyTransforms(blocks: readonly Block[], transforms: readonly Transform[]): readonly Block[]

Applies transforms in order.

autoNumberLabel
function autoNumberLabel(scheme: string, value: number): string

Turns a run of numbers into the label a list marker shows. Presentations number their bullets by scheme rather than by value: the file says `arabicPeriod` and the position in the list is everything else. The schemes are named the same way across DrawingML, so this is shared.

blockOffset
function blockOffset(document: ContentDocument, locator: Locator): { block: Locator; offset: number; } | undefined

An address inside a run, restated as an offset into its block. The translation the viewer needs and cannot do for itself. Extraction addresses a character as "run three of block twelve, seven characters in", because that is what survives a relayout. The page has no runs to speak of — the renderer emits one span per *formatting*, not per source run, and merges neighbours that look alike — so the only thing both halves can count is characters from the start of the block. The extracted document knows the text of every run in order, so the sum is exact. Doing it here rather than in the viewer also means the two never have to agree about what a run is.

blockRange
function blockRange(document: ContentDocument, range: { start: Locator; end: Locator; }): { start: { block: Locator; offset: number; }; end: { block: Locator; offset: number; }; } | undefined

Both ends of a resolved range, as offsets into their blocks.

blockText
function blockText(block: Block): string

The text of a block and everything under it.

buildContent
function buildContent<TDocument extends GenomDocument>(document: TDocument, hash: string, walk: Walk<TDocument>, options?: ExtractOptions): Promise<ContentDocument>

Builds the content document from a document that is already open. The hash is computed from the bytes where they are to hand and omitted where they are not: an address without one still resolves, it simply cannot warn that it came from a different file.

buildTextIndex
function buildTextIndex(document: ContentDocument, output?: OutputKind): TextIndex
childrenOf
function childrenOf(block: Block): readonly Block[]

The children of a block, whatever it calls them.

chunk
function chunk(document: ContentDocument, options?: ChunkOptions): Chunk[]
dropEmptyRows
function dropEmptyRows(): Transform

Drops rows and cells that hold nothing, which a generous used range invents.

dropRepeatedHeaders
function dropRepeatedHeaders(options?: { minRepeats?: number; }): Transform

Drops the running head and foot after their first appearance. A header repeated on every page of an eighty-page report contributes eighty identical fragments, and identical fragments are the worst thing that can happen to a nearest-neighbour search: they crowd out the answer with copies of the company name. Keeping the first occurrence keeps the information. Matched on normalised text so that a header carrying a page number — which is most of them — still counts as the same header.

dropTableOfContents
function dropTableOfContents(): Transform

Removes a table of contents. A generated contents page is a list of every heading in the document with a page number after it, which means it matches almost every query about the document and answers none of them. Recognised by shape — many short lines, most ending in a number, most of whose text appears again as a heading later — rather than by the field that produced it, because half of them are typed by hand.

extractWith
function extractWith<TDocument extends GenomDocument>(input: ByteSourceInput | TDocument, open: (source: ByteSource, options: OpenOptions) => Promise<TDocument>, walk: Walk<TDocument>, options?: ExtractOptions): Promise<ContentDocument>

The whole path for one format: bytes or an open document, into content. What `extractDocx` and its three neighbours call. A document that arrives open passes through and is not closed — it belongs to the caller; one opened here is closed here.

findText
function findText(document: ContentDocument, query: string, options?: { caseSensitive?: boolean; wholeWord?: boolean; limit?: number; }): { range: { start: Locator; end: Locator; }; text: string; }[]

Plain search over a document, returning anchors rather than offsets. What the viewer's find box is built on. Everything it returns is a selector set, so a hit found here highlights through exactly the same path as a chunk retrieved from a vector database — one mechanism, not two.

headingLevelOf
function headingLevelOf(mode: "auto" | "style" | "outline" | "none" | HeadingRule, context: { styleName: string | undefined; styleId: string | undefined; outlineLevel: number | undefined; text: string; }): number | undefined

Whether a paragraph is a heading, and how deep. `auto` believes the style first and the outline level second. Not type size: a document whose author never used a heading style genuinely has no headings, and guessing them from how large the text is produces a table of contents made of pull quotes and drop caps. Wrong structure is worse than none — chunking follows headings, so an invented one splits a passage in half.

inlinesOf
function inlinesOf(block: Block): readonly Inline[]

The inline content of a block, when it has any.

inlineText
function inlineText(inlines: readonly Inline[]): string

The text of a run of inlines, with nothing added.

isContainer
function isContainer(block: Block): boolean
isOpenDocument
function isOpenDocument(value: unknown): value is GenomDocument

Tells an open document from something a document has yet to be opened from.

mergeHyphenation
function mergeHyphenation(): Transform

Rejoins words a line break split with a hyphen. `manage-\nment` is one word that no search will find and no tokenizer will recognise. It comes from justified text in a two-column layout, which is most academic and legal PDF-adjacent material. Conservative on purpose: only a lowercase letter, a hyphen at the end of a line, and a lowercase letter after it. `well-\nknown` is a real hyphen and joining it would be an error, so a word that appears elsewhere in the document with its hyphen intact is left alone.

optionsProfile
function optionsProfile(options: ResolvedOptions, extra?: string): string

A short hash of everything that changes the output. This is what a `TextPosition` selector carries, and the reason it can be trusted. Offsets into markdown with GFM tables and offsets into markdown with HTML tables are different numbers for the same passage; without a fingerprint of the settings that produced them, the difference arrives as a highlight forty characters off rather than as an error anybody can act on. Handlers are folded in as *whether they were present*, not as what they are. A function has no stable identity to hash, and "an image handler ran" is the part that changes the text.

outputOfProfile
function outputOfProfile(profile: string | undefined): OutputKind | undefined

The output a profile was measured against, when it says.

profileFor
function profileFor(optionsProfile: string, output: OutputKind): string

The fingerprint a `TextPosition` carries. The options alone are not enough, and finding that out was expensive: character 89 of the markdown and character 89 of the plain text of the same document with the same settings are different characters in different paragraphs. A selector that recorded only the options resolved against whichever output the reader happened to build, and produced a confident answer pointing somewhere else entirely.

readMedia
function readMedia(document: GenomDocument, partName: string): Promise<{ bytes: Uint8Array; contentType: string; } | undefined>

Reads a media part out of a document, where the format has one. Duck-typed rather than a field of the interface: not every format has media, and the image handler needs a way to reach the bytes where they exist.

resolveOptions
function resolveOptions(options?: ExtractOptions): ResolvedOptions
resolveSelectors
function resolveSelectors(document: ContentDocument, selectors: readonly Selector[], options?: ResolveOptions): ResolvedRange | undefined
selectorsForRange
function selectorsForRange(text: string, map: OffsetMap, start: number, end: number, profile: string): Selector[]

The anchor triple for a range: exact, portable, robust. All three, always, and in this order. They cost a few hundred bytes together and they fail at different times — the exact one the moment somebody saves the file, the portable one the moment the extraction options change, and the robust one only when the words themselves go. Storing one of them is a decision to lose the anchor on a day nobody will connect to the cause.

styleAction
function styleAction(rules: readonly StyleRule[], styleName: string | undefined, styleId: string | undefined): StyleRule["to"] | undefined

The style rule that claims this paragraph, if any.

toHtml
function toHtml(document: ContentDocument, options?: HtmlOptions): { text: string; map: OffsetMap; }
toJson
function toJson(document: ContentDocument): ContentDocumentJson
toMarkdown
function toMarkdown(document: ContentDocument, options?: MarkdownOptions): { text: string; map: OffsetMap; }
toPlainText
function toPlainText(document: ContentDocument, options?: TextOptions): { text: string; map: OffsetMap; }
walk
function walk(blocks: readonly Block[]): Generator<Block>

Every block in the tree, in reading order, parents before children. A generator because callers stop early far more often than they finish — finding the first heading, locating a block by its address — and building the flat list first would undo the point of walking at all.

walkInlines
function walkInlines(inlines: readonly Inline[]): Generator<Inline>

Every inline in a tree of inlines, in reading order.

Interfaces

Annotation
interface Annotation

Something written outside the flow, attached to a place in it. The `anchor` is where the marker sits, so a reader following a footnote knows which sentence it belongs to and a highlight can land on that sentence rather than on the note.

kind
"footnote" | "endnote" | "comment" | "note"
id
string
label?
string | undefined
text
string
blocks
readonly Block[]
anchor?
string | undefined
author?
string | undefined
date?
string | undefined
parentId?
string | undefined
For a threaded comment: the one it replies to.
resolved?
boolean | undefined
BlockBase
interface BlockBase
type
BlockType
locator?
string | undefined
Where this came from. Absent only for blocks a transform invented.
text
string
The flattened text of this block and everything under it. Kept on the block rather than computed on demand because it is what search, chunking and every `includes` in user code reach for first, and computing it repeatedly over a tree of a million nodes is the difference between an extraction that takes a second and one that takes a minute.
BlockquoteBlock
interface BlockquoteBlock extends BlockBase
type
"blockquote"
children
readonly Block[]
BreakBlock
interface BreakBlock extends BlockBase
type
"break"
breakType
"page" | "section" | "column"
BreakInline
interface BreakInline

A hard line break inside a paragraph.

type
"break"
locator?
string | undefined
ChartBlock
interface ChartBlock extends BlockBase

A chart, as what it plots. Every other extractor in this space leaves a hole where a chart was, because a chart looks like a picture and pictures are hard. It is not a picture: it is a table with a title, stored as a table, and reading it out is the single largest thing this package does that the alternatives do not.

type
"chart"
title?
string | undefined
kind?
string | undefined
categories
readonly string[]
Category labels — the x axis, in the ordinary case.
series
readonly ChartSeriesData[]
axisTitles?
{ readonly x?: string; readonly y?: string; } | undefined
ChartSeriesData
interface ChartSeriesData
name?
string | undefined
values
readonly (string | number | null)[]
Chunk
interface Chunk
text
string
The chunk as stored and embedded, contextualised if that was asked for.
contextLength
number
How much of `text` is the prepended heading path.
selectors
readonly Selector[]
Where to find it again: exact, portable and robust, in that order.
breadcrumbs
readonly string[]
The heading path above it.
section
string | undefined
`Slide 5`, `Sheet Budget`, or the section's label.
tokens
number
start
number
Offsets into the serialised output this came from.
end
number
ChunkOptions
interface ChunkOptions

Chunking that knows what a document is. The state of the art in JavaScript is to take the text, split it every 512 characters, and hope. That destroys exactly the things retrieval depends on: a table loses its header three rows in, a heading is separated from the section it names, a sentence is cut in half, and every chunk arrives at the index with no idea where it came from. This walks the tree instead. Headings become breadcrumbs and stay with their content, a table row is never split, a table too big for one chunk is split by rows with its header repeated in each, and every chunk carries the selectors that find it again in the document it came from — which is the part nobody else has, and the reason a retrieval hit can be highlighted rather than merely quoted.

maxTokens?
number | undefined
Target size. Default 512.
overlap?
number | undefined
Overlap between neighbours, in tokens. Default 64.
tokenCounter?
((text: string) => number) | undefined
How to count. Default: characters over four. Pluggable because the right answer depends on a model this package has never heard of, and a default that shipped a tokenizer would be a megabyte of tables for something the caller can do in one line.
contextualize?
boolean | undefined
Prepend the heading path to each chunk's text. Default true. "Q3 Results › Risks › Currency exposure" in front of a paragraph that says "the position was closed in October" is the difference between a chunk that retrieves and one that does not. The context is marked in `contextLength` so a caller that wants the bare text can strip it.
tables?
"whole" | "rows" | undefined
`whole` keeps a table together; `rows` splits large ones. Default `rows`.
source?
"markdown" | "text" | undefined
Which output the chunk text comes from. Default `markdown`.
CodeBlock
interface CodeBlock extends BlockBase
type
"code"
language?
string | undefined
ContentDocumentInit
interface ContentDocumentInit
format
FormatId
hash
string
metadata
DocumentMetadata
blocks
readonly Block[]
annotations
readonly Annotation[]
profile
string
Fingerprint of the options that produced this. Travels in selectors.
ContentDocumentJson
interface ContentDocumentJson

The tree, as data. A stable, versioned schema, and that is the point of it existing at all: the tree is a public contract the moment somebody stores one, and a contract with no version number is a contract nobody can migrate. `schema` is bumped when the shape changes in a way a reader would notice. `JSON.stringify(doc)` on the class would produce private fields, method-less blocks with `undefined` scattered through them, and no version — which is exactly the sort of accident that becomes a file format.

schema
"@genomdev/genom/content@1"
format
FormatId
hash
string
profile
string
metadata
Record<string, unknown>
blocks
readonly Block[]
annotations
readonly Annotation[]
DiagramBlock
interface DiagramBlock extends BlockBase

SmartArt, as the nesting it draws.

type
"diagram"
nodes
readonly DiagramNodeData[]
DiagramNodeData
interface DiagramNodeData
text
string
children
readonly DiagramNodeData[]
ExtractOptions
interface ExtractOptions

What to do with the things that are not words. The defaults are chosen for the commonest use, which is feeding a language model, and they are not the conservative choices. `charts: 'data'` reads a chart out as the numbers it plots, because a chart is a table and leaving a hole where one was is how every other extractor in this space loses the content of a quarterly report. `headers: 'first'` drops the running head repeated on every page, because eighty repetitions of a company name is what poisons a retrieval index.

signal?
AbortSignal | undefined
concurrency?
number | undefined
How many handler calls may be in flight at once. Default 4. The handlers are the slow part — an OCR round trip is a second where everything else in this package is a microsecond — so the number that matters is this one, and it belongs to the caller who knows what is on the other end of it.
images?
"omit" | "alt" | "reference" | "dataUri" | undefined
`omit` drops images; `alt` keeps the block with its alt text and no bytes; `reference` adds the part name so the caller can fetch them; `dataUri` embeds them. Default `alt`.
charts?
"omit" | "title" | "data" | undefined
`data` reads the plotted values out. Default `data`.
diagrams?
"omit" | "nodes" | undefined
Default `nodes`: SmartArt comes out as the nesting it draws.
headers?
"omit" | "first" | "all" | undefined
Running heads and feet. Default `first`. `first` keeps one copy per section, which is where the information is; `all` keeps every repetition, which is what a fidelity-minded caller wants and a retrieval index does not.
comments?
"omit" | "annotate" | undefined
Default `annotate`: comments come out as annotations, not inline text.
notes?
"omit" | "annotate" | "inline" | undefined
Footnotes and endnotes. Default `annotate`.
revisions?
"final" | "original" | "markup" | undefined
Which side of a tracked change to take. Default `final`. `final` is the document as its last author left it; `original` is what it said before; `markup` keeps both, marked.
hiddenText?
boolean | undefined
Hidden text (`w:vanish`). Default false, which is what hiding it meant.
hiddenSheets?
boolean | undefined
Hidden worksheets. Default false: they hold the lookup tables.
formulas?
"value" | "formula" | "both" | undefined
Default `value`: the number as the sheet displays it, not the formula.
emptyCells?
boolean | undefined
Empty rows and columns inside a sheet's used range. Default false. Excel's idea of the used range is generous, and a sheet whose author once typed in `ZZ4000` reports four thousand rows of nothing.
headings?
"auto" | "style" | "outline" | "none" | HeadingRule | undefined
How a paragraph becomes a heading. Default `auto`. `auto` believes the style first, then `w:outlineLevel`, and does not guess from type size — a document whose author never used a heading style has no headings, and inventing them from font size produces a table of contents full of pull quotes.
styleMap?
readonly StyleRule[] | undefined
Map a named style onto an output element, the way mammoth does. The escape hatch for the house style nobody outside the company has heard of: `Zitat` is a blockquote, `Code Block` is code, `Untertitel` is a level two heading. Rules are tried in order and the first match wins.
onImage?
ImageHandler | undefined
onTable?
TableHandler | undefined
onChart?
ChartHandler | undefined
onMath?
MathHandler | undefined
transforms?
readonly Transform[] | undefined
Applied to the tree in order, after the walk and before serialisation.
HandlerContext
interface HandlerContext
signal?
AbortSignal | undefined
section?
string | undefined
The section the block sits in: `Slide 5`, `Sheet Budget`.
breadcrumbs
readonly string[]
The heading path above the block.
readMedia
(partName: string) => Promise<{ bytes: Uint8Array; contentType: string; } | undefined>
Fetches the bytes of a media part, whatever `images` was set to.
HeadingBlock
interface HeadingBlock extends BlockBase
type
"heading"
level
number
1..6.
inlines
readonly Inline[]
styleName?
string | undefined
The style that made it a heading, when a style did.
HtmlOptions
interface HtmlOptions

Semantic HTML: the structure, not the appearance. Not a renderer, and the distinction is the whole design. `@genomdev/docx/view` reproduces what a document looks like — fonts, page boxes, measured line breaks. This produces what it *is*: headings that are headings, tables that are tables, a `<figure>` around a picture and its caption. There is no CSS and no colour, because the consumer is a language model, a search index, or a page that has its own stylesheet and does not want this one. Every element carries its address in `data-loc`, which is what makes an HTML extraction round-trip: a click in the rendered output can be turned back into a place in the document.

locators?
boolean | undefined
Write `data-loc` on every element that has an address. Default true.
figures?
boolean | undefined
`<figure>`/`<figcaption>` around images. Default true.
sections?
boolean | undefined
`<section>` per slide, sheet or document section. Default true.
wrap?
boolean | undefined
Wrap the output in `<article>`. Default false.
footnotes?
boolean | undefined
Footnotes as a `<section class="footnotes">` at the end. Default true.
withMap?
boolean | undefined
ImageBlock
interface ImageBlock extends BlockBase
type
"image"
alt?
string | undefined
Alt text as the document states it.
caption?
string | undefined
A caption a handler supplied, or one found beside the image.
source?
ImageSource | undefined
How to fetch the bytes, when the caller asked to keep them.
widthEmu?
number | undefined
heightEmu?
number | undefined
mimeType?
string | undefined
described?
boolean | undefined
Set when a handler described the image; `text` then holds what it said.
ImageInline
interface ImageInline

An image that flows with the text rather than standing on its own.

type
"image"
alt?
string | undefined
source?
ImageSource | undefined
locator?
string | undefined
ImageResult
interface ImageResult
alt?
string | undefined
Becomes the alt text, and the text the block reports for search.
caption?
string | undefined
replaceWith?
Block | undefined
Replaces the image entirely — an OCR that found a table, say.
ImageSource
interface ImageSource
partName
string
The part name inside the package. Stable, and enough to fetch by.
bytes?
Uint8Array<ArrayBufferLike> | undefined
Present when the caller asked for bytes or a data URI.
url?
string | undefined
LinkInline
interface LinkInline
type
"link"
href
string
title?
string | undefined
children
readonly Inline[]
locator?
string | undefined
ListBlock
interface ListBlock extends BlockBase
type
"list"
ordered
boolean
children
readonly ListItemBlock[]
level
number
Nesting depth, 0 at the top.
ListItemBlock
interface ListItemBlock extends BlockBase
type
"listItem"
marker?
string | undefined
The marker as the document computes it: `1.`, `a)`, `•`, `%1.%2`-resolved. Computed rather than left to the output format, because Word's multi-level patterns cannot be expressed as an ordered list — `2.3.1` is not what any markdown renderer produces from three levels of nesting — and because a restart partway down a document is invisible to a counter. This is `derived` text: real, and belonging to no text node in the file. It anchors to the item, never to a character inside it.
inlines
readonly Inline[]
children?
readonly Block[] | undefined
Nested lists and anything else the item contains.
LocatedRange
interface LocatedRange
start
string
end
string
fit
"exact" | "clamped"
`exact` when the requested range began and ended on source characters; `clamped` when it had to be pulled in to the nearest ones.
MarkdownOptions
interface MarkdownOptions

Markdown, written properly. The bar is not "produces something a renderer accepts". Every library in this space clears that. The bar is that a person reading the output can tell what the document said, and a language model reading it does not have to guess — which means the table alignment survives, the nested list stays nested, a pipe inside a cell does not end the column, a paragraph starting with `1.` does not silently become a list, and a footnote is a footnote rather than a number floating in the middle of a sentence. Every one of those is a bug this had at some point.

tables?
"html" | "gfm" | "list" | undefined
`gfm` for pipe tables, `html` for a `<table>`, `list` for one line a row. `html` is not a cop-out: a pipe table cannot express a row span, and a merged cell rendered as a pipe table is silently wrong in a way nobody notices. `list` is for tables so wide that either of the others is unreadable — a workbook of forty columns, most often.
tableColumnLimit?
number | undefined
Widest table, in columns, still worth a pipe table. Default 12.
images?
"text" | "omit" | "alt" | "link" | undefined
`alt` writes `![alt]()`; `link` writes a real path; `omit` drops them; `text` writes just the alt text with no image syntax at all.
footnotes?
boolean | undefined
`[^1]` footnotes plus a section at the end. Default true.
comments?
boolean | undefined
Comments as footnotes too, marked with the author. Default false.
frontMatter?
boolean | undefined
A YAML block of the document metadata at the top. Default false.
sections?
"none" | "heading" | "rule" | undefined
`---` between sections, and a heading naming each. Default `heading`.
bullet?
"-" | "*" | "+" | undefined
`*` or `-`. Default `-`.
nativeMarkers?
boolean | undefined
Keep the document's own list markers instead of `1.` and `-`. On by default, and it is the right default: Word's `%1.%2` patterns produce `2.3.1`, which three levels of markdown nesting cannot express, and a numbered list that restarts partway down is invisible to a counter. The marker is `derived` text and anchors to the item.
math?
boolean | undefined
`$…$` for maths. Default true where a LaTeX form is available.
marks?
boolean | undefined
Emphasis, strikethrough and the rest. Default true.
withMap?
boolean | undefined
Marks
interface Marks

What a run of text is wearing. A short list on purpose. Everything a word processor can do to a character is not what a reader means by emphasis, and carrying all of it would produce markdown full of `<span style>`. These are the marks that survive being written down as text.

bold?
boolean | undefined
italic?
boolean | undefined
strike?
boolean | undefined
code?
boolean | undefined
superscript?
boolean | undefined
subscript?
boolean | undefined
underline?
boolean | undefined
highlight?
string | undefined
A named or hex highlight colour, when the author marked the text.
insertion?
boolean | undefined
Set on text a revision marked as inserted or deleted.
deletion?
boolean | undefined
MathBlock
interface MathBlock extends BlockBase
type
"math"
display
"inline" | "block"
mathml?
string | undefined
MathML, which is what the parser already produces.
latex?
string | undefined
Set by a handler that can produce it; markdown prefers it when present.
MathInline
interface MathInline
type
"math"
mathml?
string | undefined
latex?
string | undefined
locator?
string | undefined
ParagraphBlock
interface ParagraphBlock extends BlockBase
type
"paragraph"
inlines
readonly Inline[]
styleName?
string | undefined
alignment?
string | undefined
`left`, `center`, `right`, `justify` — kept for HTML, ignored by markdown.
ReferenceInline
interface ReferenceInline

A reference to something written elsewhere. Footnotes, endnotes and comments are separate flows, and putting their text where the marker is would corrupt both the reading order and every offset after it. What goes inline is the marker; the text is an annotation.

type
"reference"
kind
"footnote" | "endnote" | "comment"
id
string
label
string
The marker as printed: `1`, `*`, `a`.
locator?
string | undefined
ResolvedOptions
interface ResolvedOptions

Everything filled in.

signal
AbortSignal | undefined
concurrency
number
images
"omit" | "alt" | "reference" | "dataUri"
charts
"omit" | "title" | "data"
diagrams
"omit" | "nodes"
headers
"omit" | "first" | "all"
comments
"omit" | "annotate"
notes
"omit" | "annotate" | "inline"
revisions
"final" | "original" | "markup"
hiddenText
boolean
hiddenSheets
boolean
formulas
"value" | "formula" | "both"
emptyCells
boolean
headings
"auto" | "style" | "outline" | "none" | HeadingRule
styleMap
readonly StyleRule[]
onImage
ImageHandler | undefined
onTable
TableHandler | undefined
onChart
ChartHandler | undefined
onMath
MathHandler | undefined
transforms
readonly Transform[]
ResolveOptions
interface ResolveOptions

Turning a stored anchor back into a place in a document. The selectors are tried in order of precision and the first that succeeds wins. Which one that was is reported, and it is not decoration: an application that watches `resolvedBy` can see its corpus drifting — the day the exact addresses stop matching and everything falls through to quotes is the day somebody started editing the documents — and can see it before a user reports a highlight in the wrong place.

text?
string | undefined
The output the `TextPosition` selectors were measured against.
map?
OffsetMap | undefined
strictProfile?
boolean | undefined
Refuse a `TextPosition` whose profile does not match this document's. Default true, and it should stay true. Offsets into markdown with GFM tables and offsets into markdown with HTML tables are different numbers for the same passage, and a mismatch resolved anyway is a highlight forty characters off that nobody can explain.
minConfidence?
number | undefined
Below this, a fuzzy quote match is rejected. Default 0.75.
SectionBlock
interface SectionBlock extends BlockBase

A container for the format's own idea of a page. The three formats disagree about what a page is and only one of them is right in the way a reader means. A slide is a page. A worksheet is a page in the sense that matters (it is the unit you navigate to) and not in the sense that prints. A Word section is neither: pages inside it fall wherever the fonts on the machine put them, which is why extraction refuses to number them — see `pageHint`.

type
"section"
label
string
What a reader would call it: `Slide 5`, `Sheet Budget`, `Section 2`.
index
number
Zero-based position among its siblings.
children
readonly Block[]
pageHint?
number | undefined
The page Word last cached a break for, when the file states one. A hint and named one. Word writes `w:lastRenderedPageBreak` where the machine that last saved the file happened to break the page, and that machine had its own fonts and its own printer. Reporting it as a page number would be reporting somebody else's pagination as this document's.
Segment
interface Segment
start
number
Half-open range in the output string.
end
number
kind
SegmentKind
locator?
string | undefined
The node this came from. Absent on `syntax`.
nodeOffset?
number | undefined
Offset of `start` within that node's own text. `source` only.
StyleRule
interface StyleRule
style
string | RegExp
Style name or id. A string matches exactly; a regexp matches loosely.
to
{ readonly type: "heading"; readonly level: number; } | { readonly type: "blockquote"; } | { readonly type: "code"; readonly language?: string; } | { readonly type: "paragraph"; } | { readonly type: "omit"; }
TableBlock
interface TableBlock extends BlockBase
type
"table"
rows
readonly TableRowBlock[]
headerRows
number
How many leading rows are the header. Usually 0 or 1.
caption?
string | undefined
columnCount
number
Column count after spans are accounted for.
TableCellBlock
interface TableCellBlock extends BlockBase
type
"tableCell"
children
readonly Block[]
rowSpan
number
columnSpan
number
merged?
boolean | undefined
True for the cells a merge swallowed. Kept rather than dropped so that a consumer counting columns gets the same answer as the grid does. Serialisers skip them.
reference?
string | undefined
`Sheet!C14`, for the one format whose cells have names.
TableRowBlock
interface TableRowBlock extends BlockBase
type
"tableRow"
cells
readonly TableCellBlock[]
header
boolean
TextIndex
interface TextIndex

A document's text with the addresses it maps to, built once and reused. Resolving one quote means scanning the whole text of the document. Resolving a hundred citations from a chat answer means doing it a hundred times, so the normalised form and its map back are built once and kept.

text
string
map
OffsetMap
normalized
import("/repo/packages/core/src/index").NormalizedText
TextInline
interface TextInline
type
"text"
text
string
marks?
Marks | undefined
locator?
string | undefined
TextOptions
interface TextOptions

Plain text: the output with nothing added. The one that has to be genuinely plain. It is what goes into a search index, a diff, a `grep`, and every pipeline whose next stage is not a markdown parser — and every character of markup in it is a false hit waiting to happen. So the only characters here that are not from the document are the separators between blocks, and a caller who wants none of those can say so.

separator?
string | undefined
Between blocks. Default `\n`.
sectionBreaks?
boolean | undefined
An extra newline between sections. Default true.
sectionLabels?
boolean | undefined
Name each section (`Slide 5`) before its content. Default true.
tables?
"tab" | "align" | "lines" | undefined
How a table row is written. Default `tab`. `tab` keeps the columns machine-readable — a row is still a record — and `align` pads them so a person can read the table in a terminal. `align` costs a second pass over the table to measure it.
markers?
boolean | undefined
Include list markers. Default true.
annotations?
boolean | undefined
Append footnote and comment text at the end. Default true.
withMap?
boolean | undefined
WalkResult
interface WalkResult

What a walk returns: the block tree, and the annotations found on the way.

blocks
Block[]
annotations
Annotation[]

Type aliases

Block
type Block = | SectionBlock | HeadingBlock | ParagraphBlock | ListBlock | ListItemBlock | TableBlock | TableRowBlock | TableCellBlock | ImageBlock | ChartBlock | DiagramBlock | MathBlock | BlockquoteBlock | CodeBlock | BreakBlock
BlockType
type BlockType = | 'section' | 'heading' | 'paragraph' | 'list' | 'listItem' | 'table' | 'tableRow' | 'tableCell' | 'image' /** A chart, carried as the data it plots. */ | 'chart' /** SmartArt, carried as the nesting it draws. */ | 'diagram' | 'math' | 'blockquote' | 'code' /** An explicit page or column break the author put there. */ | 'break'

What a block is.

ChartHandler
type ChartHandler = ( chart: ChartBlock, context: HandlerContext ) => Promise<Block | void> | Block | void
HeadingRule
type HeadingRule = (context: { styleName: string | undefined; styleId: string | undefined; outlineLevel: number | undefined; text: string; }) => number | undefined

Decides the heading level of a paragraph, or that it is not one.

ImageHandler
type ImageHandler = ( image: ImageBlock, context: HandlerContext ) => Promise<ImageResult | void> | ImageResult | void

What a handler is given, and what it may say back. Handlers run over the finished tree rather than during the parse. That is a deliberate trade: the parse stays synchronous and fast, and everything slow is a separate pass that can be skipped, retried, rate-limited or cancelled without touching it. A hook inside the parser would have made the whole pipeline asynchronous to accommodate the OCR of one logo. Returning nothing leaves the block alone. Returning a block replaces it — which is how a caller turns an image into a table, or a table into prose.

Inline
type Inline = TextInline | LinkInline | BreakInline | ImageInline | ReferenceInline | MathInline
MathHandler
type MathHandler = ( math: MathBlock, context: HandlerContext ) => Promise<{ latex?: string } | void> | { latex?: string } | void
OutputKind
type OutputKind = 'markdown' | 'text' | 'html'

Which serialisation a set of offsets was measured against.

SegmentKind
type SegmentKind = 'source' | 'derived' | 'syntax'

Where every character of the output came from. Addresses are not written into the text. The text stays text — no markers, no spans, nothing that would land in an embedding, in a prompt, or in the bill for the tokens. And nothing that would survive anyway: the caller is going to chunk this with their own splitter, and a splitter throws away whatever markup it does not understand, taking the addresses with it. So the addresses travel beside the text, in this. Markdown is what makes it interesting. Markdown *adds* characters — `## `, `**`, `|`, a row of dashes — that exist in no document, and a map that did not know the difference would put every highlight out by the width of the syntax in front of it. Hence three categories: source characters from a text node of the document derived text computed from the model: a list number, a footnote marker syntax markdown the serialiser invented The same distinction the renderer makes with `data-synthetic` when it counts characters in the DOM, running the other way. One idea, both halves of the system.

TableHandler
type TableHandler = ( table: TableBlock, context: HandlerContext ) => Promise<Block | void> | Block | void
Transform
type Transform = (blocks: readonly Block[]) => readonly Block[]

A pass over the tree. Returning `undefined` for a block drops it. The three shipped with the package are the three problems every retrieval pipeline hits in its first week, and finding out about them a week in is expensive.

Walk
type Walk<TDocument extends GenomDocument = GenomDocument> = ( document: TDocument, hash: string, options: ResolvedOptions ) => Promise<WalkResult>

One format's walk: its model into the block tree.

@genomdev/core/dom

Classes

BaseDocumentView
class BaseDocumentView<TDocument extends GenomDocument = GenomDocument> implements DocumentView

Base implementation of {@link DocumentView}. Handles what every renderer needs identically: the root element, the resize subscription, zoom, and correct resource cleanup. Subclasses only implement {@link renderContent}.

root
HTMLElement
zoom
number
fit
NonNullable<"none" | "width" | "page" | undefined>
initialize
() => Promise<void>
Initial render. Separate from the constructor because it is asynchronous: a renderer may need to fetch document parts that the parser left lazy.
renderContent
() => Promise<void> | void
Renders the content into {@link root}. Called on every update.
onContainerResize
() => void
Called when the container is resized. The default is a full re-render. Renderers that can reflow incrementally should override this — a full rebuild on every resize frame is exactly the behaviour that makes viewers feel slow.
update
() => void
Re-render after the container was resized or settings changed.
zoomLevel
number
The zoom now. Public because `setZoom` is, and a setting nobody can read back is a setting an application has to keep a second copy of — which is how a toolbar comes to show one number while the document is at another. Named apart from the protected field because a class cannot have both.
setZoom
(zoom: number) => void
Changes the zoom level and re-renders.
destroy
() => void
Tear the view down and release resources. The container is left empty.
destroyed
boolean
StyleSheetBuilder
class StyleSheetBuilder

Accumulates CSS rules and installs them as a single stylesheet. Renderers must emit shared CSS classes rather than inline `style` attributes. The difference is not cosmetic: a document with 50 000 runs produces 50 000 inline style attributes, each of which the browser parses separately and none of which can be shared. Routing the same formatting through a handful of generated classes cuts both the DOM size and the style recalculation cost by an order of magnitude. The builder also deduplicates: identical declaration blocks collapse onto one class, which is exactly what happens in real documents where a few dozen distinct formatting combinations cover the entire text.

addRaw
(css: string) => void
Appends a raw rule as-is.
revision
number
Increments on every change; identical values mean identical CSS.
addRule
(selector: string, styles: Record<string, string | undefined>) => void
Appends a rule built from a selector and a style map.
classFor
(styles: Record<string, string | undefined>, layer?: string) => string | undefined
Registers a set of declarations and returns a class name for them. Repeated calls with equal declarations return the same class, so callers do not have to deduplicate formatting themselves.
size
number
Number of generated rules; useful for diagnostics.
toString
() => string
install
(ownerDocument: Document, key: string) => HTMLStyleElement
Installs the accumulated rules into a `<style>` element. A single `textContent` assignment is used on purpose: inserting rules one by one through `CSSOM` forces a style recalculation per rule. Unchanged sheets are left alone. Callers install after every lazily rendered fragment, and most of those introduce no new formatting; rewriting the element anyway replaces the whole stylesheet, which invalidates the computed style of every node in the document. During scrolling that happens once per page and shows up as a visible flicker.
TextMetricsCache
class TextMetricsCache
available
boolean
Whether measurement is available; without a canvas the engine must estimate.
measureWidth
(text: string, font: FontSpec) => number
Measures the advance width of a string in CSS pixels.
measure
(text: string, font: FontSpec) => MeasuredText
Measures a string together with the vertical metrics of its font.
fontMetrics
(font: FontSpec) => { ascent: number; descent: number; }
Ascent and descent of a font, in CSS pixels. Measured once per font from a reference string that reaches both extremes. Falls back to typographic ratios when the browser does not expose the detailed `TextMetrics` fields.
descentShare
(font: FontSpec) => number
What share of a font's own box hangs below the baseline. PowerPoint compresses a line from the top: a pitch under a hundred per cent keeps the descent and takes the rest off the ascent. How much descent it keeps is the *font's* — `top-faces` sets the same line at ninety per cent of 44 points in five faces and PowerPoint puts the five first baselines 50.11, 47.91, 47.93, 49.73 and 51.33 px below the box, which is the line's height less `1.2 × descent / (ascent + descent)` ems in every one of them, to an eighth of a pixel. Measured rather than taken from a table, because the point of it is the face the browser actually resolved: a document naming a font the machine has not got is drawn in a stand-in, and the stand-in's descent is the one on the screen. At {@link DESCENT_REFERENCE} px, because the numbers a canvas hands back are whole pixels and this is a ratio of them.
baselineWithin
(font: FontSpec, lineHeightPx: number) => number
Where the browser puts the baseline of the first line of a box. The distance from the top of a line box of height `lineHeightPx` down to the baseline of the text in it. CSS says half-leading — `ascent + (L − G)/2` — but the ascent and the glyph box in that formula are the *layout's*, and the layout's are not the canvas's: a font that sets `USE_TYPO_METRICS` is laid out with its typographic metrics and its line gap while `measureText` reports the window metrics, and browsers differ over which they take. Computing it from `fontMetrics` is out by a pixel or more on ordinary faces, which is the whole size of the correction it is used for. So it is measured: a zero-sized inline-block sits on the baseline by definition, and its top edge is the baseline's position. Cached per font and height, because a slide asks the same question once per paragraph.
fitCharacters
(text: string, font: FontSpec, maxWidth: number) => number
Finds the longest prefix of `text` that fits into `maxWidth`. Returns the number of characters that fit. Uses binary search over the string rather than measuring character by character: measuring a 2000 character paragraph one character at a time is 2000 canvas calls, whereas binary search needs about eleven.
clear
() => void
cacheSize
number
Number of cached string measurements; exposed for diagnostics.

Functions

attachZoomGestures
function attachZoomGestures(target: HTMLElement, options: ZoomGestureOptions): () => void

Listens for the zoom gestures on an element; the returned function stops. Registered with `passive: false` throughout, because every one of these has to be able to prevent the browser's own answer to the same gesture — and a listener registered passively cannot.

clampZoom
function clampZoom(zoom: number): number
clearChildren
function clearChildren(node: Node): void

Removes every child of a node.

contrastingTextColor
function contrastingTextColor(background: Rgba): Rgba

Picks a readable text colour for a given background. Needed wherever a format specifies only a fill: a table header with a dark shade and default black text would be unreadable. The 0.5 threshold is on WCAG relative luminance.

createElement
function createElement<K extends keyof HTMLElementTagNameMap>(ownerDocument: Document, tagName: K, options?: ElementOptions): HTMLElementTagNameMap[K]
createSvgElement
function createSvgElement(ownerDocument: Document, tagName: string, attributes?: Record<string, string | undefined>): SVGElement

Creates an SVG element; needed for VML shapes and drawing fallbacks.

cssRule
function cssRule(selector: string, styles: Record<string, string | undefined>): string

Builds a complete CSS rule from a selector and a style map.

escapeCssIdentifier
function escapeCssIdentifier(value: string): string

Escapes a string so it can be used as a CSS class name. Word style identifiers may contain spaces, dots and non-ASCII characters; all of them have to be neutralised before they become part of a selector.

escapeHtml
function escapeHtml(text: string): string

Escapes a string for safe insertion into HTML markup.

hasView
function hasView(module: FormatModule): module is ViewModule

Whether a module brought a renderer with it.

hslToRgb
function hslToRgb(hsl: Hsl, alpha?: number): Rgba
injectStyleOnce
function injectStyleOnce(ownerDocument: Document, key: string, css: string): void

Adds a stylesheet to the document exactly once. A page may host several viewers while the renderer stylesheet is shared; the key prevents a duplicate `<style>` on every mount.

installFontFallbacks
function installFontFallbacks(ownerDocument: Document, families: Iterable<string>): readonly string[]

Declares stand-ins for whichever of `families` the browser cannot resolve. Idempotent and additive: the rules accumulate in one stylesheet per document, so a viewer may call this once per slide without re-measuring what it has already answered. A family the table does not know is left alone — a wrong correction is worse than none, and the table is only as wide as the fonts whose metrics have actually been read.

lengthToCss
function lengthToCss(length: Length | undefined): string | undefined

Converts a {@link Length} to a CSS string, or `undefined` when it is `auto`.

lengthToPixels
function lengthToPixels(length: Length | undefined, reference?: number): number

Converts a {@link Length} to pixels; percentages need a reference size.

observeResize
function observeResize(element: HTMLElement, onResize: () => void): () => void

Subscribes to container size changes. Returns an unsubscribe function. When `ResizeObserver` is unavailable (older environments, server rendering) no subscription is created and the caller is responsible for triggering re-layout itself.

parseHexColor
function parseHexColor(value: string | undefined): Rgba | undefined

Parses `ST_HexColor`: six or eight hex digits, with or without a leading hash. The special value `auto` means "the application picks the colour", so it returns `undefined` and lets the caller apply its own contextual rule (usually black text on a light background).

percent
function percent(value: number): Length
pixelsToPoints
function pixelsToPoints(pixels: number): number
points
function points(value: number): Length
pointsToPixels
function pointsToPixels(points: number): number
pt
function pt(value: number): string

Formats a value as a CSS point string.

px
function px(value: number): string

Formats a value as a CSS pixel string.

relativeLuminance
function relativeLuminance(color: Rgba): number

WCAG 2.1 relative luminance, 0..1.

rescaleFrame
function rescaleFrame(frame: ScaledFrame, width: number, height: number, zoom: number): void

Re-scales a frame already in the document. The whole reason the model is worth having: changing the zoom is two style writes and no layout at all. Under the multiply-everything arrangement it was a full re-render — for Word, a re-pagination of the entire document on every step of the zoom control.

rgbToHsl
function rgbToHsl(color: Rgba): Hsl
scaledFrame
function scaledFrame(ownerDocument: Document, width: number, height: number, zoom: number, options?: { className?: string; innerClassName?: string; }): ScaledFrame

A box that draws at natural size and occupies the scaled one.

scrollParentOf
function scrollParentOf(element: HTMLElement): HTMLElement | undefined

The element that actually scrolls above this one. A viewer is mounted into a container the application styled, and whether that container scrolls or one of its ancestors does is the application's choice rather than the viewer's. Anything that has to keep a point still while the document changes size — which is every zoom — needs the answer, and guessing wrong means the scroll offsets are written to an element that has none.

stylesToCssText
function stylesToCssText(styles: Record<string, string | undefined>): string

Serialises a style map into a CSS rule body. Used by the stylesheet generator, which emits real CSS rules instead of inline styles: one rule shared by ten thousand paragraphs is dramatically cheaper for the browser than ten thousand inline `style` attributes.

toCssColor
function toCssColor(color: Rgba | undefined): string | undefined
toKebabCase
function toKebabCase(property: string): string

Interfaces

DecorateContext
interface DecorateContext
format
string
The format doing the rendering: `docx`, `xlsx`.
kind
string
What is being decorated: `cell`, `paragraph`, `hyperlink`.
document
GenomDocument
DocumentView
interface DocumentView

A live view of a document mounted into the DOM.

update
() => void
Re-render after the container was resized or settings changed.
destroy
() => void
Tear the view down and release resources. The container is left empty.
setZoom?
((zoom: number) => void) | undefined
Changes the scale the document is drawn at. Optional, because a renderer may have nothing to scale — but declared here because otherwise the zoom is a *constructor argument*, and an application that wants a zoom control has to tear the document down and parse it again for every step of it. Every renderer built on `BaseDocumentView` has it.
zoomLevel?
number | undefined
The scale it is drawn at now, where the renderer has one.
goToPage?
((index: number, behavior?: ScrollBehavior) => void) | undefined
Scrolls to a page, a sheet or a slide, counted from nought. Optional for the same reason as the zoom, and here for the same reason: a document with an outline, a table of contents or a search result beside it is useless without a way to reach the place, and every renderer already has one — it just could not be got at through the viewer.
pageCount?
number | undefined
How many pages, sheets or slides there are, where the renderer counts.
currentPage?
number | undefined
The one the reader is looking at, where the renderer follows.
ElementOptions
interface ElementOptions

Thin DOM helpers. Renderers create thousands of elements per document, and calling `document.createElement` followed by one-by-one style assignment is the single biggest source of noise in that kind of code.

className?
string | undefined
style?
Partial<Record<string, string | undefined>> | undefined
CSS properties in camelCase; `undefined` values are skipped.
text?
string | undefined
attributes?
Record<string, string | undefined> | undefined
children?
readonly Node[] | undefined
FontMetrics
interface FontMetrics

Type metrics for the families a document is most likely to ask for and a browser least likely to have, as fractions of the em. Generated by `node tools/fonts/generate.mjs` from the fonts themselves — `head` for the design grid, `OS/2`/`hhea` for the vertical extents, and the average advance over a sample of English text. Do not edit by hand. What they are for: a viewer cannot draw a font it has not got, and the substitute the browser picks is not the one the authoring application picked. A third of the presentation corpus's line mass is set in a family the browser cannot resolve — Aptos above all, because Office keeps its cloud fonts in a private directory it never registers — and where a font is substituted the lines wrap **later** than PowerPoint's on 472 slides against 280. The stand-in is narrower, so a line that should have broken carries one more word, and from there neither side has the same lines at all. With these an `@font-face` can make a font the machine *does* have take the missing one's measurements: `size-adjust` for the width of the letters, `ascent-override` and `descent-override` for the height of the line.

ascent
number
Ascent above the baseline, as a fraction of the em.
descent
number
Descent below it, positive.
lineGap
number
boldAverageWidth?
number | undefined
Mean advance of the family's **bold** cut, where the machine had one. A stand-in built from the regular’s widths and then synthesised bold by the browser is four per cent wide of the real cut — Roboto Bold draws a line 1117.8 px wide where a synthesised Arial with Roboto’s regular width draws it 1176.8. Bold words are titles, so the error is where it shows.
averageWidth
number
Mean advance over a sample of English text, as a fraction of the em.
FontSpec
interface FontSpec

A font as far as measurement is concerned.

family
string
sizePx
number
Size in CSS pixels.
bold
boolean
italic
boolean
kerning?
boolean | undefined
Whether the pairs of this text are kerned. Word kerns only where `w:kern` asks it to, and a canvas kerns by default — so a measurement that does not say leaves the two disagreeing about every pair a font tightens, which is most of them. It measures narrower than the text will be drawn, and a line breaker fed that number puts a word too many on the line. Defaults to off, which is what the viewer's own stylesheet says.
Hsl
interface Hsl
h
number
0..1
s
number
0..1
l
number
0..1
Length
interface Length

A length as stored in the file, together with the unit it was stored in. Keeping the unit lets the renderer decide how to emit it: some measurements are better expressed in `pt` so the browser can round them itself, others must be pixels because they take part in layout arithmetic.

value
number
unit
"auto" | "pt" | "px" | "percent"
MeasuredText
interface MeasuredText
width
number
Advance width in CSS pixels.
ascent
number
Distance from the baseline to the top of the tallest glyph.
descent
number
Distance from the baseline to the bottom of the lowest glyph.
Rgba
interface Rgba

Colour arithmetic, in the form every format needs it. A colour as a number, as CSS, as HSL, and as the luminance that decides whether text on it should be black or white. Nothing here belongs to a particular format — the transforms DrawingML defines on top of this (`tint`, `shade`, `lumMod`) live in `@genomdev/office-core`, because they are elements of a schema rather than facts about colour.

r
number
0..255
g
number
0..255
b
number
0..255
a
number
0..1
ScaledFrame
interface ScaledFrame
outer
HTMLElement
The element to put in the document flow. A CSS transform does not affect layout: a page scaled to half size still occupies its full height, and a document of two hundred of them would scroll twice as far as it draws. The outer box carries the scaled size so that the flow, the scrollbars and any virtualiser see the truth.
inner
HTMLElement
The element to draw into, at natural size. Whatever goes in here is laid out as though the zoom did not exist, which is the point.
ViewFactory
interface ViewFactory<TDocument extends GenomDocument = GenomDocument>

Mounts a parsed document into an element.

mount
(document: TDocument, container: HTMLElement, options?: ViewOptions) => Promise<DocumentView>
ViewModule
interface ViewModule<TDocument extends GenomDocument = GenomDocument> extends FormatModule<TDocument>

A format module that can also draw. What `@genomdev/docx/view` exports.

view
ViewFactory<TDocument>
ViewOptions
interface ViewOptions
zoom?
number | undefined
Rendering scale, 1 means 100%.
fit?
"none" | "width" | "page" | undefined
How the page should be fitted into the container.
initialPage?
number | undefined
Initial page/sheet/slide, zero-based.
signal?
AbortSignal | undefined
emit?
((event: string, payload: unknown) => void) | undefined
Where a renderer reports what happened. Supplied by the viewer. A renderer that has something to say — the reader moved to another page, selected a cell, followed a link — says it here, and the viewer turns it into an event with a name. This is why the React, Vue and Angular wrappers are the same size: they subscribe to one bus rather than to a callback per format.
decorate?
Readonly<Record<string, Decorator>> | undefined
Decorators, keyed by the node kind the renderer names.
ZoomGesture
interface ZoomGesture

What a gesture asks for.

zoom
number
The zoom being asked for, already held to the bounds given.
clientX
number
The point on the screen that should stay where it is.
clientY
number
ZoomGestureOptions
interface ZoomGestureOptions
zoom
() => number
The zoom now. Read at the start of every gesture rather than held, so that the caller stays the one place that knows what the zoom is — a toolbar step taken between two pinches must not be undone by the second of them.
onZoom
(gesture: ZoomGesture) => void
wheel?
boolean | undefined
Ctrl (or the command key) with the wheel, and the trackpad pinch that arrives as it.
pinch?
boolean | undefined
Two fingers on a touchscreen, and Safari's own trackpad gesture.
min?
number | undefined
max?
number | undefined

Type aliases

Decorator
type Decorator = ( element: HTMLElement, node: unknown, context: DecorateContext ) => HTMLElement | void

Changes the element a renderer just produced. Called *after* the default rendering, which is what makes it a stable contract: replacing a renderer breaks whenever its internals move, while adding a class or an attribute to a finished element does not. Returning an element replaces the original for callers who really do need that.

Values

AUTO_LENGTH
AUTO_LENGTH: Length
BLACK
BLACK: Rgba
FONT_METRICS
FONT_METRICS: Readonly<Record<string, FontMetrics>>
MAX_ZOOM
MAX_ZOOM: 8
MIN_ZOOM
MIN_ZOOM: 0.1

What a zoom is allowed to be. Beyond this the browser stops being useful.

PX_PER_INCH
PX_PER_INCH: 96

CSS pixels per inch — 96, as is conventional on the web.

PX_PER_POINT
PX_PER_POINT: number
WHITE
WHITE: Rgba

@genomdev/core/dom/highlight

Classes

HighlightApiPainter
class HighlightApiPainter

Paints ranges through the Custom Highlight API. One registry entry per highlight name rather than per range: the API takes a set of ranges, and a document with four hundred search hits should be four hundred ranges in one entry, not four hundred entries. The rules that colour them are injected once per name.

paint
(name: string, ranges: readonly Range[], style: HighlightStyle) => void
clear
(name: string) => void
HighlightRegistry
class HighlightRegistry
add
(selectors: readonly Selector[], options?: HighlightOptions) => HighlightHandle
Registers a highlight and paints whatever of it is currently on the page. Resolution happens once, here, rather than on every repaint: a scroll through a document with four hundred hits would otherwise re-resolve four hundred anchors per frame, and quote resolution scans the whole text.
highlights
readonly HighlightHandle[]
Every registered highlight, painted or not.
get
(id: string) => HighlightHandle | undefined
remove
(id: string) => void
clear
() => void
refresh
() => void
Repaints from the DOM as it is now. Cheap enough to call on every scroll: the work is one selector lookup and one range construction per highlight whose block is mounted, and none at all for the rest. Coalesced to an animation frame so a burst of mount events costs one repaint.
scrollTo
(id: string, options?: ScrollIntoViewOptions) => boolean
Brings a highlight into view. Returns false when the highlight is not on the page and the host has given no way to get there. That is not a failure to report as an error — it is the ordinary state of a highlight nine hundred pages away — but the caller has to know, because "scroll to the answer" that silently does nothing is worse than a message saying the document has moved on.
pageOf
((block: Locator) => number | undefined) | undefined
Set by the host: which page a block landed on after layout.
onScrollToPage
((page: number) => void) | undefined
Set by the host: scroll to a page so its content mounts.
dispose
() => void
OverlayPainter
class OverlayPainter

Paints ranges as a layer of positioned rectangles. The layer is per container rather than per document, and positioned against it, so a highlight moves with the page it is on and disappears with it. That is what makes this survive virtualisation without any bookkeeping: the rectangles belong to the same element the content does.

paint
(name: string, ranges: readonly Range[], style: HighlightStyle) => void
clear
(name: string) => void
clearAll
() => void

Functions

blockOf
function blockOf(locator: Locator): Locator

The block part of an address, without its character offset.

domRange
function domRange(root: ParentNode, start: BlockOffset, end: BlockOffset): Range | undefined

A DOM range from a pair of block offsets. Returns `undefined` when either end is not on the page. Half a range is not a usable answer: a highlight drawn from the start of a mounted block to the end of the document is worse than no highlight, because it looks deliberate.

elementFor
function elementFor(root: ParentNode, block: Locator): HTMLElement | undefined

The element a block was rendered into. Returns `undefined` when the block is not in the DOM, which under virtualisation is the normal case rather than an error: only the pages near the viewport exist. The registry deals with that by re-applying highlights when a page mounts — see `registry.ts`.

elementsFor
function elementsFor(root: ParentNode, block: Locator): HTMLElement[]

Every element a block was rendered into: more than one when a page split it.

offsetOf
function offsetOf(locator: Locator): number

The character offset an address carries, or zero.

positionIn
function positionIn(element: HTMLElement, offset: number, prefer?: "start" | "end"): { node: Text; offset: number; } | undefined

A DOM position from a character offset inside a block. Walks the block's text in document order, skipping anything marked synthetic, until the count reaches the offset. An offset past the end of the block clamps to its last position rather than failing: a highlight whose end is one character beyond the text — which happens whenever a range ends on a paragraph boundary — should still be a highlight. `prefer` decides what to do on a boundary, where two DOM positions describe the same character index: the end of one text node and the start of the next. They are equivalent to the DOM and not to the eye. A range that *starts* at the end of a node produces a zero-width rectangle at the end of that line whenever the text wraps there — a stray tick in the margin — and a range that *ends* at the start of the next node produces the same thing at the start of the following line. So a start prefers the later position and an end the earlier, which is also what a browser does with its own selection.

supportsHighlightApi
function supportsHighlightApi(): boolean

True when the host can paint ranges without touching the DOM.

textNodesOf
function textNodesOf(element: HTMLElement): Generator<Text>

The text nodes of a block that came from the document. A `TreeWalker` rather than `textContent`, because the synthetic ones have to be left out and `textContent` cannot leave anything out. `FILTER_REJECT` on the element prunes the whole subtree, which is what a marker is.

textOf
function textOf(element: HTMLElement): string

The document text of a block, as the page holds it.

Interfaces

BlockOffset
interface BlockOffset
block
string
The address of the block, without a character offset.
offset
number
Characters into that block's own text.
HighlightFragment
interface HighlightFragment
element
HTMLElement
range
Range
block
string
The block this fragment sits in.
HighlightHandle
interface HighlightHandle
id
string
selectors
readonly Selector[]
resolvedBy
"locator" | "position" | "quote" | "native" | undefined
How the address was found: exact, portable, robust — or not at all.
confidence
number
fragments
readonly HighlightFragment[]
What is on the page right now. Empty while the pages are elsewhere.
data
unknown
remove
() => void
HighlightOptions
interface HighlightOptions extends HighlightStyle

Highlights, as state rather than as an operation. This is the design decision virtualisation forces, and it is not a detail. Only the pages near the viewport exist in the DOM — that is what lets a thousand-page document open as quickly as a five-page one — so "highlight this passage" cannot mean "find it and paint it". The passage is usually not there yet. So a highlight is a registered intention. The registry keeps it, paints what is currently on the page, and repaints when the page changes: a scroll, a zoom, a mount. Add a highlight on page four hundred of an unopened document and nothing visible happens until page four hundred arrives, at which point it is already there. The other thing that falls out of this: a highlight is a *list of fragments*, never a rectangle. A paragraph split between page three and page four gives two groups of rectangles on two pages, and an API that promised one would have to be rewritten the first time somebody highlighted a long quotation.

id?
string | undefined
A caller-chosen id. Adding twice with the same id replaces the first.
data?
unknown
Carried through to the caller; the registry does not read it.
HighlightRegistryOptions
interface HighlightRegistryOptions
onChange?
((highlights: readonly HighlightHandle[]) => void) | undefined
Called when the set of painted fragments changes. The hook a scrollbar's minimap or a "3 of 47" counter is built on.
HighlightStyle
interface HighlightStyle

Putting a highlight on the page without touching the page. The obvious implementation — wrap the range in a `<mark>` — is the wrong one here, and not marginally. This viewer paginates by measuring the content it actually rendered, so any element inserted into the flow changes where the pages break. A highlight would move the text it is highlighting, and a search across a long document would repaginate it on every hit. So nothing is inserted. Two techniques, in order of preference: The CSS Custom Highlight API takes `Range` objects and paints them from the highlight registry with no DOM involvement at all — designed for exactly this and available in every current engine. It is the primary path. An absolutely positioned layer of rectangles is the fallback, and is also the only option for the things a text highlight cannot express: a box around a picture or a merged cell, a highlight that has to be clickable, one that carries a label. The rectangles come from `Range.getClientRects()` rather than from arithmetic, because the browser has already solved bidirectional text, ligatures and line wrapping, and doing it again by hand would get all three wrong.

className?
string | undefined
A class on the overlay rectangles.
color?
string | undefined
Painted with the Custom Highlight API when the host supports it.
background?
string | undefined
overlay?
boolean | undefined
Force the overlay even where the highlight API is available.
onClick?
((event: MouseEvent) => void) | undefined
Called when a rectangle is clicked. Implies the overlay.
title?
string | undefined
Tooltip on the overlay rectangles.

Values

HIGHLIGHT_CSS
HIGHLIGHT_CSS: "\n.genom-highlight {\n --genom-highlight-fill: rgb(255 213 0 / 0.42);\n background: var(--genom-highlight-fill);\n border-radius: 2px;\n}\n\n.genom-highlight--active {\n --genom-highlight-fill: rgb(255 145 0 / 0.55);\n outline: 1px solid rgb(255 145 0 / 0.9);\n}\n\n::highlight(genom-find) {\n background-color: rgb(255 213 0 / 0.42);\n}\n\n::highlight(genom-find-active) {\n background-color: rgb(255 145 0 / 0.6);\n}\n"

The default look, for a host that would rather not write any. Injected by the caller rather than by the registry: a viewer embedded in an application has a design system, and a package that put a stylesheet in the head without being asked would be fighting it. The colours are stated as custom properties so overriding one does not mean copying the rule. `::highlight()` accepts a short list of properties — colour, background, decoration and shadow — and nothing that affects layout, which is the point of it: a highlight cannot move the text it marks.

SYNTHETIC_SELECTOR
SYNTHETIC_SELECTOR: "[data-synthetic]"

Finding a place in the page from an address in the document. The last link of the chain, and the one where the two halves of the system have to agree exactly: Selector[] → ModelRange → **DOM Range** → rectangles → a highlight The renderer stamps `data-loc` on block-level elements only — a document with 200 000 runs would otherwise carry 200 000 attributes, which is the weight this viewer avoids by emitting shared CSS classes instead of inline styles. So a character inside a block is found by walking the block's text nodes and counting, which is cheap and is only ever done for the handful of blocks a highlight actually touches. The counting has one rule, and it is the whole reason this works: text the renderer computed rather than read is skipped. List numbers are put on the page as ordinary text (deliberately — so they are correct under virtualisation, correct in print, selectable and searchable), and so are footnote markers and tab leaders. All of it is real on the page and belongs to no run in the file. Counting it would put every highlight after the first numbered list a few characters late.

@genomdev/core/viewer

Classes

DocumentSearch
class DocumentSearch
content
ContentDocument
highlights
HighlightRegistry
hits
readonly SearchHit[]
currentIndex
number
Index of the hit currently marked as active, or -1.
find
(query: string, options?: SearchOptions) => readonly SearchHit[]
Finds every occurrence and highlights all of them. All of them, not the visible ones: a highlight is a registered intention, so the four hundredth hit is already painted by the time the reader scrolls to it. That is what makes "next" instant on a long document.
next
() => number
Moves to the next hit, wrapping. Returns its index, or -1.
previous
() => number
clear
() => void
show
(selectors: readonly Selector[], options?: HighlightOptions) => HighlightHandle
Highlights a passage identified by stored selectors, and scrolls to it. The other half of the loop: a chunk extracted months ago, embedded, stored, retrieved by a question, and now shown in the document it came from.
EventBus
class EventBus<Events extends Record<string, unknown> = ViewerEventMap>

A minimal typed emitter. Handlers are copied before dispatch: a handler that unsubscribes itself — which is what "once" looks like, and what a React effect does on unmount — must not shorten the list being walked.

on
<K extends keyof Events & string>(event: K, handler: Handler<Events[K]>) => () => void
once
<K extends keyof Events & string>(event: K, handler: Handler<Events[K]>) => () => void
emit
<K extends keyof Events & string>(event: K, payload: Events[K]) => void
clear
() => void
Drops every subscription. Called when the viewer is destroyed.
Viewer
class Viewer
state
ViewerState
container
HTMLElement
view
DocumentView | undefined
The live view, for reaching format-specific APIs such as page navigation.
registry
FormatRegistry
content
(options?: ExtractOptions) => Promise<ContentDocument>
The open document as content: blocks, markdown, chunks, addresses. Built with the walk the format module brought, so this needs no dispatcher and no second parse — and it is the same tree extraction produces on a server, which is why an address minted there resolves here. Memoised: the tree does not change while the document is open.
search
(options?: ExtractOptions) => Promise<DocumentSearch>
Search over the extracted text, with hits highlighted in the document.
on
<K extends keyof ViewerEventMap & string>(event: K, handler: Handler<ViewerEventMap[K]>) => () => void
Subscribes to a named event. Returns an unsubscribe function.
once
<K extends keyof ViewerEventMap & string>(event: K, handler: Handler<ViewerEventMap[K]>) => () => void
emit
<K extends keyof ViewerEventMap & string>(event: K, payload: ViewerEventMap[K]) => void
Emits an event. Renderers and plugins report through here.
subscribe
(listener: (state: ViewerState) => void) => () => void
Subscribes to state as a whole. Kept beside the event bus because a UI framework wants a store rather than a stream: `useSyncExternalStore` needs exactly this shape.
open
(input: ByteSourceInput | GenomDocument, options?: ViewerOptions) => Promise<void>
Opens a file and mounts its view. Calling it again before the previous call settles is correct: the older result is discarded. That is the normal case when a reader picks files in quick succession.
refresh
() => void
Re-renders the current view.
zoom
number
The scale the document is drawn at, and one otherwise. A renderer that cannot scale reports nothing, and a viewer over one reads as being at its natural size — which it is.
setZoom
(zoom: number) => void
Changes the scale the document is drawn at. Here rather than only on the renderer because this is the object an application holds: the React, Vue and Angular wrappers all pass their options in at mount and none of them could reach a zoom control without it, so the only way to change the scale through them was to open the file again. Nothing is laid out again — see `dom/scale.ts` for why a zoom is a transform.
pageCount
number
How many pages, sheets or slides the open document came to.
currentPage
number
The one the reader is looking at, counted from nought.
goToPage
(index: number, behavior?: ScrollBehavior) => void
Scrolls to a page, a sheet or a slide. Here for the same reason as {@link setZoom}: this is the object an application holds, and until it had this the renderers' navigation could not be reached from the React, Vue or Angular wrappers at all.
close
() => void
Closes the document and clears the container.
destroy
() => void
Releases every resource. The instance must not be used afterwards.

Functions

contentOf
function contentOf(document: GenomDocument, walk: Walk, bytes?: Uint8Array | ByteSourceInput, options?: ExtractOptions): Promise<ContentDocument>

Builds the content of a document already open, using its own walk. The walk comes from the format module, which is why this needs no dispatcher and no knowledge of any format: the module that opened the document also knows how to read it out. That is the whole reason a format is one object.

createSearch
function createSearch(container: HTMLElement, content: ContentDocument): Promise<DocumentSearch>

Builds the search index for a document already on screen. The bytes are wanted but not required. With them the addresses carry a hash and a stored anchor from a different file is refused rather than resolved to whatever sits at that path; without them everything still works and simply cannot warn.

createViewer
function createViewer(container: HTMLElement, options?: ViewerOptions): Viewer

Creates a viewer mounted into the given element.

Interfaces

SearchHit
interface SearchHit
text
string
selectors
readonly Selector[]
handle
HighlightHandle
SearchOptions
interface SearchOptions

Search and highlighting, over one mechanism rather than two. The temptation is to write a find box that walks the DOM looking for a string, which every viewer has, and which is wrong here for three reasons: it cannot see the pages that are not mounted, it finds the list numbers and page numbers the renderer computed, and what it produces is a DOM node rather than an address — so a hit cannot be stored, sent anywhere, or found again after a relayout. Instead the search runs over the extracted text, which the extractor can produce in the browser because it depends on nothing that is not there. A hit is a range of that text, which is an address, which is a highlight. The same path a chunk retrieved from a vector database takes. So `find` and `showChunk` are the same operation with different inputs, and neither had to be built twice.

caseSensitive?
boolean | undefined
wholeWord?
boolean | undefined
limit?
number | undefined
Stop after this many hits. Default 1000.
ViewerEventMap
interface ViewerEventMap
status
ViewerStateEvent
Any change of viewer state: status, progress, error.
'document:open'
{ document: GenomDocument; detection: DetectionResult | undefined; }
'document:close'
Record<string, never>
'page:change'
{ index: number; total: number | undefined; }
'zoom:change'
{ zoom: number; }
The zoom changed, whoever changed it. On the bus rather than only in a callback because a gesture changes it: a reader pinching the document is not something the application asked for, and a toolbar showing a stale number is the commonest way a zoom control looks broken.
'selection:change'
{ text: string; }
'link:activate'
{ href: string; event: MouseEvent; }
A link was followed. `preventDefault` on the event stops the navigation.
error
{ error: Error; }
ViewerOptions
interface ViewerOptions extends OpenOptions
formats?
readonly FormatEntry[] | undefined
The formats this viewer can open, eager or lazy. Lazy descriptors are the interesting case: they carry the rules that recognise a file, so the right module is fetched and the others never are.
registry?
FormatRegistry | undefined
A registry built by hand, for an application that shares one.
zoom?
number | undefined
fit?
"none" | "width" | "page" | undefined
initialPage?
number | undefined
options?
Readonly<Record<string, Record<string, unknown>>> | undefined
Renderer options, addressed by format id: `{ xlsx: { formulaBar: false } }`.
decorate?
Readonly<Record<string, Decorator>> | undefined
Decorators, keyed `format:kind` — `xlsx:cell`, `docx:hyperlink`.
plugins?
readonly ViewerPlugin[] | undefined
ViewerPlugin
interface ViewerPlugin

An extension that gets the viewer itself. Returns its own teardown.

name
string
setup
(viewer: Viewer) => (() => void) | void
ViewerState
interface ViewerState
status
ViewerStatus
document
GenomDocument | undefined
detection
DetectionResult | undefined
error
Error | undefined
progress
number
Parse and layout progress, 0..1.
ViewerStateEvent
interface ViewerStateEvent
status
"error" | "idle" | "loading" | "ready"
progress
number
error
Error | undefined

Type aliases

Handler
type Handler<T> = (payload: T) => void
ViewerStatus
type ViewerStatus = 'idle' | 'loading' | 'ready' | 'error'

@genomdev/core/testing

Functions

buildZip
function buildZip(files: readonly ZipFileSpec[], options?: { comment?: string; }): Promise<Uint8Array>
crc32
function crc32(bytes: Uint8Array): number

Interfaces

ZipFileSpec
interface ZipFileSpec

A ZIP archive builder for tests. Simpler and more reliable than keeping binary fixtures in the repository: a test states the compression method, the archive comment and the set of parts itself, and therefore exercises exactly the reader branch it was written for.

name
string
content
string | Uint8Array<ArrayBufferLike>
compress?
boolean | undefined
Content is deflated by default.