Skip to Content

@stesura/core/utils

Framework-free helpers a host needs: unit conversion, exact line heights, URL safety, upload validation and randomUUID. No ProseMirror dependency.

Functions & values

cmToInches

Parameters:
NameTypeDefault
cmstring | number
Returns:
number | undefined

cmToPoints

Rounded to whole points.

Parameters:
NameTypeDefault
cmstring | number
Returns:
number | undefined

cmToPx

Exact, quantized (quantizePx).

Parameters:
NameTypeDefault
cmnumber
Returns:
number

cmToTwips

Rounded to whole twips.

Parameters:
NameTypeDefault
cmstring | number
Returns:
number | undefined

cssUnitToPixels

A CSS length in px. baseSize (px) resolves em and %; rem is fixed at 16px and viewport units assume 1920×1080. Unitless or unknown units count as px; unparseable input returns baseSize.

Parameters:
NameTypeDefault
valuestring | number
baseSizenumber
Returns:
number

cssUnitToPoints

A CSS length in points, snapped to 0.5pt. Handles px, pt, cm, mm and in; anything else counts as px, and unparseable input returns 10.

Parameters:
NameTypeDefault
valuestring | number
Returns:
number

DISPLAY_UNIT_LABELS

The abbreviation shown beside a display-unit value. Not localised: these are the SI and typographic symbols, which do not translate.

NameTypeDefault
cmstring
inchesstring
ptstring
pxstring

displayUnitToPx

A value in the user’s display unit, as quantized px: 1.25cm stores 47.24409 and exports as Word’s 709 twips. Editing surfaces store px and show displayUnits, so the pair lives here rather than in each picker.

Parameters:
NameTypeDefault
unitDisplayUnit
valuenumber
Returns:
number

diffLayoutManifests

How two layouts differ: the pages that start on a different block (a page only one of them has counts too) and the first of them, 0-based; null when they match.

Parameters:
NameTypeDefault
expectedExportLayoutManifest
actualExportLayoutManifest
Returns:
{ pages: number; firstPage: number; } | null

EXPORT_LOSSES_HEADER

The PDF response header carrying ExportLosses: the render succeeds without what it couldn’t load, and the exporting user should know.

