chenglou / chenglou/pretext

Feature request: pluggable measure function for non-browser environments

Open
#34 3 comments 7 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
50.3k
Forks
2.7k
Avg merge
2h 40m
Merged PRs (30d)
69

Description

## Use case

I'm building a terminal UI framework (React/Ink-based) that needs fast text measurement for scrollable content. Today we spin up a full layout engine render per child component just to count how many lines text wraps to at a given width. This is expensive and we'd love to replace it with pure-arithmetic measurement like Pretext offers.

The problem: terminals use **monospace fixed-width cells**, not proportional fonts. Each character is either 1 or 2 cells wide (fullwidth CJK, emoji). There are no sub-pixel measurements, no kerning, no ligatures. The standard way to measure terminal text width is via `string-width`, which returns an integer cell count.

Since `prepare()` bakes in `canvas.measureText()` pixel-based measurements, the computed line breaks don't match what a terminal renderer would produce. We can't use Pretext today without hacking around this mismatch.

## Proposal

Expose an optional custom measure function in `prepare()` and `prepareWithSegments()`:

```ts
const prepared = prepare(text, {
measure: (segment: string) => stringWidth(segment), // terminal cell count
})
const { lineCount } = layout(prepared, 80, 1) // 80 columns, 1 row per line
```

When `measure` is provided, Pretext would use it instead of `canvas.measureText()` for segment width computation. Everything else (grapheme segmentation, word break opportunities, bidi handling, whitespace normalization, line breaking algorithm) stays the same.

## Why this matters beyond terminals

A pluggable measure function would unlock Pretext for any environment where text width isn't measured by browser Canvas:

- **Terminal UIs** (Ink, Blessed, terminal-kit) - monospace cell counting
- **Server-side rendering** - no Canvas/DOM available, use font metrics from a table
- **Game engines** - custom bitmap font renderers with their own metrics
- **PDF generation** - font metrics from font files, not Canvas
- **Testing/CI** - deterministic measurement without Canvas polyfills
- **Custom font renderers** - WebGL text, SDF fonts, etc.

Pretext's real value is the text processing pipeline: segmentation, bidi, line breaking, whitespace normalization. The measurement backend is the part that ties it to browsers. Decoupling these two concerns would make the library much more broadly useful.

## What the API could look like

Minimal approach - just a `measure` option:

```ts
// Browser (current behavior, unchanged)
const prepared = prepare('Hello world', '16px Inter')

// Terminal
const prepared = prepare('Hello world', {
measure: (segment) => stringWidth(segment),
})

// Custom font with known metrics
const prepared = prepare('Hello world', {
measure: (segment) => myFontMetrics.getWidth(segment),
})
```

The `font` string parameter could become optional when `measure` is provided, since the custom function encapsulates all width knowledge.

## Context

I work on a large-scale terminal application with scrollable views, virtualized lists, and text selection. We currently run a full React/Yoga render pipeline per component just to pre-compute line counts for scroll math. Pretext's `prepare()` + `layout()` pattern is exactly what we need architecturally, but the Canvas dependency blocks adoption. Happy to contribute if this direction makes sense.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by tracing prepare(), prepareWithSegments(), and layout(), then find where canvas.measureText() supplies segment widths. Check how an optional measure function could flow through preparation without changing the existing browser path. Done means custom widths affect line breaking and layout while segmentation, bidi handling, whitespace normalization, and other existing behavior remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
backend-api-design
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.