Documentation / @ripl/terminal
@ripl/terminal ​
A terminal rendering context for Ripl: draws the same 2D graphics and charts as Unicode braille characters with ANSI truecolor, without a DOM.
Features ​
- No DOM — implements Ripl's
Contextabstraction directly, so a scene written for Canvas or SVG renders unchanged in a terminal. - Braille sub-pixels — each character cell packs a 2×4 dot grid (U+2800–U+28FF), quadrupling the vertical resolution a text grid would otherwise give.
- Source-over compositing — pixels are held as an RGBA framebuffer at dot resolution, so overlapping translucent shapes blend rather than the later one claiming the whole cell. A cell emits the alpha-weighted mean of its lit dots.
- Logical coordinates — optional
logicalWidth/logicalHeightlet you author in CSS pixels; the context uniformly scales and letterboxes that space into the character grid, so a canvas-sized scene renders proportionally at any terminal size. - Transforms, clipping and hit testing are all honored — geometry maps through the full affine matrix, nested clips intersect rather than replace, and
isPointInPath/isPointInStroketest the flattened contours in logical space under both fill rules. - Runtime-agnostic output — writes to any
TerminalOutput(write,columns,rows, optionalonResize):process.stdoutvia@ripl/node, or an xterm.js instance in a browser. - Pluggable rasterizer —
BrailleRasterizeris the default; theRasterizerinterface takes any cell geometry viacellWidth/cellHeight. - Snapshot export — braille text,
ImageData, or a PNG object URL.
Installation ​
# npm
npm install @ripl/terminal @ripl/node
# yarn
yarn add @ripl/terminal @ripl/node
# pnpm
pnpm add @ripl/terminal @ripl/node@ripl/node supplies the process.stdout-backed output adapter and the headless platform bindings. In a browser driving xterm.js, install @ripl/terminal alone.
Quick start ​
import {
createContext,
} from '@ripl/terminal';
import {
createCircle,
} from '@ripl/core';
import {
createTerminalOutput,
} from '@ripl/node';
const context = createContext(createTerminalOutput(), {
logicalWidth: 800,
logicalHeight: 600,
});
createCircle({
stroke: '#38bdf8',
lineWidth: 3,
cx: 400,
cy: 300,
radius: 150,
}).render(context);Key API ​
| Export | What it does |
|---|---|
createContext | Binds a TerminalContext to a TerminalOutput |
TerminalOutput | The write/columns/rows adapter interface |
BrailleRasterizer | The default 2×4 braille rasterizer |
Rasterizer | The interface to implement for other cell geometry |
Limitations ​
The terminal context is a rasterizer onto a character grid, so parts of the canvas contract cannot be honored. Everything below is deliberate; none of it errors.
| Feature | Behavior |
|---|---|
Transforms (translate/rotate/scale, and element/group transform state) | Honored. Geometry is mapped through the full affine matrix, so a rotated marker draws rotated and a translated group draws where the transform puts it. |
| Rotated text | A glyph fills a whole cell and cannot itself be rotated, so a rotated run advances along whichever of eight compass directions the transform is nearest. A quarter-turn axis title reads down the side of a chart. |
| Stroke width under a transform | A round pen is genuinely elliptical under a non-uniform scale, so lineWidth maps through the geometric mean of the transform's scale factors. |
| Clipping | Honored. applyClip intersects with any clip already in force, so nested clips narrow rather than replace, and glyphs clip on their cell centre. |
| Hit testing | Honored. isPointInPath/isPointInStroke test the path's flattened contours in logical space; both fill rules are supported. Note that @ripl/terminal has no pointer source of its own — a host that has one (xterm.js in a browser) drives Context.hitTest itself. |
| Images | drawImage is a no-op. |
| Text metrics | One terminal cell per glyph, regardless of font. The font state has no visual effect. |
| Text on a path | Drawn straight from the anchor; pathData/startOffset are ignored. |
| Fill rule | Even-odd only when rasterizing; a nonzero fill rule is ignored there, though hit testing honors both. |
| Gradients and patterns | Resolved to a single color — the gradient's first stop, or the pattern's foreground. |
| Opacity and alpha | Pixels are composited source-over in an RGBA framebuffer, so overlapping translucent shapes blend correctly. A cell emits one color — the alpha-weighted mean of its lit dots — and residual alpha composites against an assumed background (opaque black by default, configurable per rasterizer). Zero alpha draws nothing. |
| Stroke geometry | lineWidth is honored, by stamping a round brush along the path. The brush centres on a dot, so thickness quantises to an odd number of dots — widths of 1, 2, 3, 4 and 5 give strokes 1, 3, 3, 5 and 5 dots across. lineCap, lineJoin and miterLimit are ignored: the brush makes every cap and join round. lineDash/lineDashOffset are honored, with arc length measured along the centreline and approximated by plotted-pixel count. |
| Shadows, filters, compositing | Ignored. globalCompositeOperation: 'destination-out' warns: canvas erases where the terminal draws, so that geometry renders inverted rather than merely degraded. |
Exporting ​
const snapshot = context.export();
const text = snapshot.toString(); // plain braille art
const image = await snapshot.toImage(); // ImageData (rasterized)
const url = snapshot.toURL(); // PNG object URL (browser)
snapshot.release(); // revokes the object URLA glyph occupies a whole cell, which is only 2×4 pixels in the exported image — too small for a letterform — so text rasterizes as a filled block rather than being dropped from the image.
Related packages ​
@ripl/node— stdout adapter and headless platform bindings@ripl/core— the elements and scene graph this context draws@ripl/charts— the same 25 chart types, in a terminal
Documentation ​
Guides, a live demo and the full API reference are at ripl.run/docs/core/contexts/terminal. That page also covers implementing a custom Rasterizer.
License ​
Classes ​
| Class | Description |
|---|---|
| BrailleRasterizer | Braille-dot rasterizer. Each terminal cell encodes a 2×4 grid of sub-pixel dots via Unicode braille patterns (U+2800–U+28FF). |
| TerminalContext | Terminal rendering context that rasterizes Ripl elements into character-based output via a TerminalOutput adapter. |
| TerminalPath | Terminal path implementation that records drawing commands for later rasterization. |
Interfaces ​
| Interface | Description |
|---|---|
| BrailleRasterizerOptions | Options for constructing a BrailleRasterizer. |
| ClipMask | A raster-space stencil restricting where a context may draw. |
| ContourContext | Contour-building state passed to a command's toContour handler. |
| GlyphRun | A glyph run laid out onto the character grid. |
| GlyphRunOptions | Options describing the text run to lay out. |
| RasterContext | Rasterization state passed to a command's rasterize handler. |
| Rasterizer | Abstract rasterizer interface for converting pixel data to terminal characters. |
| SerializeOptions | Options controlling how a rasterizer serializes its grid to a string. |
| TerminalCommandHandler | A path command's two rendering passes: contour flattening (for fills) and outline rasterization. |
| TerminalContextOptions | Options for constructing a terminal rendering context. |
| TerminalOutput | Abstract terminal output interface, runtime-agnostic. |
| TerminalPathCommand | A recorded drawing command with its type and parameters. |
| TerminalTransform | The mapping from the logical space a scene is authored in onto the raster grid, composing the context's letterbox with whatever transform is current. |
| Vertex | A 2D point in raster (pixel) space. |
Type Aliases ​
| Type Alias | Description |
|---|---|
| PixelCallback | Callback invoked for each pixel in a rasterization pass. |
| TerminalColor | A resolved terminal paint: an RGBA color, or null for a paint the terminal cannot resolve, which draws in the terminal's own default foreground rather than not at all. |
| TerminalPathCommandType | Types of drawing commands recorded by a terminal path. |
Variables ​
| Variable | Description |
|---|---|
| ANSI_RESET | ANSI SGR reset sequence. |
| BRAILLE_CELL_HEIGHT | Each braille cell is 2 pixels wide and 4 pixels tall. |
| BRAILLE_CELL_WIDTH | Each braille cell is 2 pixels wide and 4 pixels tall. |
| TERMINAL_COMMAND_HANDLERS | Dispatch table keyed by path command type, driving both rendering passes. |
Functions ​
| Function | Description |
|---|---|
| clipPixels | Gates a plot callback on a clip mask, dropping every pixel outside it. |
| colorToAnsiBg | Converts a CSS color string to an ANSI truecolor background escape sequence. Resolution and return values match colorToAnsiFg. |
| colorToAnsiFg | Converts a CSS color string to an ANSI truecolor foreground escape sequence. Resolution matches resolveTerminalPaint; alpha darkens the color toward black, there being no destination to composite a lone escape sequence against. |
| createClipMask | Builds a clip mask covering the interior of the given contours, intersected with an existing mask. |
| createContext | Creates a terminal rendering context bound to the given output adapter. |
| createTerminalTransform | Composes a letterbox with a transform and wraps the result as a TerminalTransform. |
| dashPixels | Wraps a plot callback so pixels landing in a dash gap are dropped. |
| estimateArcSteps | Estimates a reasonable number of line segments for an arc of the given raster-space radius. |
| estimateEllipseSteps | Estimates a reasonable number of line segments for an ellipse of the given raster-space radii. |
| fillPolygon | Fills the interior of one or more closed contours using the even-odd rule. Each contour is a polyline (implicitly closed); interiors are determined per scanline from edge crossings, so concave shapes, circular segments, and annular sectors (holes) fill correctly. |
| flattenArc | Samples an arc from startAngle to endAngle into a polyline of points (inclusive of both endpoints). |
| flattenCubicBezier | Samples a cubic bezier curve into a polyline of points (inclusive of both endpoints). |
| flattenEllipse | Samples an ellipse outline into a polyline of points. |
| flattenQuadBezier | Samples a quadratic bezier curve into a polyline of points (inclusive of both endpoints). |
| isPointInContours | Tests whether a point falls inside one or more closed contours. |
| isPointOnContours | Tests whether a point falls within width of any edge of the given contours. |
| layoutGlyphRun | Lays a text run onto the character grid, honoring the current transform. |
| letterboxMatrix | Builds the letterbox matrix a terminal context maps logical coordinates through: a uniform scale plus the centring offset that fits the logical space into the character grid. |
| normalizeArcSweep | Resolves an arc's signed sweep, wrapping the end angle by a full turn when the requested direction contradicts the angles as given. |
| normalizeEllipseSweep | Resolves an ellipse's signed sweep. A full turn is reported as a whole turn regardless of direction, matching how flattenEllipse closes the curve implicitly. |
| rasterizeArc | Rasterizes an arc from startAngle to endAngle at (cx,cy) with given radius by subdivision into line segments. |
| rasterizeCircle | Rasterizes a circle outline at (cx,cy) with the given radius using the midpoint algorithm. |
| rasterizeCubicBezier | Rasterizes a cubic bezier curve by adaptive subdivision into line segments. |
| rasterizeEllipse | Rasterizes an ellipse outline at (cx,cy), honoring rotation and sweep. |
| rasterizeLine | Rasterizes a line segment from (x0,y0) to (x1,y1) using Bresenham's algorithm. |
| rasterizePolygon | Rasterizes a closed polygon outline, joining the last point back to the first. Used for shapes whose axis-aligned form has a dedicated rasterizer but whose transformed form does not — a transformed rect is a parallelogram, not a rect. |
| rasterizePolyline | Rasterizes a polyline by drawing a line between each consecutive pair of points. |
| rasterizeQuadBezier | Rasterizes a quadratic bezier curve by adaptive subdivision into line segments. |
| rasterizeRect | Rasterizes a rectangle outline. |
| resolveTerminalPaint | Resolves a CSS paint string and a context opacity into the color a pixel should be painted with. |
| thickenPixels | Widens a rasterization pass by stamping a round brush at every pixel it plots, so a stroke can be more than one dot thick. Thickness is geometry, not color depth: a braille cell is 2×4 dots, so a width is perfectly expressible even though each dot is only lit or unlit. |