Skip to content
Apertura
API reference

@apertura/render

Shared Apertura rendering primitives: measurement units, colours, DOM helpers, base view class

79 exported symbols · 27 declared here · 52 re-exported

Classes

BaseDocumentView
class BaseDocumentView<TDocument extends AperturaDocument = AperturaDocument> 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<"page" | "none" | "width" | 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.
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

Text measurement backed by a canvas, with aggressive caching. The pagination engine needs to know how wide a run is before it exists in the DOM, and doing that by inserting elements and reading `offsetWidth` forces a synchronous layout per measurement — the classic reason document viewers stall on large files. A 2D canvas context measures text without touching layout at all. Two caches sit on top of it: one for font metrics (per font, computed once) and one for measured strings (per font + string). Documents repeat the same words in the same formatting constantly, so the hit rate is high and the measurement cost effectively disappears after the first page.

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.
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.
ViewRegistry
class ViewRegistry extends FormatRegistry

A format registry that also knows how to draw. Parsers are registered in `@apertura/core`, which knows nothing about the browser; renderers are registered here. Subclassing rather than composing keeps the fluent chain a caller expects — `registerParser` returns `this`, so a registry can be built in one expression — while leaving the parser half usable on a server that has no DOM at all.

registerView
<TDocument extends AperturaDocument>(plugin: ViewPlugin<TDocument>) => this
getView
(format: FormatId) => ViewPlugin | undefined
viewableFormats
() => FormatId[]
Formats that have both a parser and a renderer.

Functions

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

Removes every child of a node.

columnWidthToPixelsfrom @apertura/core
function columnWidthToPixels(width: number, maxDigitWidth?: number): number

Excel column width in "characters" converted to pixels. Excel measures width in multiples of the width of the "0" glyph of the Normal style font and adds 5 pixels of cell padding (MS-OI29500, Column Width): `px = trunc(width * mdw) + 5`, where `mdw` is the digit width in pixels (7 for the default Calibri 11pt). This is where Excel's well-known default comes from: 8.43 characters is exactly 64 pixels.

contrastingTextColorfrom @apertura/core
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.

eighthPointsToPointsfrom @apertura/core
function eighthPointsToPoints(eighths: number): number

Eighths of a point: the unit of `w:sz` on border elements.

emuToInchesfrom @apertura/core
function emuToInches(emu: number): number
emuToPixelsfrom @apertura/core
function emuToPixels(emu: number): number
emuToPointsfrom @apertura/core
function emuToPoints(emu: number): number
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.

grayscalefrom @apertura/core
function grayscale(color: Rgba): Rgba

`a:gray`: the colour rendered in shades of grey, by perceived brightness.

halfPointsToPointsfrom @apertura/core
function halfPointsToPoints(halfPoints: number): number

Half-points: the unit Word uses for font sizes (`w:sz w:val="24"` is 12pt).

hslToRgbfrom @apertura/core
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.

invertfrom @apertura/core
function invert(color: Rgba): Rgba

`a:inv`: every channel inverted.

lengthToCssfrom @apertura/core
function lengthToCss(length: Length | undefined): string | undefined

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

lengthToPixelsfrom @apertura/core
function lengthToPixels(length: Length | undefined, reference?: number): number

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

modulateHuefrom @apertura/core
function modulateHue(color: Rgba, hueMod?: number, hueOff?: number): Rgba

Applies the `hueMod`/`hueOff` hue rotation of DrawingML; both wrap.

modulateLuminancefrom @apertura/core
function modulateLuminance(color: Rgba, lumMod?: number, lumOff?: number): Rgba

Applies the `lumMod`/`lumOff` luminance modulation used by DrawingML themes. Word writes theme colour variations this way, e.g. "Accent 1, lighter 40%" becomes `lumMod 60000` + `lumOff 40000` (values are thousandths of a percent).

modulateSaturationfrom @apertura/core
function modulateSaturation(color: Rgba, satMod?: number, satOff?: number): Rgba

Applies the `satMod`/`satOff` saturation modulation of DrawingML. The other half of the pair Office writes for a theme variation. Every theme Word ships states its fills as a scheme colour with both a luminance and a saturation modifier on it — a heading colour is `accent1` at 110% saturation and 75% luminance — and applying only the first paints a colour that is the right lightness and visibly the wrong intensity.

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.

parseHexColorfrom @apertura/core
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).

percentfrom @apertura/core
function percent(value: number): Length
pixelsToColumnWidthfrom @apertura/core
function pixelsToColumnWidth(pixels: number, maxDigitWidth?: number): number

Inverse of {@link columnWidthToPixels}.

