# Functions P–W

Import these from `@grafloria/renderer`.

## Functions

### `pointAtPositionOnPolyline`

Arc-length interpolation of a point at position `t` (0-1) along a polyline. Mirrors {@link LinkModel.getPointAtPosition }'s polyline fallback so label
anchors line up with what the renderer draws. Returns `null` for < 2 points.

```ts
function pointAtPositionOnPolyline(points: Point[], t: number): Point | null
```

### `portLabelGeometry`

Where does the label go, which way does it point, and how does it hang off its
anchor?

The `align`/`valign` choice matters as much as the position: a label to the
LEFT of a port must be right-aligned (`end`), or it grows away from the port
and collides with the node.

```ts
function portLabelGeometry(input: PortLabelInput): PortLabelGeometry
```

### `portLabelWidth`

The label's rendered width, using the shared text engine's estimator.

```ts
function portLabelWidth(spec: PortLabelSpec, fontSize: number): number
```

### `portLayoutNames`

For tests / tooling.

```ts
function portLayoutNames(): string[]
```

### `portPositionInBounds`

Same answer, for callers that already hold the node's bounding box.

```ts
function portPositionInBounds(
  port: PortModel,
  node: NodeModel,
  bounds: BoundingBox
): { x: number; y: number }
```

### `portWorldPosition`

The port's WORLD position — for hit-testing, magnets and link endpoints alike. Everything that needs "where is this port on screen" comes through here, so
what you click is always what you see.

```ts
function portWorldPosition(port: PortModel, node: NodeModel): { x: number; y: number }
```

### `registerLabelTemplate`

```ts
function registerLabelTemplate(name: string, template: LabelTemplate): void
```

### `registerLinkTemplate`

```ts
function registerLinkTemplate(name: string, template: LinkTemplate): void
```

### `registerMarker`

```ts
function registerMarker(name: string, definition: MarkerDefinition): void
```

### `registerNotationShapes`

Register every notation shape that the base registry does not already own.

```ts
function registerNotationShapes(): void
```

### `registerPathShape`

Register an arbitrary SVG-path shape. This is the payoff of the
geometry contract: a userland caller adds a brand-new silhouette in ONE call
and it works everywhere the built-ins do — node body, selection highlight,
drop shadow, smart-connection boundary and geometry-true port anchors — with
NO core-team switch edits.

```ts
// Parametric — a 5-point star that fills any box.
registerPathShape('star', (w, h) => starPath(w, h));
// Static — a chevron authored in a 24×24 art box.
registerPathShape('chevron', 'M2,4 L14,4 L22,12 L14,20 L2,20 L10,12 Z',
  { viewBox: { x: 0, y: 0, w: 24, h: 24 } });
```

```ts
function registerPathShape(
  type: string,
  path: PathGeometry,
  opts: PathShapeOptions = {}
): void
```

### `registerPortLayout`

Register a custom strategy. Hosts can add their own; the built-ins are just entries.

```ts
function registerPortLayout(name: string, strategy: PortLayoutStrategy): void
```

### `registerShape`

Register (or replace) a shape definition. This is the payoff: a new shape is
added in ONE place and immediately works across body/selection/shadow,
smart-connection boundaries and port positioning — no switch edits.

```ts
function registerShape(type: string, def: Omit<ShapeDefinition, 'type'> & { type?: string }): void
```

### `renderNodePanel`

Build the panel overlay VNodes for a node, in draw order (background bands
first, corner overlays last). Returns [] when the node has no panel. These are
appended to the node group ON TOP of the base shape body, so the panel rides
whatever silhouette the shape registry drew.

```ts
function renderNodePanel(
  node: NodeModel,
  width: number,
  height: number,
  ctx: PanelRenderContext
): VNode[]
```

### `renderPortGlyph`

Build the port's glyph VNode.

Every existing diagram's port
VNodes are unchanged.

```ts
function renderPortGlyph(input: PortGlyphInput): VNode
```

### `renderPortLabel`

Render the port's label as a `<text>` (or a `<g>` when it is rotated — a
rotation needs a transform, and putting it on the text itself would fight the
`x`/`y` the text-block engine emits for its tspans).

```ts
function renderPortLabel(input: PortLabelInput): VNode
```

### `renderTextBlock`

Render a text block as a single <text> VNode.

- one line  → a <text> carrying `textContent` (+ dominant-baseline for
  vertical centering), preserving the historical single-line output.
- many lines → a <text> with one <tspan> per line, vertically aligned via
  per-line `dy` (first line offset by {@link baselineOffset}).

```ts
function renderTextBlock(opts: TextBlockOptions): VNode
```

### `resolveAspectRatio`

The explicit aspect ratio (w÷h) a node is locked to, or null when unlocked. `aspectLock: true` resolves against a supplied current size.

```ts
function resolveAspectRatio(
  sizing: NodeSizing,
  current?: { width: number; height: number }
): number | null
```

### `resolveRotation`

Total label rotation, with keep-upright auto-flip.

`angle` rotates the label. `keepUpright` (default ON) then adds 180° to any
label that would end up reading upside-down — the standard trick, and the
reason a `radial` label on the LEFT half of a circle still reads
left-to-right instead of mirror-written.

```ts
function resolveRotation(spec: PortLabelSpec, direction: { x: number; y: number }): number
```

### `resolveSpot`

Resolve `spot` to a world point on the glyph box, plus the direction the link
travels there.

Nothing moves unless the
author asks it to.

```ts
function resolveSpot(
  spot: PortSpot | undefined,
  input: SpotInput
): { point: { x: number; y: number }; direction: Side }
```

### `resolveToolbar`

The effective toolbar config for a node: the node's own metadata, with a
host-supplied resolver (per-type policy) layered on top.

