Rendering the Display List
Box is both the measured result of layout and the complete display contract. It contains no canvas handle, DOM node, texture, or platform text run. A host draws its paths and…
Display math is "block-style" in the mathematical sense, not a block-layout element in the host document. Untie returns a box; the host decides where that box sits in a page, paragraph, or editor pane. Inline math returns an inline box but still leaves paragraph placement to the host.
Coordinates and Units
All public geometry is in the layout's host units:
- x increases to the right;
- y increases downward;
- the box baseline origin is
(0, 0); Box.AscentandBox.Descentare positive distances above and below the baseline;- glyph paths use the same y-down coordinates as the rest of the box.
The top of the nominal box is -Box.Ascent. The bottom is Box.Descent.
Box.Width is the horizontal advance of the complete box; it is not derived
from the ink bounds.
Box.Italic is the signed OpenType MATH italic correction at the box's final
horizontal endpoint. It may be negative. Do not clamp it to zero or use it as a
hit-test width.
What a Box Contains
type Box struct {
Width float32
Ascent float32
Descent float32
Italic float32
AxisHeight float32
Overflow float32
Items []Item
Hits []Hit
Breaks []Break
Alt Alt
Diagnostics []source.Diagnostic
Tag *TagBox
Lines []LineBox
}The slices are owned by the returned box. A new successful layout creates new box storage.
Box.Diagnostics contains recoverable layout problems, including missing
glyphs rendered as visible placeholders. Parse diagnostics remain on
RenderResult.Diagnostics; the high-level renderer returns a normalized union
of both.
Streaming Drawing Protocol
github.com/caelyreth/untie/render provides a framework-neutral convenience
layer over the same display contract:
err := render.Walk(box, func(command render.Command) error {
switch command.Kind {
case render.CommandPath:
drawPath(command.Path, command.At, command.Em, command.Paint)
case render.CommandRule:
fillRect(command.Rect, command.Paint)
}
return nil
})Walk preserves Box.Items order and skips ItemGlyphRef metadata. For path
commands, Command.Path borrows template storage from the box and resolves in
box coordinates as At + point*Em; it does not materialize transformed
geometry. The template is callback-scoped and must not be mutated or retained.
A callback error stops the walk and is returned unchanged.
Call render.ResolvePath(command) only when a host API requires a standalone
geometry.Path. It intentionally allocates. render.Fit(box, viewport, opts)
returns a uniform Transform that fits box.Bounds() after padding, including
certified overflow; it centers vertically and supports start, center, or end
horizontal alignment.
Raster and PNG Export
github.com/caelyreth/untie/encode/raster is an optional pure-Go encoder for
hosts that cannot consume the display list directly:
options := raster.DefaultOptions()
options.Scale = 2 // two output pixels per host unit
options.Padding = 12
options.Background = &color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
img, err := raster.Encode(box, &options)
if err != nil {
return err
}
err = raster.EncodePNG(writer, box, &options)The encoder walks visible commands in paint order, uses the same nonzero
fill rule as the path contract, and antialiases lines and Bézier curves.
Explicit paints take precedence over the Ink, Error, and Placeholder
palette colors. A nil Options uses DefaultOptions; a nil Background
leaves the image transparent.
Output dimensions are deterministic:
width = ceil(box.Bounds().Width*Scale + 2*Padding)
height = ceil(box.Bounds().Height*Scale + 2*Padding)Each dimension has a one-pixel minimum. Padding is measured in output pixels,
not host units. Box.Bounds() includes certified overflow, so the encoded
image does not silently clip display ink. Invalid scale, padding, geometry, or
an image too large for image.RGBA returns a classified error instead of
panicking.
SVG Output
github.com/caelyreth/untie/encode/svg is an optional pure-Go output adapter:
document, err := svg.Marshal(box, &svg.Options{
Padding: 8,
Precision: 3,
Background: &color.NRGBA{R: 255, G: 255, B: 255, A: 255},
Title: "Mass-energy relation",
Description: "E equals m c squared",
})The encoder walks the same visible commands as a host, resolves
At + point*Em, emits rules as rectangles, and uses nonzero path filling. Its
nonnegative viewBox is sized from Box.Bounds() plus Options.Padding, so
certified overflow is included. Palette maps semantic roles to colors;
an explicit display.RGBA always wins. A nil Background remains transparent.
SVG is an optional interchange format, not a stable wire protocol. Persisted output should carry an application-owned format version.
Drawing Items
Item is already positioned. Draw it at the host's chosen box origin; do not
re-run TeX spacing, font shaping, or script placement in the host.
ItemGlyphPath
box.Paths[item.PathIndex] is a shared fillable path template with MoveTo,
LineTo, QuadTo, CubeTo, and Close segments. Template points are in
local em units. Resolve each point in box coordinates as:
x = item.At.X + point.X*item.Em
y = item.At.Y + point.Y*item.Emitem.Bound is the already-placed conservative rectangular extent. Templates
may be shared by many items, so do not mutate box.Paths or apply item
placement to the template itself.
The path may contain multiple contours. When a glyph uses a hole, the contours are wound so that either nonzero or even-odd filling produces the intended hole.
ItemRule
item.Rect is a filled rectangle in box coordinates. Rules are used for
fraction bars, radicals, overlines, table boundaries, boxes, and other
constructed geometry.
ItemGlyphRef
ItemGlyphRef identifies an exact font glyph that the host may resolve through
its own glyph atlas:
FontID: the host-provided face ID;Glyph: the OpenType glyph index;At: the glyph origin in box coordinates;Em: the glyph's em size;Bound: the placed glyph bounds;Paint: the requested paint.
When Style.GlyphRefs is true, an outlined glyph produces both its path and a
reference. The reference is cache metadata, not a second visible shape. A glyph
with no outline can produce a reference without a path.
Draw only one representation of the same glyph. The simplest host draws
ItemGlyphPath and ItemRule, ignores references, and treats a reference-only
item as non-visible geometry. An optimized host can use exact references to
cache glyphs.
Paint
Paint is either semantic or explicit:
HasExplicit == true: useExplicitRGBA.- otherwise: resolve
RoleInk,RoleError, orRolePlaceholderthrough the host theme.
Error and placeholder roles are intentional visible fallback states. Do not silently paint them as normal ink unless the host has a better accessibility policy.
Inline Layout
Inline layout returns a box with:
- no
Lines; - no use of
Style.MaxWidthorStyle.Align; - inline break opportunities in
Box.Breaks; - the complete formula as
Box.Items.
The host owns placement in a paragraph or text baseline. Untie does not choose a surrounding paragraph's ascent, descent, or line height. The inline box's ascent and descent are already suitable for the font and formula.
Unconstrained Display Layout
Display layout with Style.MaxWidth == 0 produces a free-standing display
box:
Linesis empty;- the complete formula is in
Items; Widthis the natural formula width;Alignis irrelevant.
Constrained Display Layout
Display layout with a positive Style.MaxWidth produces a constrained frame:
Box.Widthequals the requestedMaxWidth, including when the formula fits;Box.Lineshas at least one line;Box.ItemsandBox.Hitsare the only stored item and hit slices and use parent-frame coordinates;- each
LineBox.ItemsandLineBox.Hitsis a contiguous range into those parent slices; - each line has explicit
Width,Ascent,Descent,Overflow,Baseline, andOffsetXmetadata; Box.Overflowcovers the assembled frame and logical width excesses.
Draw Box.Items once. Use a line's ranges when the host needs clipping,
line-local interaction, or per-line metadata; do not apply another coordinate
translation. This single-source contract prevents duplicate path storage and
double drawing.
Line Baselines
- Every line's
Baselineis its y offset from the parent baseline. - The first baseline is zero.
- Adjacent baselines are separated by the previous line's descent plus the current line's ascent.
OffsetXis nonnegative and places the line in the frame.- Overfull lines receive
OffsetX == 0and positive overflow.
Style.Align positions each line in the available frame:
AlignStart;AlignCenter;AlignEnd.
What Can Wrap
Display wrapping is width-greedy. Legal cuts come from relation, binary, and explicit break opportunities. Equal-width candidates use the earliest source cut. Some constructs are deliberately unbreakable, including a formula with no safe cut or source topology that cannot be partitioned without changing meaning. Those cases produce one overfull line rather than an unsafe split.
Box.Breaks is populated for inline output. Constrained display output uses
break opportunities internally and does not expose them as parent
Box.Breaks.
Alternative Text on Lines
Only the parent Box.Alt can be populated, and only when
Style.GenerateAlt is true. Line metadata has no independent alternative
text.
Equation Tags
The current public contract exposes formula-level tags for TeX display math. Typst input does not produce equation-tag metadata in this release.
An unconstrained tagged display box has:
Box.Tag != nil;- tag path and hit records in the suffix of
Box.ItemsandBox.Hitsselected byBox.Tag.ItemsandBox.Tag.Hits; - explicit tag width, ascent, descent, italic correction, and parent-frame
X/Y; - no
Box.Lines.
A constrained tagged box uses one complete line so the tag has unambiguous ownership:
len(Box.Lines) == 1;- both
Box.TagandBox.Lines[0].Tagare present; - the line is aligned in the reduced formula frame
MaxWidth - Tag.Width; - if the line is overfull, it begins at zero offset and the tag follows it;
- logical or ink excess contributes to
Box.Overflow.
The tag body is already positioned in parent-frame coordinates. Draw the tag
range once as part of Box.Items; use TagBox.Items and TagBox.Hits only to
identify that subset. A \tag* value has no automatic parentheses.
Tag source metadata is available through TagBox.Source or the Tags field
of a formula returned by Expr.RootSnapshot().
Box.Tag.Manual is true for the rendered formula-level tag. See the TeX
support page for the exact Phase 1 tag limitations.
Source Hits
Hit links a source range to a placed rectangular area:
type Hit struct {
Source source.Range
Area geometry.Rect
Depth int
Kind HitKind
}Source ranges are half-open UTF-8 byte offsets. They are not rune indexes and not line/column pairs. Convert at the editor boundary if the UI uses runes.
Hit kinds identify semantic regions:
- atom;
- group;
- script;
- fraction numerator or denominator;
- radical;
- fence;
- table cell;
- row;
- other.
Rows and larger groups produce enclosing hits. Atoms and semantic children usually produce deeper hits. Macro-derived nodes can map to the macro invocation origin while retaining their expanded source spans.
Point Hit Testing
box.At(point) returns one semantic hit containing the point:
- deepest hit first;
- then smallest area;
- then earliest source range.
Hits use the same parent-frame coordinates as items. A line's hit range selects the hits belonging to that line without changing their coordinates.
Range Highlighting
box.Covering(sourceRange) returns placed areas intersecting the range. Areas
on the same line that overlap or touch are sorted and merged deterministically.
Use it for editor selection highlighting or source-linked overlays.
Overflow and Bounds
Box.Width, Ascent, and Descent describe the nominal layout box. Ink can
extend outside it because of accents, italic overhangs, delimiters, tags,
negative spacing, or an overfull constrained line.
Box.Overflow is the nonnegative certified allowance around that nominal box.
Do not infer the drawable extent from the nominal fields alone.
bounds := box.Bounds()
// Bounds is:
// X = -Overflow
// Y = -Ascent - Overflow
// Width = Width + 2*Overflow
// Height = Ascent + Descent + 2*OverflowBounds() is suitable for fitting the complete display list into a viewport
or allocating a temporary drawing surface. It is baseline-relative, not an
absolute screen rectangle.
For constrained display:
- each line records its own overflow;
- the parent overflow is the global allowance for the aligned shared list;
- a line's logical width excess can create overflow even when its ink fits.
Drawing Checklist
- Apply the host's box translation once.
- Draw
Box.Itemsonce; line and tag ranges select subsets of that list. - Fill glyph paths and rules with resolved paints.
- Treat
ItemGlyphRefas either a cache substitute or metadata, not an extra shape. - Use
Bounds()for fitting. - Use source hits in the same parent-frame coordinate space.
- Do not mutate a shared
Box.