Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Numeric Routing

Examples assume the package import from getting started. Numeric geometry needs no setup; document selectors need the standard setup too. Excerpts with callback variables extend the surrounding example.

When coordinates are already known, use deixis-route without notes, pins, setup, page overlays or a layout context. The small example connects two custom cards. For measured document content, keep using pin connections. For several sharp orthogonal fragments, use group routing to share a scene and run checked congestion repair before the same painting step.

Source

#let path = deixis-route(from: (0pt, 0pt), to: (120pt, 40pt), route: "hobby")
#context {
  let drawing = deixis-prepare-connector(path, stroke: teal + 1pt, heads: "triangle")
  deixis-render-connector(drawing)
}

Complete source and preview setup

Output · Click to zoom

The three steps have separate responsibilities:

FunctionResultContext
deixis-route(from:, to:, route:, via:, segments:, radius:)Numeric line/cubic geometryNone required; no queries, measurement or callbacks
deixis-prepare-connector(path, stroke:, heads:, cuts:, gap-marks:)Existing connector painter requestStyles and custom head preparation can need context
deixis-render-connector(request)Native drawing contentUses the prepared request without rerouting or repeating head callbacks

A custom backend can consume the numeric geometry directly, or consume the prepared shaft/heads. Preparing again is only necessary when preparation inputs change. Stroke-only halo passes can reuse the request as described in the painter guide.

Input Plane And Constraints

All points, including B-spline controls and points inside sections, use one caller-defined plane: x increases rightward and y downward. Supply pairs of finite absolute lengths, such as (30pt, -10mm). Unitless numbers, font lengths, percentages, frame dictionaries, pin targets and page splits are rejected. Resolve those in your own adapter first. The numeric entry does not read deixis-set defaults.

An endpoint is a point or (point: ..., normal: ...):

Source

#let path = deixis-route(
  from: (point: (20pt, 30pt), normal: right),
  to: (point: (180pt, 90pt), normal: left),
  route: "hobby", via: ((80pt, 10pt),),
)

Complete source and preview setup

Output · Click to zoom

Normals point outward at both ends, following the port convention. A curve departs along the source normal and arrives opposite the target normal. Accepts left, right, top, bottom, or a nonzero finite numeric vector such as (2, -1); vectors normalize internally. Omit normal or use auto for a free endpoint. Orthogonal/chamfer endpoint normals must be cardinal. Polylines follow their supplied vertices and do not add bends to satisfy normals.

route accepts the same native names and grouped algorithm settings as document connectors. The default is a straight line. radius is a finite nonnegative absolute length, default 0pt; it controls orthogonal/chamfer corners. There is no stroke-dependent automatic radius in this geometry-only entry. Legacy routing callbacks are not numeric builders.

via is an array of absolute points and native route switches. All supplied numeric points are hard visits, as pin waypoints are in the document API; rounded corners preserve them. Alternatively, segments uses the existing (route:, via:) section records. Supply either via or segments, with none meaning omitted. B-spline controls are also absolute input-plane points, but influence the curve without becoming hard visits. Explicit equal B-spline/Hobby switches delimit independent sections. A "none" section makes a discontinuity; it does not cut an unchanged interpolating curve midway. Use cuts at preparation for that. Page continuation belongs to the document adapter. Optional scene and page arguments add page-local nodes and avoidance.

Geometry Result

The result has kind: "deixis-route" and frame: "source-local-x-right-y-down". It contains only numeric geometry:

FieldMeaning
originInput source point in the caller’s plane
start, endSource-local endpoint positions; start is (0pt, 0pt)
segmentsOrdered line/cubic records in that local frame
boundsConservative bounds including endpoint positions and cubic handles, with left/top/right/bottom/center-x/center-y
lengthApproximate drawable arc length, excluding undrawn gaps
has-start, has-endA drawable segment reaches the corresponding endpoint
start-tangent, end-tangentUnit travel directions; zero when that endpoint has no drawable path
gapsInternal discontinuities with start/end positions and travel tangents

A line is (kind: "line", start:, end:). A cubic adds c1 and c2. Consecutive records need not touch: a gap starts a new subpath. Custom painters must preserve these breaks. Quadratic corners are elevated to cubics. No native content, note IDs, pins, registry state or document metadata enters this result.

Bounds are conservative centerline/control bounds, not tight curve extrema or stroke/head/ink bounds. Arc lengths and cuts retain the shared numeric precision limits. Degenerate segments and hard-point duplicates within 0.00001pt are cleaned up. Coincident endpoints can still form a nonempty returning path. Native overflow limits still apply to extreme coordinates and algorithm settings.

Preparation And Placement

deixis-prepare-connector accepts a geometry result. Defaults are black 1pt, no heads, no cuts and no gap marks. Heads use the existing shape/custom-head and (mark:, body:) conventions; cuts and optional ticks use the existing connector records. Mark/body mean source/target here. Preparation keeps the route intact, calls each present head renderer once, then trims/cuts its shaft. Head content executes when painted, so painting it twice also repeats its content effects. There is no measurement of custom node content. Opt-in scene cuts join explicit cuts at preparation; crossing events remain available on the original route.

The request uses the existing source-local painter frame. route.document is none; crossings is none, since an isolated route has no page/layer/group context. It includes route, shaft, manual-shaft, uncut-shaft, cuts, heads, gaps, gap-marks and stroke as in document callbacks.

The native painter does not apply origin automatically. To draw in the original input plane, position its output yourself:

Source

#context {
  let drawing = deixis-prepare-connector(path, heads: "triangle")
  place(top + left, dx: path.origin.first(), dy: path.origin.last(),
    deixis-render-connector(drawing))
}

Complete source and preview setup

Output · Click to zoom

Allocate your diagram’s frame normally. Painting is overlay content and does not reserve node or drawing dimensions. For automatic document lookup, page relays, z-order and grouped crossing gaps, use deixis-connect instead.