pixelsToPointsfrom @apertura/core
function pixelsToPoints(pixels: number): number
pixelsToTwipsfrom @apertura/core
function pixelsToTwips(pixels: number): number
pointsfrom @apertura/core
function points(value: number): Length
pointsToEmufrom @apertura/core
function pointsToEmu(points: number): number
pointsToPixelsfrom @apertura/core
function pointsToPixels(points: number): number
presetGeometryPathfrom @apertura/core
function presetGeometryPath(geometry: PresetGeometry): string | undefined

Builds the outline of a preset shape.

ptfrom @apertura/core
function pt(value: number): string

Formats a value as a CSS point string, rounded to two decimals.

pxfrom @apertura/core
function px(value: number): string

Formats a value as a CSS pixel string, rounded to two decimals.

rectanglePathfrom @apertura/core
function rectanglePath(width: number, height: number): string

An axis-aligned rectangle path, the fallback for an unknown preset.

relativeLuminancefrom @apertura/core
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.

rgbToHslfrom @apertura/core
function rgbToHsl(color: Rgba): Hsl
rowHeightToPixelsfrom @apertura/core
function rowHeightToPixels(heightInPoints: number): number

Excel row heights are expressed in points.

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.

shadefrom @apertura/core
function shade(color: Rgba, amount: number): Rgba

Darkening (`shade`): mixes towards black.

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.

tintfrom @apertura/core
function tint(color: Rgba, amount: number): Rgba

Lightening (`tint` in DrawingML): mixes towards white.

toCssColorfrom @apertura/core
function toCssColor(color: Rgba | undefined): string | undefined
toKebabCase
function toKebabCase(property: string): string
twipsfrom @apertura/core
function twips(value: number): Length
twipsToPixelsfrom @apertura/core
function twipsToPixels(twips: number): number
twipsToPointsfrom @apertura/core
function twipsToPoints(twips: number): number

Interfaces

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.
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
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.
Hslfrom @apertura/core
interface Hsl
h
number
0..1
s
number
0..1
l
number
0..1
Lengthfrom @apertura/core
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
"pt" | "px" | "percent" | "auto"
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.
PresetGeometryfrom @apertura/core
interface PresetGeometry

Everything a preset needs to produce its path.

preset
string
`a:prstGeom/@prst`.
width
number
height
number
adjust?
ReadonlyMap<string, number> | undefined
`a:avLst` values by name, in their raw units.
Rgbafrom @apertura/core
interface Rgba

Colour handling for office formats. OOXML expresses colour in three different ways: a direct RGB value (`FF0000`), a reference to a theme colour (`accent1`), and modifiers applied on top of a theme colour (`lumMod`, `tint`, `shade`). This module holds the conversions that are common to every format; resolving theme references stays in the format packages, which are the ones with access to `theme1.xml`.

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.
ViewOptions
interface ViewOptions
zoom?
number | undefined
Rendering scale, 1 means 100%.
fit?
"page" | "none" | "width" | undefined
How the page should be fitted into the container.
initialPage?
number | undefined
Initial page/sheet/slide, zero-based.
signal?
AbortSignal | undefined
ViewPlugin
interface ViewPlugin<TDocument extends AperturaDocument = AperturaDocument>

A renderer plugin: turns a parsed document into DOM.

format
FormatId
mount
(document: TDocument, container: HTMLElement, options?: ViewOptions) => Promise<DocumentView>

Values

AUTO_LENGTHfrom @apertura/core
AUTO_LENGTH: Length
BLACKfrom @apertura/core
BLACK: Rgba
defaultViewRegistry
defaultViewRegistry: ViewRegistry

Shared registry for applications that do not need isolation.

EIGHTHS_PER_POINTfrom @apertura/core
EIGHTHS_PER_POINT: 8

Eighths of a point: the unit of border widths in WordprocessingML.

EMU_PER_CMfrom @apertura/core
EMU_PER_CM: 360000

EMUs per centimetre.

EMU_PER_INCHfrom @apertura/core
EMU_PER_INCH: 914400

English Metric Units: 914400 per inch. The base unit of DrawingML.

EMU_PER_POINTfrom @apertura/core
EMU_PER_POINT: 12700

EMUs per point: 914400 / 72.

HIGHLIGHT_COLORSfrom @apertura/core
HIGHLIGHT_COLORS: Readonly<Record<string, string>>

Named `ST_HighlightColor` values from WordprocessingML.

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_INCHfrom @apertura/core
PX_PER_INCH: 96

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

PX_PER_POINTfrom @apertura/core
PX_PER_POINT: number
TWIPS_PER_INCHfrom @apertura/core
TWIPS_PER_INCH: 1440

Twentieths of a point: 1440 per inch. The unit of WordprocessingML.

TWIPS_PER_POINTfrom @apertura/core
TWIPS_PER_POINT: 20
WHITEfrom @apertura/core
WHITE: Rgba