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

Routing Scenes

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.

Use a prepared scene when routing between your own shapes or around fixed objects. Resolved bounds need no note setup, counters, pins or document queries. For existing content, the same constructor also accepts explicit mark, body and pin selectors in a context with deixis-setup-notes. These read accepted geometry; they do not render or measure the content again.

Source

#let scene = deixis-routing-scene(nodes: (
  (id: <a>, bounds: (left: 0pt, top: 20pt, right: 40pt, bottom: 50pt)),
  (id: <b>, bounds: (left: 200pt, top: 20pt, right: 240pt, bottom: 50pt)),
  (bounds: (left: 80pt, top: 10pt, right: 120pt, bottom: 70pt)),
))
#let path = deixis-route(scene: scene, from: <a>, to: <b>, route: "orthogonal")
#assert.eq(path.status, "ok")

Complete source and preview setup

Output · Click to zoom

Prepare and place the result as in numeric routing. The small drawing example includes a cut node. Prepare the scene once and reuse it for several routes. Node bounds stay fixed; routing cannot move or pack content. Use group routing when several explicit orthogonal fragments should consider each other’s shafts. Separate deixis-route calls remain independent.

Measured Document Nodes

Use selectors instead of bounds to borrow existing document geometry:

Fragment · Requires surrounding code

#context {
  let scene = deixis-routing-scene(nodes: (
    (id: <source>, pin: <figure-port>),
    (id: <target>, body: <explanation>),
    (id: <obstacle>, pins: (<top-left>, <bottom-right>)),
  ))
  let path = deixis-route(scene: scene, from: <source>, to: <target>, route: "orthogonal")
  if path.status == "ok" {
    let request = deixis-prepare-connector(path, heads: "triangle")
    deixis-absolute-place(top + left, dx: path.origin.first(), dy: path.origin.last(),
      deixis-render-connector(request))
  }
}

See Routing Measured Content for a complete small example with deixis-attach corner pins and an annotation body.

Declared connections can register the same selectors through deixis-routing(nodes: ...). That scope combines the selected geometry with live attachments before planning its individual connections; no separate scene callback is required.

SelectorGeometry
mark: <note>Original mark; add occurrence: for a named inline, phantom or region reuse
body: <note>One accepted margin, inset or nonbreaking inline frame, with a complete live span/destination
body: <note>, point: "start" or "end"A live boundary point, also for printed footnotes/endnotes and paginated bodies
pin: targetOne exact pin point, using existing name/scope/select/page rules
pins: (target, ...)Rectangle around selected pins on one page, including producer padding

Choose exactly one selector or numeric bounds. Optional padding expands the resolved document bounds; it accepts signed lengths or the existing edge padding dictionary and resolves in the constructor’s font context. pin: uses the exact coordinate independently of producer padding; pins: follows region padding semantics. A positive padding can turn a point into an area obstacle.

Point/zero-area document nodes default to behavior: "ignore"; rectangular nodes default to "avoid". An ignored point also works with literal numeric bounds. Only ignore permits zero-area bounds. Named point endpoints have a free normal by default; an explicit side: constrains their departure/arrival.

Document selectors preserve existing strict frame rules: they do not approximate a paginated/fragmented body with a union of distant rectangles. Use body start/end points when a full frame is unavailable. Multi-page regions require explicit page: and use that native region fragment’s bounds. Raw pin bounds cannot combine pages. An explicit page on a single frame/point must match its actual page. Missing, ambiguous, unprinted or unusable targets receive diagnostics.

Layer controls painting. An explicitly selected node participates regardless of its body’s layer; the scene does not discover or pack other layers’ contents. Use the scene for drawing after geometry is accepted. Feeding its result back into body size/placement can create a layout cycle; native convergence limits still apply. Extra live observers of late route evidence can consume another layout pass, especially with immediate margin bodies; inspect diagnostic results outside document layout when possible.

Hidden nodes have no fabricated bounds, contribute no obstacle, and remain named so a route selecting one can return status: "endpoint-hidden". Callers can skip painting that result. The other registered nodes remain usable. Forward selectors work; constructor position does not determine the selected body page. Native drawing still follows the caller’s placement/layer order.

Document nodes infer search bounds from captured page frames when available. Explicit pages: entries override those bounds. Pure numeric nodes never acquire an implicit document coordinate plane or query dependency. Explicit registration is required: text, arbitrary figures and other note bodies do not automatically become obstacles. Existing note connectors do not silently opt into this engine.

Scenes In Document Connectors

Add scene to an ordinary note’s connector or to deixis-connect. The small Notes Around Figures example uses:

Fragment · Requires surrounding code

connector: (
  route: "orthogonal", radius: 0pt, heads: "triangle",
  scene: _ => deixis-routing-scene(nodes: (
    (mark: <detail>), (body: <detail>), (pins: (<tl>, <br>)),
  )),
)

