# 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
```