NameTypeDefault
toString() => string
charAt(pos: number) => string
charCodeAt(index: number) => number
concat(...strings: string[]) => string
indexOf(searchString: string, position?: number | undefined) => number
lastIndexOf(searchString: string, position?: number | undefined) => number
localeCompare{ (that: string): number; (that: string, locales?: string | string[] | undefined, options?: CollatorOptions | undefined): number; (that: string, locales?: LocalesArgument, options?: CollatorOptions | undefined): number; }
match{ (regexp: string | RegExp): RegExpMatchArray | null; (matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null; }
replace{ (searchValue: string | RegExp, replaceValue: string): string; (searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string; (searchValue: { ...; }, replaceValue: string): string; (searchValue: { ...; }, replacer: (substring: string, ...args: any[]) => string): string; }
slice(start?: number | undefined, end?: number | undefined) => string
split{ (separator: string | RegExp, limit?: number | undefined): string[]; (splitter: { [Symbol.split](string: string, limit?: number | undefined): string[]; }, limit?: number | undefined): string[]; }
substring(start: number, end?: number | undefined) => string
toLowerCase() => string
toLocaleLowerCase{ (locales?: string | string[] | undefined): string; (locales?: LocalesArgument): string; }
toUpperCase() => string
toLocaleUpperCase{ (locales?: string | string[] | undefined): string; (locales?: LocalesArgument): string; }
trim() => string
lengthnumber
substr(from: number, length?: number | undefined) => string
valueOf() => string
codePointAt(pos: number) => number | undefined
includes(searchString: string, position?: number | undefined) => boolean
endsWith(searchString: string, endPosition?: number | undefined) => boolean
normalize{ (form: "NFC" | "NFD" | "NFKC" | "NFKD"): string; (form?: string | undefined): string; }
repeat(count: number) => string
startsWith(searchString: string, position?: number | undefined) => boolean
anchor(name: string) => string
big() => string
bold() => string
fixed() => string
fontcolor(color: string) => string
fontsize{ (size: number): string; (size: string): string; }
italics() => string
small() => string
strike() => string
sub() => string
sup() => string
padStart(maxLength: number, fillString?: string | undefined) => string
padEnd(maxLength: number, fillString?: string | undefined) => string
trimEnd() => string
trimStart() => string
trimLeft() => string
trimRight() => string
matchAll(regexp: RegExp) => RegExpStringIterator<RegExpExecArray>
replaceAll{ (searchValue: string | RegExp, replaceValue: string): string; (searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string; }
at(index: number) => string | undefined
__@iterator@6565() => StringIterator<string>

formatDisplayUnit

How a display unit’s value is rendered in an input: points to 1dp, else 2dp.

Parameters:
NameTypeDefault
valuenumber
unitDisplayUnit
Returns:
string

formatExportLosses

images=2, files=0, glyphs=0, layout=0, or null when nothing was lost.

Parameters:
NameTypeDefault
lossesExportLosses
Returns:
string | null

inchesToCm

Parameters:
NameTypeDefault
inchesstring | number
Returns:
number | undefined

inchesToPoints

Rounded to whole points.

Parameters:
NameTypeDefault
inchesstring | number
Returns:
number | undefined

inchesToPx

Exact, quantized (quantizePx).

Parameters:
NameTypeDefault
inchesnumber
Returns:
number

inchesToTwips

Rounded to whole twips.

Parameters:
NameTypeDefault
inchesstring | number
Returns:
number | undefined

isEmbedUrlAllowed

Whether the host may fetch or frame this URL.

allowedHosts undefined → allowed unless isForbiddenEmbedUrl says otherwise; defined → the URL’s host (including a non-default port) must also appear in the list. The never-allow class applies either way, so an allowlist can only narrow, never widen.

Parameters:
NameTypeDefault
urlstring
allowedHostsstring[]
Returns:
boolean

isForbiddenEmbedUrl

Whether a URL is in the never-allow class: no configuration may permit it.

Covers non-http(s) schemes, loopback, RFC1918 private ranges, link-local (including the cloud-metadata endpoint), CGNAT, IPv6 ULA, .local / .internal hostnames, and anything new URL cannot parse. Unparseable input is forbidden rather than passed through — a protocol-relative //host/x has no origin to judge.

Parameters:
NameTypeDefault
urlstring
Returns:
boolean

lineHeightsMatch

Parameters:
NameTypeDefault
anumber
bnumber
Returns:
boolean

parseExportLosses

The losses a EXPORT_LOSSES_HEADER value names; null when it names none.

Parameters:
NameTypeDefault
Returns:
ExportLosses | null

parsePtLineHeight

The pt number of an exact "Xpt" line height; null for any other shape.

Parameters:
NameTypeDefault
valueunknown
Returns:
number | null

pointToCm

Parameters:
NameTypeDefault
ptstring | number
Returns:
number | undefined

pointToInches

Parameters:
NameTypeDefault
ptstring | number
Returns:
number | undefined

pointToPx

Exact, quantized (quantizePx).

Parameters:
NameTypeDefault
ptnumber
Returns:
number

pointToTwips

Rounded to whole twips.

Parameters:
NameTypeDefault
ptstring | number
Returns:
number | undefined

ptToPxExact

Exact line height in px. Unrounded on purpose: the measurement must match what the browser lays out from the same CSS value, and rounding 13pt to 17px would drift by a third of a px per line.

Parameters:
NameTypeDefault
ptnumber
Returns:
number

pxToCm

Parameters:
NameTypeDefault
pxstring | number
Returns:
number | undefined

pxToDisplayUnit

Inverse of displayUnitToPx; pt keeps a decimal (Word shows 7.5 pt).

Parameters:
NameTypeDefault
unitDisplayUnit
valuenumber
Returns:
number

pxToInches

Parameters:
NameTypeDefault
pxstring | number
Returns:
number | undefined

pxToPoints

Rounded to whole points.

Parameters:
NameTypeDefault
pxstring | number
Returns:
number | undefined

pxToTwips

Rounded to whole twips: the one rounding a stored px length gets, at export.

Parameters:
NameTypeDefault
pixelsnumber
Returns:
number

randomUUID

crypto.randomUUID() with a getRandomValues fallback.

randomUUID is secure-context-only: on a plain-HTTP deploy (intranet tooling, LAN preview) it is undefined and every id-minting path — new sections, tables, numbering instances, the uniqueId plugin — would crash the editor outright. getRandomValues has no such restriction, so the fallback builds a standard v4 UUID from it with the same entropy.

Parameters:
This function does not accept any parameters.
Returns:
string

safeNormalizeUrl

Canonicalizes a URL (forcing https unless told otherwise) and strips dangerous schemes; null when nothing safe is left. For every path that renders or follows a user-supplied URL. Refuses everything safeRawUrl refuses.

Document-relative URLs are kept verbatim: normalizeUrl has no base to resolve them against and throws, and resolving against our own origin would turn a pasted #section into a link to our host.

Parameters:
NameTypeDefault
urlstring
__1SafeNormalizeUrlOptions
Returns:
string | null

safeRawUrl

Null for dangerous schemes (javascript:, vbscript:, data:, file:) and non-strings; otherwise the URL verbatim — for storage paths that must round-trip exactly (e.g. markdown import; sanitizeUrl would canonicalize via new URL()). Render paths still apply safeNormalizeUrl.

Parameters:
NameTypeDefault
urlstring
Returns:
string | null

snapToHalfPoint

Round a point value to the nearest 0.5 (e.g. 10.7 → 10.5, 10.8 → 11, 10.2 → 10).

Parameters:
NameTypeDefault
ptnumber
Returns:
number

sniffDocumentType

Identify a document upload by content. The magic bytes decide the CLASS — PDF, ZIP container, or plain text — and nothing outside those three is accepted. Within the ZIP class the name’s extension picks the Office type: telling docx from xlsx by content means reading the archive’s central directory, and the distinction only ever changes a label and a storage Content-Type that no browser executes. A ZIP under any other name is served as application/zip. Text is served as text/plain whatever the extension says, so a .html upload can never come back as a page.

Parameters:
NameTypeDefault
fileBlob
namestring
Returns:
Promise<SniffedDocumentType | null>

sniffImageBytes

Identify an image by its magic bytes. Needs at least the first 12 bytes; returns null for anything that isn’t a supported raster format.

Parameters:
NameTypeDefault
bytesUint8Array<ArrayBufferLike>
Returns:
SniffedImageType | null

sniffImageType

Identify a supported image File/Blob by its magic bytes.

Parameters:
NameTypeDefault
fileBlob
Returns:
Promise<SniffedImageType | null>

sniffPdf

True if the blob starts with the %PDF- signature.

Parameters:
NameTypeDefault
fileBlob
Returns:
Promise<boolean>

toLineHeightAtLeast

A lineHeightAtLeast value (types/formatting LineHeightAtLeast), or null for anything else. JSON, collab and the clipboard reach the attr unchecked.

Parameters:
NameTypeDefault
valueunknown
Returns:
{ pt: number; derived: number; } | null

twipsToCm

Parameters:
NameTypeDefault
twipsstring | number
Returns:
number | undefined

twipsToInches

Parameters:
NameTypeDefault
twipsstring | number
Returns:
number | undefined

twipsToPt

Parameters:
NameTypeDefault
twipsstring | number
Returns:
number | undefined

twipsToPx

Exact, quantized (quantizePx).

Parameters:
NameTypeDefault
twipsnumber
Returns:
number

validateDocumentFile

Validate a candidate document upload: size cap plus content class. On success returns the content type to store the file under — never file.type.

Parameters:
NameTypeDefault
fileBlob
options{ maxBytes?: number; name?: string; }
Returns:
Promise<FileValidationResult<SniffedDocumentType>>

validateImageFile

Validate a candidate image upload: size cap plus magic-byte sniff. On success returns the sniffed content type — callers must use it (not file.type) for storage Content-Type and any branching.

Parameters:
NameTypeDefault
fileBlob
options{ maxBytes?: number; }
Returns:
Promise<FileValidationResult<SniffedImageType>>

validatePdfFile

Validate a candidate PDF upload: size cap plus %PDF- signature.

Parameters:
NameTypeDefault
fileBlob
options{ maxBytes?: number; }
Returns:
Promise<FileValidationResult<"application/pdf">>

Types

FileValidationResult

NameTypeDefault
okboolean

Other exports

ExportTypeDescription
LINE_HEIGHT_PT_MAX600
LINE_HEIGHT_PT_MIN0.7Exact line-height bounds (pt); the min is Word’s own.
LINE_HEIGHT_MULTIPLIER_MIN0.06Multiplier bounds; the min is Word’s floor for “Multiple”.
LINE_HEIGHT_MULTIPLIER_MAX5Beyond it a value is corrupt, not a layout.
MAX_DOCUMENT_BYTESnumberSize cap for a document upload (the file node).
MAX_IMAGE_BYTESnumberDefault upload size cap, aligned with the upload-block dropzone.
SNIFFED_IMAGE_TYPESreadonly SniffedImageType[]Every image type the sniffer recognises; the default allowedImageTypes.
SniffedDocumentType"application/pdf" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/vnd.openxmlformats-officedocument.presentationml.presentation" | "application/zip" | "text/plain"
SniffedImageType"image/png" | "image/jpeg" | "image/gif" | "image/webp"

Text folding

foldAccents(text) folds a string’s accents code point by code point (é → e, a combining mark dropped), keeping offsets one to one; foldCase(codePoint) is the simple case fold the iu regexp flag compares by. Search, the approximate matcher and redaction’s leak check share them.

Last updated on