Variable Width
Variable width lets a stroke's thickness vary along the path it follows, instead of holding one constant width. The material is a width profile: a small ordered set of stops, each pinned to a position along the curve and carrying a thickness. This document specifies the profile model, how it interpolates into the rendered stroke outline, and the authoring facet that edits it.
It specifies only the width delta. The vector network it rides — the
vertices, segments, and tangents that define the path — is the
vector-edit model (VEC-*), and the exclusive
edit-mode slot the facet occupies is the
edit-mode slot: the width facet is that
document's MODE-4, a lens nested inside vector editing, not a
second mode. This spec defers both and models the profile alone.
The model — width profile
A width profile is an ordered sequence of width stops over one node's path. It is authoring material attached to the node's stroke geometry, not a separate node.
A width stop is:
- Position
u— a scalar in[0, 1]naming where the stop sits along the path.u = 0is the path's start,u = 1its end;uruns over the whole path arc, spanning all of its segments, not one segment's local parameter. Mappinguto a concrete point and tangent is the network's job — a monotone traversal of the ordered segments — and is defined by the path, not by the profile. - Width — the stroke half-extents at that position, expressed as a
left and a right radius measured perpendicular to the path
tangent. The two may differ: a profile is possibly asymmetric.
The symmetric case (left equals right) is the common one and the
reduced baseline (
VWID-9): a conformer that stores a single radius per stop implements the symmetric profile and reads asymmetric widths as their mean.
Profile invariants:
- Ordering: stops are kept sorted by
u. Re-sorting onuchange is part of the model, not an authoring nicety — the interpolation below assumes monotoneu. - Endpoints implied, not required: a profile need not carry a stop
at
u = 0oru = 1. Positions before the first stop and after the last are covered by the clamp rule below, so any non-empty profile yields a total width function over the whole path. - Floor: a profile has a minimum stop count below which it ceases
to be a profile. Removing the last stop that would breach the floor
is refused, not obeyed (
VWID-6) — variable width with no stops is not a zero-width stroke, it is the absence of a profile, and the stroke falls back to its constant width.
Interpolation and the outline
The profile defines a width function w(u) over [0, 1],
sampled from the stops:
- Between stops:
winterpolates monotonically between the two bracketing stops' widths — left against left, right against right — asuadvances from the lower stop to the higher. Interpolation is per-side, so an asymmetric profile stays asymmetric between stops. - Outside the stops: before the first stop and after the last,
wclamps — it holds the nearest stop's width flat. No extrapolation past the endpoints. - Single stop: a one-stop profile is a constant
wequal to that stop's width — variable width degenerates to uniform width, which is legal (it is the floor,VWID-6).
The width function shapes the stroke outline: at each point along
the path the stroke's boundary is offset from the path by w's left
value on one side and its right value on the other, both perpendicular
to the path tangent at that point. The result is a filled outline
whose thickness breathes along the curve. This spec fixes what the
outline expresses (the profile evaluated per-side, perpendicular to
the tangent); the rasterization of that outline — joins, caps, and the
sampling density along curved segments — is the stroke rendering
concern and is not restated here.
The profile binds to the stroke geometry, not to a fill: it is a
property of how the node's stroke is outlined. A node with no width
profile strokes at its constant width; a node with a profile strokes
at w(u). Binding is one profile per node's stroke geometry — the
profile addresses the same path the stroke already follows, so it
needs no independent geometry of its own.
The width facet
Editing a profile happens in the width facet — the width lens over a vector node, nested inside its vector edit context. The facet's entry, its place in the exclusive slot, and its Escape behavior (Escape lands on the geometry facet of the same node, never on plain selection) are edit-mode MODE-4. This section specifies only what the facet does to the profile.
The facet is subject-pinned to one node and shows the node's path as inert reference chrome — the vertices and segments render, dimmed and non-interactive, so the author sees the curve the stops ride without being able to edit topology from here. Topology edits belong to the geometry facet.
Facet state: the working profile, the selected stop (at most one), and — while idle — a projected insertion point: the nearest point on the path to the cursor, shown as an add-here affordance, the same hover-armed projection idiom the pen uses on segments (vector-edit). The projection is suppressed while a gesture holds the pointer.
Stop chrome
Each stop renders as three linked controls perpendicular to the path:
- a center handle on the path at the stop's
u; - a left and a right width handle, offset from the center by the stop's left and right radius along the path's perpendicular; and
- a connector drawn between the two width handles through the center — a visual readout of the stop's total thickness and its perpendicular orientation.
The controls re-orient live: the perpendicular follows the path
tangent at u, so the handles always straddle the curve squarely as
the stop slides or the curve bends.
Stop operations
- Add: placing at the projected insertion point inserts a stop at
its
u, born with a width sampled from the current width function at thatu— a new stop does not kink the outline, it lands flush with the existing profile and can then be dragged. Insertion keeps the stops sorted. - Select: clicking a stop's controls selects it; at most one stop is selected at a time.
- Slide (translate along the path): dragging a stop's center
handle moves the stop along the curve — its
uchanges, its width is carried unchanged. The center stays on the path: the drag projects to the nearest point on the curve and snaps to the path's own geometry (its vertices and the projected point), so a stop cannot leave the curve it parameterizes. Re-crossing another stop'sure-sorts the profile; the width function follows the new order. - Set width (resize perpendicular): dragging a width handle changes that side's radius. Motion is projected onto the path's perpendicular at the stop — only the perpendicular component moves the handle, so dragging along the curve does not change width. Each side is independent: the left handle sets the left radius, the right handle the right, and asymmetry is authored by moving them apart. (A conformer holding the symmetric baseline moves both sides together.)
- Remove: deleting the selected stop removes it, subject to the
floor (
VWID-6) — a delete that would drop below the minimum stop count is refused.
Preview and commit
The facet previews live and commits once. A slide or a width drag
updates the working profile continuously — the outline re-renders each
frame under the cursor — while the profile as it stood at gesture
start is retained. Aborting a gesture restores that start profile
exactly; releasing lands the gesture. The facet also retains the
profile as it stood at facet entry, so a facet visited and left
with no effective change is a no-op on the node. The history framing
of these commits — how a gesture becomes one undoable step, and how an
untouched facet leaves history clean — is the edit-mode slot's domain
(edit-mode MODE-7) and is deferred there;
this spec fixes only the preview/commit boundary (VWID-8).
Deferrals
- The path itself — vertices, segments, tangents, the pen, sub-
selection, bend — is vector-edit (
VEC-*). The width facet never edits topology. - The exclusive edit-mode slot, facet entry, Escape nesting, and the authoring-context history domain are edit-mode (MODE-4, MODE-7). This spec does not restate the slot mechanics.
- The rasterization of the outline — joins, caps, sampling — is the stroke rendering concern; this spec fixes only the profile the outline expresses.
Contracts
- VWID-1 Profile model: a width profile is an ordered set of stops
over one node's path; each stop is a position
u ∈ [0, 1]over the whole path arc plus a left and a right radius. Stops are kept sorted byu. - VWID-2 Width function: the profile defines
w(u)that interpolates monotonically per-side between bracketing stops and clamps flat outside the first and last stop; a single-stop profile is constant. - VWID-3 Asymmetry: left and right radii are independent and interpolate independently; a profile with unequal sides stays asymmetric between its stops.
- VWID-4 Outline binding: the stroke outline is offset from the
path by
w's left and right values, each perpendicular to the path tangent at that point; the profile binds to one node's stroke geometry and needs no geometry of its own. - VWID-5 Slide stays on the path: sliding a stop changes only its
uand keeps its center on the curve (projected + snapped to the path's geometry); its width is carried unchanged; re-crossing another stop re-sorts the profile. - VWID-6 Floor: a profile has a minimum stop count; a remove that would breach it is refused. A one-stop profile is legal and reads as a constant width; a profile is never emptied into a zero-width stroke.
- VWID-7 Perpendicular width: dragging a width handle moves only along the path's perpendicular at the stop and sets exactly that side's radius; motion along the tangent leaves the width unchanged.
- VWID-8 Live preview, single commit: a stop gesture previews the working profile continuously; abort restores the gesture-start profile exactly, release lands it; a facet entered and left with no effective change leaves the profile identical.
- VWID-9 Symmetric baseline: a conformer that stores one radius per stop implements the symmetric profile — left equals right at every stop, both width handles move together, and asymmetric input reads as the mean of its sides.
- VWID-10 Insert flush: adding a stop at the projected insertion
point samples its width from the current
w(u), so insertion does not change the rendered outline until the new stop is subsequently moved.