Skip to content
D
Documentation

functions b–s

reference
10 min readUpdated

Import these from @grafloria/renderer.

Functions

base64ToBytes

Pure base64 → bytes. (No atob: it does not exist in Node, and this must stay DOM-free.)

ts
function base64ToBytes(base64: string): Uint8Array

buildPrintDocument

Build a printable HTML document from already-exported SVG pages.

PURE — it returns a string. That is deliberate: it keeps the page-building testable, and leaves the one genuinely browser-side act (opening a print dialog) to printDocument.

The page-break-after on every sheet but the last is what makes the browser emit one physical page per tile instead of reflowing them all into a single column.

ts
function buildPrintDocument(svgPages: string[], options: PrintOptions = {}): string

bytesToBase64

Pure bytes → base64.

ts
function bytesToBase64(bytes: Uint8Array): string

bytesToDataUrl

bytes → data:<mime>;base64,…

ts
function bytesToDataUrl(bytes: Uint8Array, mimeType: string): string

canRasterizeInThisEnvironment

Is there a canvas we can draw into? (browser main thread or worker)

ts
function canRasterizeInThisEnvironment(): boolean

captureCustomNodeHost

Capture ONE custom node's host into plain data.

Never throws: an export must not be taken down by a host in a state we did not anticipate. A failed capture degrades to fidelity: 'empty', which the pure layer turns into a marked box and a warning — the whole point being that a blank is never silent.

ts
function captureCustomNodeHost(
  id: string,
  rect: Rectangle,
  host: unknown,
  options: CaptureHostOptions = {}
): CustomNodeCapture

captureCustomNodes

THE SYNCHRONOUS CAPTURE — materialize → read → restore, with no suspension point.

The DOM read happens HERE, once, and produces plain data — exportSvg stays pure, DOM-free and deterministic. A painter that defers its paint has not drawn anything by the time this looks; paintWarning says so rather than exporting a silent blank.

ts
function captureCustomNodes(
  source: CustomNodeHostSource,
  needed: (node: NodeModel) => boolean = () => true
): CustomNodeCapture[]

captureCustomNodesAsync

THE ASYNC CAPTURE — the same boundary, allowed to wait for a painter that said it was not finished.

THE SIGNAL IS THE PROMISE (pendingPaint), and nothing else — never a fixed sleep. timeoutMs is a safety net: on expiry the export takes the host as it stands and paintWarning(…, waited = true) reports it. If nothing in scope is pending, this runs materialize → read → restore with no suspension point at all, i.e. the identical sequence {@link captureCustomNodes} performs, so an all-sync board exports the same bytes through both paths.

ts
async function captureCustomNodesAsync(
  source: CustomNodeHostSource,
  needed: (node: NodeModel) => boolean,
  timeoutMs: number
): Promise<CustomNodeCapture[]>

clampOutputSize

Clamp an export's PIXEL size.

A 3x export of a big diagram is how you ask a browser for a 30000 × 20000 canvas and get back a blank image — canvas has a hard area/side limit (~16k on most engines, less on Safari/mobile), and it fails SILENTLY: toDataURL hands you a blank or throws deep inside the driver. So we cap the output and REDUCE THE SCALE to fit, rather than emitting a request we know will fail.

Reducing scale (not cropping) is the right lever: it keeps the whole picture and only spends fewer pixels on it, which is exactly the trade a caller who asked for "3x, and it must fit" wants.

minSize floors the result so a tiny diagram still yields a usable image rather than a 12 × 8 sliver.

ts
function clampOutputSize(
  width: number,
  height: number,
  requestedScale: number,
  maxSize: number = DEFAULT_MAX_OUTPUT_SIZE,
  minSize = 1
): ClampResult

collectAssetUrls

Every external asset URL the tree references. PURE.

Deduplicated and returned in a STABLE order (first appearance), so a caller that fetches them and re-inlines gets the same bytes for the same diagram every time — determinism runs all the way through this module.

ts
function collectAssetUrls(root: VNode): string[]

computeBreaks

Choose the break positions along ONE axis.

Walks left to right. At each naive break (origin + pageSize) it looks for boxes the break would slice; if there are any, it tries pulling the break back to the leftmost slicee's leading edge. That move is only taken when the page it leaves is still at least 1 - tolerance full — otherwise we would trade one cut node for a page count explosion.

ts
function computeBreaks(
  start: number,
  end: number,
  pageSize: number,
  boxes: Box[],
  options: { snap: boolean; tolerance: number; overlap: number; warnings: string[]; axis: 'x' | 'y' }
): number[]

crc32

ts
function crc32(bytes: Uint8Array): number

createClassStyleResolver

