Skip to content
Apertura
API reference

@apertura/render

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

68 exported symbols · 68 declared here · 0 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<"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.
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>) => 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.

Functions

clearChildren
function clearChildren(node: Node): void

Removes every child of a node.

columnWidthToPixels
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.

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.

eighthPointsToPoints
function eighthPointsToPoints(eighths: number): number

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

emuToInches
function emuToInches(emu: number): number
emuToPixels
function emuToPixels(emu: number): number
emuToPoints
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.

grayscale
function grayscale(color: Rgba): Rgba

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

halfPointsToPoints
function halfPointsToPoints(halfPoints: number): number

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

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.

invert
function invert(color: Rgba): Rgba

`a:inv`: every channel inverted.

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.

modulateHue
function modulateHue(color: Rgba, hueMod?: number, hueOff?: number): Rgba

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

modulateLuminance
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).

modulateSaturation
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.

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
pixelsToColumnWidth
function pixelsToColumnWidth(pixels: number, maxDigitWidth?: number): number

Inverse of {@link columnWidthToPixels}.

pixelsToPoints
function pixelsToPoints(pixels: number): number
pixelsToTwips
function pixelsToTwips(pixels: number): number
points
function points(value: number): Length
pointsToEmu
function pointsToEmu(points: number): number
pointsToPixels
function pointsToPixels(points: number): number
presetGeometryPath
function presetGeometryPath(geometry: PresetGeometry): string | undefined

Builds the outline of a preset shape.

pt
function pt(value: number): string

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

px
function px(value: number): string

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

rectanglePath
function rectanglePath(width: number, height: number): string

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

relativeLuminance
function relativeLuminance(color: Rgba): number

WCAG 2.1 relative luminance, 0..1.

rgbToHsl
function rgbToHsl(color: Rgba): Hsl
rowHeightToPixels
function rowHeightToPixels(heightInPoints: number): number

Excel row heights are expressed in points.

shade
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.

tint
function tint(color: Rgba, amount: number): Rgba

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

toCssColor
function toCssColor(color: Rgba | undefined): string | undefined
toKebabCase
function toKebabCase(property: string): string
twips
function twips(value: number): Length
twipsToPixels
function twipsToPixels(twips: number): number
twipsToPoints
function twipsToPoints(twips: number): number

Interfaces

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.
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.
PresetGeometry
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.
Rgba
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

Values

AUTO_LENGTH
AUTO_LENGTH: Length
BLACK
BLACK: Rgba
EIGHTHS_PER_POINT
EIGHTHS_PER_POINT: 8

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

EMU_PER_CM
EMU_PER_CM: 360000

EMUs per centimetre.

EMU_PER_INCH
EMU_PER_INCH: 914400

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

EMU_PER_POINT
EMU_PER_POINT: 12700

EMUs per point: 914400 / 72.

HIGHLIGHT_COLORS
HIGHLIGHT_COLORS: Readonly<Record<string, string>>

Named `ST_HighlightColor` values from WordprocessingML.

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
TWIPS_PER_INCH
TWIPS_PER_INCH: 1440

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

TWIPS_PER_POINT
TWIPS_PER_POINT: 20
WHITE
WHITE: Rgba