API Reference

Pretext API Reference: Every Public Function Documented

This page is the API reference for Pretext (@chenglou/pretext), the pure-JavaScript text layout library. It documents every public function — signature, behavior, parameters, return values, examples, and the gotchas that show up in production. If the official README is the marketing pitch, this is the spec sheet.

The API is intentionally small. Eight exported functions cover everything from a simple text height query to per-line iteration with variable widths. The split is two core functions you'll use 95% of the time, four advanced ones for richer cases, and two utility functions for cache and locale control.

Module Imports

import {
  // Core
  prepare,
  layout,
  layoutWithLines,
  // Advanced
  prepareWithSegments,
  walkLineRanges,
  layoutNextLine,
  // Utility
  clearCache,
  setLocale,
} from '@chenglou/pretext';

The package is ESM-first with a CJS fallback. Tree-shaking works cleanly — importing only what you use means the bundle only includes what you imported.

prepare(text, font, options?)

The one-time measurement call. Tokenizes text, identifies break opportunities using Unicode segmentation rules, measures glyph widths via canvas measureText(), and returns an opaque "prepared" object that subsequent layout calls operate on.

Signature

function prepare(
  text: string,
  font: string,
  options?: PrepareOptions
): Prepared;

interface PrepareOptions {
  whiteSpace?: 'normal' | 'pre-wrap';  // default 'normal'
  wordBreak?: 'normal' | 'break-all';  // default 'normal'
}

Parameters

Returns: an opaque Prepared object. Don't introspect its shape; pass it to the layout functions.

Example

const prepared = prepare(
  "The quick brown fox jumps over the lazy dog.",
  "16px Georgia"
);

Cost: ~0.04ms per short string on a modern laptop, or roughly 19ms for 500 distinct strings. Call once per (text, font) combination and cache.

Gotchas

layout(prepared, maxWidth, lineHeight?)

The hot-path function. Pure arithmetic over cached data. Returns the height and line count for the prepared text laid out at a given width.

Signature

function layout(
  prepared: Prepared,
  maxWidth: number,
  lineHeight?: number  // default 1.2
): LayoutResult;

interface LayoutResult {
  height: number;     // in CSS pixels
  lineCount: number;
}

Parameters

Returns: { height, lineCount }.

Example

const { height, lineCount } = layout(prepared, 320, 1.5);
// height: 144  lineCount: 6

Cost: microseconds. Safe to call inside requestAnimationFrame for every visible row of a long list.

Gotchas

layoutWithLines(prepared, maxWidth, lineHeight?)

Same inputs as layout(), but returns per-line geometry.

Signature

function layoutWithLines(
  prepared: Prepared,
  maxWidth: number,
  lineHeight?: number
): LayoutWithLinesResult;

interface LineInfo {
  text: string;       // the substring on this line
  width: number;      // actual rendered width in CSS pixels
  start: number;      // character index in original text (inclusive)
  end: number;        // character index in original text (exclusive)
}

interface LayoutWithLinesResult extends LayoutResult {
  lines: LineInfo[];
}

Example

const result = layoutWithLines(prepared, 200, 1.4);
result.lines.forEach((line, i) => {
  console.log(`Line ${i}: "${line.text}" (${line.width}px)`);
});

When to use: rendering text yourself (canvas, SVG, WebGL), highlighting individual lines, building per-line animations.

When not to use: when you only need the total height — layout() is faster because it doesn't allocate the lines array.

prepareWithSegments(text, font, options?)

A richer variant of prepare() that exposes the segmentation data — the individual word, whitespace, break-opportunity, CJK, and emoji segments the engine identified.

Signature

function prepareWithSegments(
  text: string,
  font: string,
  options?: PrepareOptions
): PreparedWithSegments;

interface Segment {
  start: number;
  end: number;
  width: number;
  kind: 'word' | 'whitespace' | 'break-opportunity' | 'cjk' | 'emoji';
}

type PreparedWithSegments = Prepared & {
  readonly segments: readonly Segment[];
};

When to use: tooling that needs to introspect the engine's decisions, syntax highlighters that need per-word geometry, internationalization debugging.

When not to use: regular layout — the segments array is overhead you don't need for plain measurement.

walkLineRanges(prepared, maxWidth, callback)

Iterate over lines without allocating a lines array or substring text. The fastest way to render line-by-line.

Signature