Build the resolver for a theme. Compiles BASE_STYLE_RULES once (var refs resolved against the theme's variable values), then matches class lists against it.

ts
function createClassStyleResolver(theme: Theme, warnings: string[] = []): ClassStyleResolver

Parameters

  • theme: the theme whose --grafloria-* values get baked in
  • warnings: collector — a declaration whose variable cannot be resolved is dropped and reported here rather than silently emitting var(--…).

createCustomNodeCapturer

Bind the capture to one canvas's hosts.

ONE async capture at a time per canvas: two exports in flight would otherwise interleave their materialize/restore pairs — the first's restore tearing down a host the second is still waiting to read.

ts
function createCustomNodeCapturer(source: CustomNodeHostSource): CustomNodeCapturer

createDomRasterBackend

The zero-dependency browser backend: draw the SVG into a canvas and read the pixels back out.

Prefers OffscreenCanvas when present (so it works in a worker, off the main thread) and falls back to a detached <canvas>.

ts
function createDomRasterBackend(): RasterBackend

createExportPipeline

Bind the whole pipeline to one renderer and one canvas's custom-node hosts.

ts
function createExportPipeline(
  renderer: PipelineRenderer,
  source: CustomNodeHostSource
): ExportPipeline

createResvgBackend

A PNG rasterizer backed by resvg. PNG only — resvg has no JPEG or WebP encoder, and pretending otherwise would mean handing a caller PNG bytes under a image/jpeg mime type, which is the kind of quiet lie this seam exists to prevent.

fitTo: width is what applies the export's scale: the SVG carries the picture, and resvg renders it at the pixel width we ask for.

ts
function createResvgBackend(resvg: ResvgModule): RasterBackend

createSharpBackend

A rasterizer backed by sharp (libvips + librsvg). Produces all three raster formats.

The SVG goes in as BYTES, not as a path: sharp reads an SVG buffer through librsvg. density is how sharp scales vector input — 72 is the 1:1 baseline, so we scale the DPI by the ratio of the target pixel width to the SVG's intrinsic width and let librsvg rasterize at that resolution rather than upscaling a small bitmap.

ts
function createSharpBackend(sharp: SharpModule): RasterBackend

customNodeBounds

The union of the captures' world rects.

Needed because a lifted chart is a <g transform> full of nested geometry and a foreignObject's content is opaque — so vnodeBounds alone can under-measure a board. On an all-custom-node dashboard it would find NOTHING and fit the file to a 40px square. The node rects are the truth about where the widgets are, so the box is fitted to those as well.

ts
function customNodeBounds(
  captures: readonly CustomNodeCapture[],
  includeIds?: ReadonlySet<string>
): Rectangle | null

customNodeVNodes

Turn captures into VNodes ready to append to the exported tree.

Pure: same captures in, same VNodes and same warnings out. Order follows the input, which the boundary builds from the model's node order — so an export is byte-stable.

ts
function customNodeVNodes(
  captures: readonly CustomNodeCapture[],
  options: CustomNodeOptions = {}
): { nodes: VNode[]; warnings: string[] }

dataUrlToBytes

data:image/png;base64,… → the bytes. Throws on a URL that is not base64 data.

ts
function dataUrlToBytes(dataUrl: string): Uint8Array

embedModelInPng

Insert the model into a PNG as an iTXt chunk, immediately before IEND.

The pixels are untouched: the result is the same image, and any decoder that does not know the chunk simply skips it (text chunks are ancillary by definition).

ts
function embedModelInPng(png: Uint8Array, envelope: DiagramDocumentEnvelope): Uint8Array

embedModelInSvg

Insert the model into an SVG document, right after the root <svg …> tag.

The JSON is XML-escaped text, not base64 — it stays greppable and diffable, and the envelope's checksum catches any tool that mangles it on the way through.

ts
function embedModelInSvg(svg: string, envelope: DiagramDocumentEnvelope): string

escapeAttr

XML-escape an attribute value.

ts
function escapeAttr(value: string): string

escapeText

XML-escape text content.

ts
function escapeText(value: string): string

exportBatch

Export many documents. Never throws for a job-level failure — a failed job comes back with error set and the batch keeps going.

Results are returned IN INPUT ORDER regardless of the order they finish in, because a caller zipping results back onto its own list should not have to think about the pool.

ts
async function exportBatch(jobs: BatchJob[], options: BatchOptions = {}): Promise<BatchResult[]>

exportScopeFilter

Which nodes this export will actually contain.

Materializing is the expensive half — it runs a painter — so it is bounded by the export's own scope rather than mounting a 300-widget board to capture the three widgets includeIds asked for. The predicates mirror what the renderer resolves ids to (SVGRenderer.selectedIds reads exactly this state.selected), so what is mounted and what survives filterCaptures are the same set.

ts
function exportScopeFilter(exportOptions?: ExportOptions): (node: NodeModel) => boolean

exportSvg

Serialize a rendered VNode tree to a standalone SVG string.

ts
function exportSvg(root: VNode, options: SvgExportOptions = {}): SvgExportResult

Parameters

  • root: the root <svg> VNode from SVGRenderer.render(viewport, zoom)

extractModel

Find the embedded model in ANY exported artifact — SVG text, a data: URL of either kind, or PNG bytes. null when the artifact carries no model (a plain image is not an error; it is simply not editable).

ts
function extractModel(artifact: Artifact): DiagramDocumentEnvelope | null

extractModelFromPng

Pull the model back out of a PNG. null when the image carries none.

Reads BOTH iTXt (what we write) and tEXt (so a file produced by another tool, or an older writer, still opens).

ts
function extractModelFromPng(png: Uint8Array): DiagramDocumentEnvelope | null

extractModelFromSvg

Pull the model back out of an SVG. null when there is none — a plain SVG is not an error, it is just not editable.

THROWS if a model IS present but does not parse: a corrupted payload must not silently degrade into "no model", which would look to the user like a successful import of an empty diagram.

ts
function extractModelFromSvg(svg: string): DiagramDocumentEnvelope | null

fetchAssetsTiered

Fetch every URL through the tiers above. Deduplicated: a URL is fetched once no matter how many elements reference it. Resolves when the last URL settles; never rejects.

ts
async function fetchAssetsTiered(
  urls: readonly string[],
  options: TieredFetchOptions = {}
): Promise<TieredFetchResult>

fetchFont

Fetch a font and turn it into a {@link FontSource}, ready for {@link fontFaceCss}.

ts
async function fetchFont(
  url: string,
  descriptor: Omit<FontSource, 'data' | 'format'> & { format?: FontFormat },
  options: ResolveAssetsOptions = {}
): Promise<FontSource>

filterCaptures

Keep only the captures whose node id is in ids — the includeIds scoping rule.

ts
function filterCaptures(
  captures: readonly CustomNodeCapture[],
  ids: Iterable<string> | undefined
): readonly CustomNodeCapture[]

filterTreeByIds

Prune a rendered tree down to the given node/link ids.

Returns a new tree; the input is not mutated (the caller's tree is the live render, and mutating it would corrupt the next frame).

ts
function filterTreeByIds(root: VNode, ids: Iterable<string>): VNode

fontFaceCss

Build the @font-face CSS that makes an export carry its own glyphs.

PURE — bytes in, CSS out. Hand the result to SvgExportOptions.embedFontCss and the file renders identically on a machine that has never heard of the typeface.

The format('…') hint is not decoration: without it a renderer must sniff the bytes, and some (notably older librsvg) simply decline and fall back.

ts
function fontFaceCss(fonts: FontSource[]): string

fontFormatFromUrl

woff2 from a .woff2 URL, etc. Defaults to woff2 — by far the most common on the web.

ts
function fontFormatFromUrl(url: string): FontFormat

importDiagram

Re-open an exported artifact as a live diagram.

The rehydration runs through the ENGINE's own DiagramSerializer.deserialize, which unwraps the envelope, VERIFIES the checksum (throwing on a mismatch), and runs the schema migrations. So an artifact exported by an older build opens in a newer one, and a corrupted one refuses to open rather than opening subtly wrong.

Returns null for an artifact with no embedded model.

ts
function importDiagram(artifact: Artifact, options?: DiagramLoadOptions): DiagramModel | null

inlineAssets

Replace external asset URLs with the supplied data: URIs. PURE — returns a new tree.

A URL with no entry in the map is LEFT ALONE rather than blanked: a broken-but-present reference is debuggable, and an element silently stripped of its href is not. The async layer reports those as warnings.

ts
function inlineAssets(root: VNode, byUrl: ReadonlyMap<string, string>): VNode

isEditableArtifact

Does this artifact carry a model we could re-open?

ts
function isEditableArtifact(artifact: Artifact): boolean

isExternalUrl

Is this a reference that has to leave the document to resolve?

ts
function isExternalUrl(value: unknown): value is string

loadNodeRasterBackend

Find a rasterizer in this Node process.

The imports are DYNAMIC and the specifiers are built at runtime, so a bundler cannot statically resolve them and will not try to pull a native module into a browser bundle (which is how an optional native dep usually breaks a web build).

ts
async function loadNodeRasterBackend(format: 'png' | 'jpeg' | 'webp' = 'png'): Promise<RasterBackend>

Parameters

  • format: the format you intend to produce — only sharp can do the lossy ones, so asking for jpeg/webp will not hand you a resvg backend that would then throw.

mimeTypeForFormat

'png' → 'image/png'. Throws for a format that is not a raster format.

ts
function mimeTypeForFormat(format: string): string

n

Deterministic number formatting — no -0, no float noise, no locale.

ts
function n(value: number): string

nodeBoxes

Collect the world boxes of the diagram's NODES.

Nodes only. Links are lines: cutting one across a page boundary is normal and reads fine (the line simply continues on the next tile). Cutting a NODE leaves half a box and half a word, which is what makes a tiled print look broken.

ts
function nodeBoxes(root: VNode): Array<{ x: Box; y: Box }>

padRect

Grow a rectangle by padding on every side.

ts
function padRect(rect: Rectangle, padding: number): Rectangle

paginate

Lay a diagram out across a grid of pages.

ts
function paginate(root: VNode, options: PaginationOptions): PaginationResult

printDocument

Open the browser's print dialog for a document built by {@link buildPrintDocument}.

Prints through a hidden IFRAME rather than window.open: a popup is blocked by default in every browser unless the call is inside a user gesture, and a blocked popup means the print button silently does nothing. An iframe always works, and it does not disturb the page the user is on.

Browser-only, and it says so rather than throwing something cryptic in Node.

ts
function printDocument(html: string): Promise<void>

resolveAssets

Fetch every external asset the tree references and inline it as a data: URI.

A FAILED ASSET IS A WARNING, NOT A THROW. One 404 avatar must not lose you the export of a 200-node diagram — the reference is left as-is (still broken, but visible and debuggable) and the caller is told. That is the same rule the batch exporter follows.

ts
async function resolveAssets(root: VNode, options: ResolveAssetsOptions = {}): Promise<ResolveAssetsResult>

resolveCssVars

Replace every var(--grafloria-*) in a declaration with its literal theme value. Returns undefined when a referenced variable has no value and no fallback — the declaration is then DROPPED rather than emitted as an unresolvable reference, and the caller records a warning.

ts
function resolveCssVars(
  value: string,
  vars: Record<string, string>
): string | undefined

resolveRasterBackend

The backend an export will actually use: the caller's, else the browser one, else a hard failure that tells you exactly what to do.

ts
function resolveRasterBackend(explicit?: RasterBackend): RasterBackend

scopeKeysFor

Every key shape the renderer can mint for a given diagram id.

renderNode has a second early-return path for HTML-layer nodes that keys them node-<id>-html-layer, so an exact-match set built only from node-<id> would silently drop those from a selection export.

ts
function selectionKeys(ids: Iterable<string>): Set<string>

selectionKeys

Every key shape the renderer can mint for a given diagram id.

renderNode has a second early-return path for HTML-layer nodes that keys them node-<id>-html-layer, so an exact-match set built only from node-<id> would silently drop those from a selection export.

ts
function selectionKeys(ids: Iterable<string>): Set<string>

serializeVNode

Serialize ONE VNode (and its subtree) to an XML string.

Pure: same VNode in, same string out — no ambient state, no DOM, no clock, no randomness. Attribute order follows prop insertion order, which the renderer builds deterministically, so two calls on the same tree are byte-identical.

ts
function serializeVNode(vnode: VNode, options: SerializeOptions = {}): string

stripResolvedImageWarnings

Remove the two external-image caveats from a capture's warning — called by the async export AFTER it embedded every external image the capture held, at which point both sentences assert a problem that no longer exists (the same defect, mirrored, as staying silent about one that does). Returns undefined when nothing else remains.

ts
function stripResolvedImageWarnings(warning: string | undefined): string | undefined

substituteCssVars

Substitute every var(--name[, fallback]) in a CSS value through lookup.

Unlike {@link resolveCssVars} (written for our own stylesheet, whose fallbacks are plain literals) this parses BALANCED parentheses, because an element's inline paint can carry var(--grafloria-link-stroke, rgb(107, 114, 128)) — and a fallback that is itself a var(). A name lookup cannot answer falls back to the var()'s fallback; a name with neither is listed in unresolved and the value is not returned.

ts
function substituteCssVars(
  value: string,
  lookup: (name: string) => string | undefined
): { value?: string; unresolved: string[] }

svgToDataUri

SVG string → data:image/svg+xml URL.

encodeURIComponent, NOT base64: btoa throws on any non-Latin-1 character, so a diagram with a non-ASCII label (or the renderer's own '…' ellipsis, or the 📌 lock indicator) would blow up the PNG path.

ts
function svgToDataUri(svg: string): string

Was this page helpful?