Functions
Import these from @grafloria/renderer.
Functions
base64ToBytes
Pure base64 → bytes. (No atob: it does not exist in Node, and this must stay DOM-free.)
tsfunction 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.
tsfunction buildPrintDocument(svgPages: string[], options: PrintOptions = {}): string
bytesToBase64
Pure bytes → base64.
tsfunction bytesToBase64(bytes: Uint8Array): string
bytesToDataUrl
bytes → data:<mime>;base64,…
tsfunction bytesToDataUrl(bytes: Uint8Array, mimeType: string): string
canRasterizeInThisEnvironment
Is there a canvas we can draw into? (browser main thread or worker)
tsfunction 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.
tsfunction captureCustomNodeHost(
id: string,
rect: Rectangle,
host: unknown,
options: CaptureHostOptions = {}
): 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.
tsfunction 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.
tsfunction 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.
tsfunction computeBreaks(
start: number,
end: number,
pageSize: number,
boxes: Box[],
options: { snap: boolean; tolerance: number; overlap: number; warnings: string[]; axis: 'x' | 'y' }
): number[]
crc32
tsfunction 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.
tsfunction createClassStyleResolver(theme: Theme, warnings: string[] = []): ClassStyleResolver
Parameters
theme: the theme whose--grafloria-*values get baked inwarnings: collector — a declaration whose variable cannot be resolved is dropped and reported here rather than silently emittingvar(--…).
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>.
tsfunction createDomRasterBackend(): RasterBackend
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.
tsfunction 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.
tsfunction 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.
tsfunction 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.
tsfunction 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.
tsfunction 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).
tsfunction 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.
tsfunction embedModelInSvg(svg: string, envelope: DiagramDocumentEnvelope): string
escapeAttr
XML-escape an attribute value.
tsfunction escapeAttr(value: string): string
escapeText
XML-escape text content.
tsfunction 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.
tsasync function exportBatch(jobs: BatchJob[], options: BatchOptions = {}): Promise<BatchResult[]>
exportPdf
Export a rendered VNode tree as a vector PDF.
tsfunction exportPdf(root: VNode, options: PdfExportOptions = {}): PdfExportResult
exportSvg
Serialize a rendered VNode tree to a standalone SVG string.
tsfunction exportSvg(root: VNode, options: SvgExportOptions = {}): SvgExportResult
Parameters
root: the root<svg>VNode fromSVGRenderer.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).
tsfunction 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).
tsfunction 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.
tsfunction 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.
tsasync function fetchAssetsTiered(
urls: readonly string[],
options: TieredFetchOptions = {}
): Promise<TieredFetchResult>
fetchFont
Fetch a font and turn it into a {@link FontSource}, ready for {@link fontFaceCss}.
tsasync 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.
tsfunction 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).
tsfunction 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.
tsfunction fontFaceCss(fonts: FontSource[]): string
fontFormatFromUrl
woff2 from a .woff2 URL, etc. Defaults to woff2 — by far the most common on the web.
tsfunction 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.
tsfunction 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.
tsfunction inlineAssets(root: VNode, byUrl: ReadonlyMap<string, string>): VNode
isEditableArtifact
Does this artifact carry a model we could re-open?
tsfunction isEditableArtifact(artifact: Artifact): boolean
isExternalUrl
Is this a reference that has to leave the document to resolve?
tsfunction 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).
tsasync 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.
tsfunction mimeTypeForFormat(format: string): string
n
Deterministic number formatting — no -0, no float noise, no locale.
tsfunction 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.
tsfunction nodeBoxes(root: VNode): Array<{ x: Box; y: Box }>
padRect
Grow a rectangle by padding on every side.
tsfunction padRect(rect: Rectangle, padding: number): Rectangle
pageDimensions
The page's dimensions in points, after orientation.
tsfunction pageDimensions(
size: PageSize | { width: number; height: number },
orientation: Orientation
): { width: number; height: number }
paginate
Lay a diagram out across a grid of pages.
tsfunction 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.
tsfunction 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.
tsasync 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.
tsfunction 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.
tsfunction 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.
tsfunction 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.
tsfunction 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.
tsfunction 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.
tsfunction stripResolvedImageWarnings(warning: string | undefined): string | undefined
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.
tsfunction svgToDataUri(svg: string): string
viewBoxTransform
The transform that maps a viewBox onto a rect the way preserveAspectRatio says
— the fit an inline <svg> gets from the browser for free, resolved into an
ordinary affine transform so every target can honour it.
Supports the two forms that actually occur: none (stretch each axis
independently) and x??Y?? meet (uniform scale, then align). slice is treated as
meet: over-filling the widget box would paint a chart over its neighbours, and a
chart that is slightly small is a far smaller lie than one that overlaps.
tsfunction viewBoxTransform(
viewBox: Rectangle,
rect: { width: number; height: number },
preserveAspectRatio = 'xMidYMid meet'
): string
vnodeBounds
The union box of everything a VNode tree paints, in the tree's own user space.
Returns null for a tree that paints nothing (an empty diagram) — the caller
decides what an empty document should be, because "nothing" is not a rectangle.
tsfunction vnodeBounds(root: VNode, options: BoundsOptions = {}): Rectangle | null
Was this page helpful?