function walkLineRanges(
  prepared: Prepared,
  maxWidth: number,
  callback: (start: number, end: number, lineIndex: number) => void
): void;

Example

walkLineRanges(prepared, 320, (start, end, i) => {
  ctx.fillText(text.slice(start, end), 0, i * lineHeight);
});

When to use: canvas renderers that draw substrings; high-frequency rendering (per-frame); zero-allocation hot paths.

layoutNextLine(prepared, start, maxWidth)

Lay out a single line at a given starting position and width. The building block for variable-width layouts (text flowing around obstacles, multi-column layouts with varying column widths).

Signature

function layoutNextLine(
  prepared: Prepared,
  start: number,         // character index to start from
  maxWidth: number
): NextLineResult;

interface NextLineResult {
  end: number;           // character index where the line ended (exclusive)
  width: number;         // actual width in CSS pixels
}

Example

let cursor = 0;
let y = 0;
while (cursor < text.length) {
  const width = availableWidthAt(y);   // your function: returns column width at this y
  const { end } = layoutNextLine(prepared, cursor, width);
  if (end === cursor) break;            // safety: avoid infinite loop on overflow
  drawLine(text.slice(cursor, end), 0, y);
  cursor = end;
  y += lineHeight;
}

When to use: any layout where the available width changes line-to-line. Magazine layouts, drop-cap text wrap, image-flow text, table-of-contents columns with varying widths.

Gotcha: if maxWidth is too narrow to fit any token, layoutNextLine returns end === start. Always check for this to avoid infinite loops.

clearCache()

Clear the internal measurement cache. Call after a font swap or when you've prepared transient text you don't expect to re-render.

Signature

function clearCache(): void;

When to use: app-wide font theme change, document.fonts.onloadingdone handler, memory pressure.

When not to use: routinely. The cache is the source of Pretext's speed; clearing it forces every subsequent prepare() to re-measure from the canvas font engine.

setLocale(locale?)

Configure the engine's locale for line-break behavior. Some scripts (Japanese, Korean, Thai) have locale-sensitive rules.

Signature

function setLocale(locale?: string): void;  // BCP 47 language tag, e.g. 'ja-JP'

Example

import { setLocale, prepare } from '@chenglou/pretext';

setLocale('ja-JP');
const prepared = prepare("日本語のテキスト", "16px 'Hiragino Sans'");

When to use: localized apps that switch language at runtime; debugging script-specific break behavior.

Gotcha: this is a global setting, not per-call. If you prepare text in multiple locales, set the locale, prepare, then either reset or accept the global state.

Type Exports

The full list of types Pretext exports:

export type {
  Prepared,
  PreparedWithSegments,
  PrepareOptions,
  LayoutResult,
  LayoutWithLinesResult,
  LineInfo,
  NextLineResult,
  Segment,
};

Prepared is intentionally opaque (branded). Don't depend on its internal shape.

Common Patterns

A few small recipes that combine the API:

Pattern: Memoized text measurement

const cache = new Map<string, Prepared>();
function getPrepared(text: string, font: string): Prepared {
  const key = `${font}\u0000${text}`;
  let p = cache.get(key);
  if (!p) {
    p = prepare(text, font);
    cache.set(key, p);
  }
  return p;
}

Pattern: Find optimal container width via binary search

function findWidthForLines(prepared: Prepared, targetLines: number, min = 50, max = 800): number {
  while (max - min > 1) {
    const mid = Math.floor((min + max) / 2);
    const { lineCount } = layout(prepared, mid);
    if (lineCount > targetLines) min = mid;
    else max = mid;
  }
  return max;
}

Pattern: Detect text overflow

function overflowsAt(prepared: Prepared, maxWidth: number, maxHeight: number, lineHeight = 1.4): boolean {
  return layout(prepared, maxWidth, lineHeight).height > maxHeight;
}

What to Read Next

For the architectural deep dive on how prepare() and layout() actually work internally, see the How Pretext Works post. For framework-specific integration, see the Pretext + React guide or the Pretext + TypeScript guide. For copy-pasteable code patterns, see the Pretext Examples page.

The library lives at github.com/chenglou/pretext. The npm package is @chenglou/pretext.


pretext.cool is a community-maintained showcase, not affiliated with Cheng Lou or the official Pretext project.

Related Pages

Try a Live Demo

← Browse all 20 demos