Skip to content

Documentation / @ripl/terminal

@ripl/terminal ​

npmlicensesize

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 Context abstraction 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/logicalHeight let 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/isPointInStroke test the flattened contours in logical space under both fill rules.
  • Runtime-agnostic output — writes to any TerminalOutput (write, columns, rows, optional onResize): process.stdout via @ripl/node, or an xterm.js instance in a browser.
  • Pluggable rasterizer — BrailleRasterizer is the default; the Rasterizer interface takes any cell geometry via cellWidth/cellHeight.
  • Snapshot export — braille text, ImageData, or a PNG object URL.

Installation ​

bash
# 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 ​

typescript
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 ​

ExportWhat it does
createContextBinds a TerminalContext to a TerminalOutput
TerminalOutputThe write/columns/rows adapter interface
BrailleRasterizerThe default 2×4 braille rasterizer
RasterizerThe 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.

FeatureBehavior
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 textA 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 transformA round pen is genuinely elliptical under a non-uniform scale, so lineWidth maps through the geometric mean of the transform's scale factors.
ClippingHonored. applyClip intersects with any clip already in force, so nested clips narrow rather than replace, and glyphs clip on their cell centre.
Hit testingHonored. 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.
ImagesdrawImage is a no-op.
Text metricsOne terminal cell per glyph, regardless of font. The font state has no visual effect.
Text on a pathDrawn straight from the anchor; pathData/startOffset are ignored.
Fill ruleEven-odd only when rasterizing; a nonzero fill rule is ignored there, though hit testing honors both.
Gradients and patternsResolved to a single color — the gradient's first stop, or the pattern's foreground.
Opacity and alphaPixels 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 geometrylineWidth 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, compositingIgnored. globalCompositeOperation: 'destination-out' warns: canvas erases where the terminal draws, so that geometry renders inverted rather than merely degraded.

Exporting ​

typescript
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 URL

A 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.

  • @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 ​

MIT

Classes ​

ClassDescription
BrailleRasterizerBraille-dot rasterizer. Each terminal cell encodes a 2×4 grid of sub-pixel dots via Unicode braille patterns (U+2800–U+28FF).
TerminalContextTerminal rendering context that rasterizes Ripl elements into character-based output via a TerminalOutput adapter.
TerminalPathTerminal path implementation that records drawing commands for later rasterization.

Interfaces ​

InterfaceDescription
BrailleRasterizerOptionsOptions for constructing a BrailleRasterizer.
ClipMaskA raster-space stencil restricting where a context may draw.
ContourContextContour-building state passed to a command's toContour handler.
GlyphRunA glyph run laid out onto the character grid.
GlyphRunOptionsOptions describing the text run to lay out.
RasterContextRasterization state passed to a command's rasterize handler.
RasterizerAbstract rasterizer interface for converting pixel data to terminal characters.
SerializeOptionsOptions controlling how a rasterizer serializes its grid to a string.
TerminalCommandHandlerA path command's two rendering passes: contour flattening (for fills) and outline rasterization.
TerminalContextOptionsOptions for constructing a terminal rendering context.
TerminalOutputAbstract terminal output interface, runtime-agnostic.
TerminalPathCommandA recorded drawing command with its type and parameters.
TerminalTransformThe mapping from the logical space a scene is authored in onto the raster grid, composing the context's letterbox with whatever transform is current.
VertexA 2D point in raster (pixel) space.

Type Aliases ​

Type AliasDescription
PixelCallbackCallback invoked for each pixel in a rasterization pass.
TerminalColorA 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.
TerminalPathCommandTypeTypes of drawing commands recorded by a terminal path.

Variables ​

VariableDescription
ANSI_RESETANSI SGR reset sequence.
BRAILLE_CELL_HEIGHTEach braille cell is 2 pixels wide and 4 pixels tall.
BRAILLE_CELL_WIDTHEach braille cell is 2 pixels wide and 4 pixels tall.
TERMINAL_COMMAND_HANDLERSDispatch table keyed by path command type, driving both rendering passes.

Functions ​

FunctionDescription
clipPixelsGates a plot callback on a clip mask, dropping every pixel outside it.
colorToAnsiBgConverts a CSS color string to an ANSI truecolor background escape sequence. Resolution and return values match colorToAnsiFg.
colorToAnsiFgConverts 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.
createClipMaskBuilds a clip mask covering the interior of the given contours, intersected with an existing mask.
createContextCreates a terminal rendering context bound to the given output adapter.
createTerminalTransformComposes a letterbox with a transform and wraps the result as a TerminalTransform.
dashPixelsWraps a plot callback so pixels landing in a dash gap are dropped.
estimateArcStepsEstimates a reasonable number of line segments for an arc of the given raster-space radius.
estimateEllipseStepsEstimates a reasonable number of line segments for an ellipse of the given raster-space radii.
fillPolygonFills 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.
flattenArcSamples an arc from startAngle to endAngle into a polyline of points (inclusive of both endpoints).
flattenCubicBezierSamples a cubic bezier curve into a polyline of points (inclusive of both endpoints).
flattenEllipseSamples an ellipse outline into a polyline of points.
flattenQuadBezierSamples a quadratic bezier curve into a polyline of points (inclusive of both endpoints).
isPointInContoursTests whether a point falls inside one or more closed contours.
isPointOnContoursTests whether a point falls within width of any edge of the given contours.
layoutGlyphRunLays a text run onto the character grid, honoring the current transform.
letterboxMatrixBuilds 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.
normalizeArcSweepResolves an arc's signed sweep, wrapping the end angle by a full turn when the requested direction contradicts the angles as given.
normalizeEllipseSweepResolves an ellipse's signed sweep. A full turn is reported as a whole turn regardless of direction, matching how flattenEllipse closes the curve implicitly.
rasterizeArcRasterizes an arc from startAngle to endAngle at (cx,cy) with given radius by subdivision into line segments.
rasterizeCircleRasterizes a circle outline at (cx,cy) with the given radius using the midpoint algorithm.
rasterizeCubicBezierRasterizes a cubic bezier curve by adaptive subdivision into line segments.
rasterizeEllipseRasterizes an ellipse outline at (cx,cy), honoring rotation and sweep.
rasterizeLineRasterizes a line segment from (x0,y0) to (x1,y1) using Bresenham's algorithm.
rasterizePolygonRasterizes 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.
rasterizePolylineRasterizes a polyline by drawing a line between each consecutive pair of points.
rasterizeQuadBezierRasterizes a quadratic bezier curve by adaptive subdivision into line segments.
rasterizeRectRasterizes a rectangle outline.
resolveTerminalPaintResolves a CSS paint string and a context opacity into the color a pixel should be painted with.
thickenPixelsWidens 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.