Skip to content
D
Documentation

Export — functions

reference
10 min readUpdated

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

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

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(--…).

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

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[]>

exportPdf

Export a rendered VNode tree as a vector PDF.

ts
function exportPdf(root: VNode, options: PdfExportOptions = {}): PdfExportResult

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

pageDimensions

The page's dimensions in points, after orientation.

ts
function pageDimensions(
  size: PageSize | { width: number; height: number },
  orientation: Orientation
): { width: number; height: number }

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

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

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.

ts
function 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.

ts
function vnodeBounds(root: VNode, options: BoundsOptions = {}): Rectangle | null

Was this page helpful?

Export — functions — Grafloria