scene accepts none (default), a prepared deixis-routing-scene value, or a function taking the current document/page-fragment record and returning that value. The function runs after visible endpoints and the active physical page are selected. It can use doc.page, doc.fragment, doc.nodes, doc.layer and doc.continuation; custom shaft painters receive these fields as request.route.document. The scene callback sees the preferred endpoints; the final painter/marker sees any selected replacement. A continuation preference is retained in continuation.allocation.preferred. Keep the function read-only: return geometry rather than drawing content or moving a body. Native layout may evaluate it again while converging. Hidden endpoints/declarations and disabled routes do not evaluate it. Prepare a shared scene in context when several edges can reuse it.

Only the current physical page’s nodes participate. An absent page is an empty plane; unpaged nodes never become global obstacles. An active avoidance node requires sharp orthogonal routing, radius: 0pt, and point-only active waypoints. Native radius: auto enables rounding and is rejected for avoidance; it retains its ordinary stroke-dependent value on pages without avoidance nodes. Explicit pins/waypoints remain hard visits, including the selected half around "split". With margin via: auto, the planner replaces the adapter’s synthetic lane waypoints while retaining the accepted body position and attachment policy. The geometric port choice is tried first. If it is blocked or unreachable, one fallback search can use the adapter’s other automatic ports. A successful preferred route stays unchanged, even when another port could be shorter. Inset and independent framed endpoints can have alternatives. Ordinary margin inline exits retain their text-line policy; a region exit can slide along its existing outward side, and its body can slide within the existing safe span of the inner border. Bodies stay in their accepted positions.

Explicit connector.ports constrain that endpoint: a pin socket or percentage stays exact, and a selected side never changes sides. With at: auto (also the default for a bare side), a blocked/unreachable preference can slide along that side. The gap and outward normal stay fixed. Explicit at: 50% or a pin socket locks the point. Successful preferred routes are retained without sliding. Numeric deixis-route endpoints also retain their existing fixed-port contract. The fallback minimizes shaft length plus bend penalties over permitted discrete choices and finite visibility projections along permitted sides (plus displacement cost for an automatic continuation). It shares the preferred search’s expansion allowance; there is no per-port-pair search loop. See An Exit Along The Same Side for a compact example. Visibility candidates do not guarantee a globally shortest continuous attachment; they preserve the bounded candidate-graph contract. Without avoidance nodes on the active page, ordinary native routing is preserved, including margin lanes, curves and mixed sections; search is none.

Registering the endpoint through its matching mark:/body: selector permits only its outward attachment stub through its own clearance pocket. Existing positive port gaps work too. This association follows the exact annotation, occurrence or selected body point, never an arbitrary enclosing box. Numeric boxes and pins: rectangles are independent obstacles. Interior/custom sockets and padding that encloses the attachment are not silently exempted; move the port outside the registered frame or choose appropriate registration. Foreign overlapping obstacles still block the stub. Duplicate matching owners receive a diagnostic. A point pin has no outward direction by default and cannot escape from inside an avoidance box merely by being named.

Routing and scene interactions finish before head preparation. Scene cuts merge with manual cuts; optional connector-crossing gaps follow that preparation. request.route.interactions and scene-cuts describe the original route, and the native painter or a custom painter uses the same prepared shaft/heads. No gap indicator or crossing symbol is enabled implicitly.

A blocked constrained endpoint, unavailable permitted ports/path, blocked hard waypoint or exhausted search produces a final diagnostic naming the physical page and failure detail. That fragment executes no head, shaft painter or continuation marker and has no unchecked fallback. Cross-page connectors route the existing endpoint fragments separately. With avoidance enabled, a blocked/unreachable automatic relay can move along its current top/bottom boundary. Explicit ("split", x-spec) positions stay fixed; bare "split" still separates exact visits and permits automatic allocation. See obstacle-aware continuations for selection, budgets and callback geometry. Intermediate pages receive no extra shaft. Scene routing changes neither body packing nor layer/paint order; independently planned connectors can still share a corridor.

Nodes And Interactions

Each node accepts bounds, optional id, page, and behavior. Bounds require finite absolute left, top, right, bottom lengths and positive dimensions (zero-area points are allowed with behavior: "ignore"). The optional ID is a nonempty string or label; equivalent spellings name the same scene-local node. IDs are unique across the scene and independent of note IDs.

behaviorEffect
"avoid" (default)Prevent the planned centerline from entering its inflated rectangle
"ignore"Retain a named endpoint without blocking or reporting interactions
"cut"Keep the original route and hide shaft intervals inside its rectangle during preparation
"cross"Keep the shaft and report entry/exit intervals for an optional custom painter

Avoidance uses route: "orthogonal", zero radius, and point-only via. Rounded corners, curves and mixed sections are rejected when avoidance nodes are present: smoothing a safe sharp corner can enter an obstacle. Scenes without avoidance nodes support all existing native line/cubic routes and sections. Numeric sharp orthogonal scene routes use the planner even in an empty plane. Document connectors preserve their native route on pages without avoidance nodes.