```ts
function resolveToolbar(node: NodeModel, resolver?: ToolbarResolver): NodeToolbarConfig
```

### `runPortLayout`

Run a port's layout: pick the strategy, apply it, then the shared `dx`/`dy`
nudge and optional rotation compensation.

```ts
function runPortLayout(spec: PortLayoutSpec | undefined, input: PortLayoutInput): { x: number; y: number }
```

### `sampleOutlineFromData`

Parse + sample a `d` string in one call.

```ts
function sampleOutlineFromData(d: string, steps = 24): Point[]
```

### `sampleOutlinePoints`

Sample a path's OUTLINE as a dense polygon of points.

The path is flattened (curves → line segments) and the single richest subpath
— the shape's main silhouette — is returned as a vertex ring. Interior detail
subpaths (a UML component's tabs, a cube's inner edges) are intentionally
dropped: the boundary/anchor scanline treats its input as ONE closed ring, and
stitching several subpaths into one ring would invent phantom edges.

Returns an empty array for an unparseable / empty `d` so callers fall back to
the bounding box.

```ts
function sampleOutlinePoints(cmds: PathCmd[], steps = 24): Point[]
```

### `sanitizeAssetUrl`

Allow only image-safe URL schemes on an asset href. Blocks `javascript:` and
`data:text/html` (the executable-URL vectors); permits `data:image/*`,
`blob:`, `http(s):`, and scheme-relative / path references. An unsafe or
non-string href resolves to '' so the slot renders empty instead of dangerous.

```ts
function sanitizeAssetUrl(href: unknown): string
```

### `sanitizeHtmlContent`

Convert ONE sanitized content node to a VNode. Unknown tags collapse to a
`<span>` (their text is kept, their dangerous identity is dropped). Returns
null for a node that contributes nothing.

```ts
function sanitizeHtmlContent(spec: HtmlContentNode): VNode | null
```

### `segmentIntersectsRect`

Liang-Barsky: does the segment touch the rect at all?

```ts
function segmentIntersectsRect(a: Point, b: Point, rect: OptimizerRect): boolean
```

### `separateParallelRoute`

Push a routed polyline onto its lane in a parallel bundle.

The two ENDPOINTS never move: they are the ports, and a fan that pulled the
line off its port would be worse than the overlap it fixes. Only the interior
of the route is displaced.

• `orthogonal` — every INTERIOR segment is slid along its own normal. A
    segment's neighbours run perpendicular to it, so sliding it only makes
    them longer or shorter: the route stays exactly orthogonal (no diagonal
    ever appears). A route with no interior segment (a straight 2-point
    orthogonal run between aligned ports) gets an S-jog inserted instead,
    because there is otherwise nothing to displace.
  • `direct` / `smooth` / `bezier` — the interior points are displaced along
    the bundle normal; a bare 2-point route gets an offset midpoint, which the
    path emitters turn into a bow (a spline for smooth, a shallow V for
    direct). This is the "curviness" fan GoJS and React Flow draw.

```ts
function separateParallelRoute(
  points: FanoutPoint[],
  offset: number,
  normal: FanoutPoint,
  pathType: string
): FanoutPoint[]
```

### `serializePathCmds`

Serialize a command list back into a compact `d` string.

```ts
function serializePathCmds(cmds: PathCmd[]): string
```

### `sideNormal`

Outward unit normal of a node side.

```ts
function sideNormal(side: FanoutSide): FanoutPoint
```

### `sideTangent`

Unit tangent of a side — the axis links spread along.

```ts
function sideTangent(side: Side): { x: number; y: number }
```

### `spreadOffsets`

The signed lane offsets for `count` links sharing one port, spaced `spacing`
apart and CENTRED on the port's own attachment point.

count 1 → [0]                 ← a lone link NEVER moves. This is the whole
                                  byte-stability guarantee: a port with one
                                  link renders exactly where it always did.
  count 2 → [-s/2, +s/2]
  count 3 → [-s, 0, +s]

```ts
function spreadOffsets(count: number, spacing: number, max = 0): number[]
```

### `toolbarAllows`

Whether a boolean tool (resize / rotate / remove) is enabled; `def` when unset.

```ts
function toolbarAllows(
  config: NodeToolbarConfig,
  tool: 'resize' | 'rotate' | 'remove',
  def = true
): boolean
```

### `translateCmds`

Offset every coordinate by (dx, dy) — used for grow/shadow transforms.

```ts
function translateCmds(cmds: PathCmd[], dx: number, dy: number): PathCmd[]
```

### `unregisterLabelTemplate`

Remove one label template. Returns false when it was not registered.

```ts
function unregisterLabelTemplate(name: string): boolean
```

### `unregisterLinkTemplate`

Remove one link template. Returns false when it was not registered.

```ts
function unregisterLinkTemplate(name: string): boolean
```

### `unregisterMarker`

Remove one marker. Returns false when it was not registered.

```ts
function unregisterMarker(name: string): boolean
```

### `unregisterShape`

Remove a registered shape. Returns false when the type was not registered.

```ts
function unregisterShape(type: string): boolean
```

### `wrapText`

Break `text` into display lines: split on hard '\n', then greedily word-wrap
each paragraph to `maxWidth`. A single word wider than maxWidth breaks at its
HYPHENS first ("predefined-process" → "predefined-" + "process" — a hyphen is
a legitimate break point; a centred clip eats BOTH ends of the word and reads
as gibberish, which is exactly what the screenshot audit caught). A word with
no hyphen is kept whole (clipped, not broken) — the historical LabelRenderer
behavior for genuinely unbreakable runs.

```ts
function wrapText(text: string, maxWidth: number | undefined, fontSize: number): string[]
```
