Add primitives to the canvas/graph package that render a text string along a path — a straight line and a circle/arc — placing each successive rune at successive cells of the path. Today the package draws
lines and circles of a single repeated rune (GetLinePoints, GetCirclePoints, GetFullCirclePoints, DrawLineSequence, …) and canvas.Model writes only horizontal strings (SetString). There is no way to write a
string that follows a line or curves around a circle.
Proposed API — in canvas/graph, consistent with the existing Draw*/Get* naming, the *canvas.Model receiver convention, and the functional-options Option pattern used elsewhere:
// DrawTextLine writes text rune-by-rune along the line from a to b.
func DrawTextLine(m *canvas.Model, a, b canvas.Point, text string, style lipgloss.Style, opts ...TextOption)
// DrawTextCircle writes text rune-by-rune around a circle of radius r centered at c.
func DrawTextCircle(m *canvas.Model, c canvas.Point, r int, text string, style lipgloss.Style, opts ...TextOption)
// DrawTextOnPoints is the underlying primitive: it places text along an
// ordered slice of points; the two functions above are thin wrappers.
func DrawTextOnPoints(m *canvas.Model, pts []canvas.Point, text string, style lipgloss.Style, opts ...TextOption)
The wrapping / fit concern — make this an explicit, documented part of the interface, not an implementation detail.
The rune count of text will rarely equal the cell count of the path. The interface must let the caller choose the behavior, e.g. via a TextOption / mode enum:
- Truncate — stop when either the text or the path runs out (suggested default).
- Repeat — tile the text to fill the whole path.
- Stretch — distribute the runes evenly across the path, leaving gaps.
- Wrap — when text exceeds the path, continue onto a parallel offset path (a concentric ring for circles, an offset line for lines). This is the trickiest mode; at minimum, document clearly whether it is
supported or explicitly out of scope.
Whatever the default, overflow and underflow behavior must be a named, documented option so callers aren't surprised. Also document the behavior for degenerate paths (zero or one point).
Other design points to settle:
- Circle point ordering. GetCirclePoints returns points grouped by octant (midpoint-circle algorithm), not in circumferential order — unusable as-is for text. DrawTextCircle needs angularly-ordered points;
expose a start angle and direction (clockwise / counter-clockwise) so callers control where text begins and which way it reads. Consider adding a reusable GetCirclePointsOrdered (or angular generator).
- Wide runes. Emoji and CJK glyphs occupy two terminal cells. Decide and document how the path advances over a wide rune (skip the next path cell, or place and accept overlap). Reuse the go-runewidth
dependency the canvas already pulls in.
- Style. Accept a single lipgloss.Style like the other Draw* functions; per-rune styling is out of scope.
- Bounds. Silently skip off-canvas cells, matching SetRune/SetString behavior.
Acceptance: unit tests covering each fit mode for both line and circle — text shorter than, equal to, and longer than the path — plus a short examples/ program.
Add primitives to the canvas/graph package that render a text string along a path — a straight line and a circle/arc — placing each successive rune at successive cells of the path. Today the package draws
lines and circles of a single repeated rune (GetLinePoints, GetCirclePoints, GetFullCirclePoints, DrawLineSequence, …) and canvas.Model writes only horizontal strings (SetString). There is no way to write a
string that follows a line or curves around a circle.
Proposed API — in canvas/graph, consistent with the existing Draw*/Get* naming, the *canvas.Model receiver convention, and the functional-options Option pattern used elsewhere:
The wrapping / fit concern — make this an explicit, documented part of the interface, not an implementation detail.
The rune count of text will rarely equal the cell count of the path. The interface must let the caller choose the behavior, e.g. via a TextOption / mode enum:
supported or explicitly out of scope.
Whatever the default, overflow and underflow behavior must be a named, documented option so callers aren't surprised. Also document the behavior for degenerate paths (zero or one point).
Other design points to settle:
expose a start angle and direction (clockwise / counter-clockwise) so callers control where text begins and which way it reads. Consider adding a reusable GetCirclePointsOrdered (or angular generator).
dependency the canvas already pulls in.
Acceptance: unit tests covering each fit mode for both line and circle — text shorter than, equal to, and longer than the path — plus a short examples/ program.