Cut and cross rectangles use their original bounds, without avoidance clearance. Boundary contact alone is not an interior crossing. Native cubic roots preserve loops and repeated entries without fitting a new curve. Interactions are recorded against the original geometry, before head trimming and any cuts. A cut applies to the shaft; it does not hide heads, node ink, labels or custom decorations.

Successful scene routes add interactions, sorted by distance then node order. Each entry contains node (normalized ID or none), index (zero-based scene node index), behavior, page, from, to, entry, exit. Distances are along the original drawable route; entry/exit points use its source-local frame. scene-cuts holds the cut intervals. Preparation merges these with explicit cuts, retaining the original route and the existing head/trim/gap contracts.

No crossing decoration or line jump is enabled implicitly. For example, a custom painter can draw a small symbol at selected request.route.interactions entries. Omit the symbol to keep ordinary cuts plain. Connector-to-connector crossing groups remain a separate document feature. Independent avoiding routes can still share a corridor; they do not jointly optimize congestion or reserve lanes.

Endpoints And Clearance

Plain numeric endpoints keep their existing behavior. A node ID is shorthand for a midpoint port on the side facing the other endpoint’s center, choosing the larger horizontal/vertical displacement (horizontal wins ties). This is a stable simple choice, not a search over all possible ports. Specify a side when needed:

Fragment · Requires surrounding code

from: (node: <a>, side: right, at: 25%),
to: (node: <b>, side: left),

side accepts auto, left, right, top, bottom. at defaults to 50% and ranges from 0% to 100%: top to bottom on vertical sides, left to right on horizontal sides. A custom exact port can instead use (point: (40pt, 30pt), normal: right, owner: <a>). On an avoidance node it must be on that node’s original border with an outward cardinal normal.

clearance defaults to 4pt and expands every avoidance rectangle. The short outward endpoint stub is exempt from its own node only. Other nodes, including overlapping ones, still block it. escape defaults to 4pt, must be positive, and sets the minimum stub length and nearby detour rails. An owned stub extends at least through the clearance pocket.

Clearance concerns the centerline, not shaft thickness, head ink or custom node ink. Increase it to account for those when appropriate. Exact-clearance boundary channels remain available, including touching inflated rectangles.

Pages And Failures

The default plane has page: none. Positive page values identify independent physical page planes; coordinates always start at that page’s own origin. Unpaged nodes do not become global obstacles on physical pages. Equal coordinates on two pages never imply a collision between them.

Fragment · Requires surrounding code

#let scene = deixis-routing-scene(
  nodes: (
    (id: <a>, page: 1, bounds: first-box),
    (id: <b>, page: 2, bounds: second-box),
  ),
  pages: (
    (page: 1, bounds: (left: 0pt, top: 0pt, right: 600pt, bottom: 400pt)),
    (page: 2, bounds: (left: 0pt, top: 0pt, right: 420pt, bottom: 300pt)),
  ),
)

Named endpoints infer their shared plane. Supply page: 1 to deixis-route for numeric endpoints on that page. A node on an undeclared page still establishes its plane with unbounded search. bounds on the constructor limits the unpaged planner; pages supplies per-page search bounds. Bounds constrain avoidance search, not arbitrary native curve painting or clipping.

A numeric route connects one plane. Connecting <a> directly to <b> in the example above is rejected. Compose separate fragments with explicit boundary ports, or use the existing document connector’s endpoint-page continuations. Document connectors can now apply connector.scene separately to both existing endpoint fragments, including fallback allocation of automatic horizontal boundary ports. Joint paired-boundary planning and intervening-page fragments remain future work. See continuation markers.

Successful scene routes have status: "ok", page, and search statistics (none for an ordinary non-planned route). A failed route/search returns kind: "deixis-route-failure", status, detail, page, stats; it has no drawable geometry. Status is endpoint-blocked, waypoint-blocked, no-path, or budget; a hidden document endpoint returns endpoint-hidden. Inspect this before preparing a route. Preparation rejects failures instead of painting an unchecked fallback through an obstacle.

The first bounded implementation accepts 128 scene nodes, eight exact waypoints per planned route, 65,536 candidate lattice cells and 50,000 expansions. Adjust max-cells/max-expansions on the scene explicitly if needed. Candidate cells are not eagerly materialized: interval indexes are prepared once, and each search tests only expanded adjacent edges. bend-cost is a nonnegative length, default 12pt; score is Manhattan route length plus this cost per bend. Search is optimal over the finite candidate graph with its cardinal ports, ordered visits and no immediate U-turns, not over arbitrary continuous geometry.

Arc-length cuts and interactions use the native backend’s approximate length and 0.00001pt cleanup precision. Node curves, stroke-aware clearance, safe smoothing, body packing and automatic flow/cross-layer packing remain separate work. Numeric groups add bounded joint congestion planning; document routing collects individual declarations and registers native content frames in place.