import { s as FontSpec } from "./displayList.js"; import { J as TextMeasurer, a as GraphemeBox, g as TextProps, h as Text, l as LineBox, o as Group, q as MeasurerRequiredError, y as WordBox } from "./nodes.js"; import { o as TextCursor, s as TextCursorProps, t as EditMark } from "./typewriter.js"; import { EaseSpec, Track } from "@glissade/core"; //#region src/captionSplit.d.ts /** * Thrown when a SINGLE word/token is too wide to fit the target width even at the * given (min-legible) font — it cannot be split further, so the author must * intervene rather than the split silently degrading legibility or dropping words. * Names the offending token + the fixes in priority order (reword the token FIRST — * it is almost always a URL / long compound the author should shorten; widening the * band or lowering the min font trade the legibility the split exists to protect, so * they come last). */ declare class TextFitError extends Error { readonly token: string; readonly maxWidth: number; readonly fontSize: number; constructor(token: string, maxWidth: number, fontSize: number); } interface SplitToFitOpts { /** Target wrap width in px (the caption band width). */ maxWidth: number; /** * The font to measure + wrap at. Pass the MIN-LEGIBLE font (the shrink floor) so * the pieces are guaranteed to fit even at the smallest size the render may use — * the render's auto-shrink then lands at a font ≥ this that still fits. */ font: FontSpec; /** Lines a piece may wrap to before it must split further. Default 2. */ maxLines?: number; /** * The measurer for the fit decision. Defaults to the process fallback. MUST be the * SAME measurer the render lays out with (measure-consistency) — else a piece judged * "fits" can overflow at render. */ measurer?: TextMeasurer; /** * measurer-fail-loud OPT-OUT — and a STRONGER caveat than `splitText`'s. The split * decides "this piece fits the band", a promise the RENDER must honor; an * estimate-split (`length×0.52`) rendered with real metrics MAY OVERFLOW the band. * So by default no real measurer THROWS `MeasurerRequiredError`; `estimate: true` * accepts the rough estimate AND the overflow risk (pieces fit only if the render * also estimates — pass a real measurer for a guaranteed fit). Default false. */ estimate?: boolean; /** * BCP-47 locale for boundary detection (sentence + word `Intl.Segmenter`). Threads * to the segmenter so zh 。!? sentence marks and space-less word boundaries are * detected correctly. Default: the host default locale. */ locale?: string; } /** * Split `text` into sequential pieces each of which wraps to ≤ `maxLines` within * `maxWidth` at `font`. Splits at the highest-priority boundary present (sentence → * clause → word), greedily packing units into as-large-as-fits pieces. Throws * {@link TextFitError} if a single word can't fit even alone (unsplittable). A text * that already fits returns `[text.trim()]` (single piece) — so a short caption is a * no-op. Pure: same inputs → same pieces (given the same measurer). */ declare function splitToFit(text: string, opts: SplitToFitOpts): string[]; //#endregion //#region src/type.d.ts type SplitBy = 'word' | 'line' | 'grapheme'; interface SplitTextOpts { /** What unit to split into. Default 'word'. */ by?: SplitBy; /** * Stable id prefix for the wrapping group and its parts (`${id}/${i}`). When * omitted, falls back to the source Text's own `id` (and throws if neither is * set — a split needs a stable id namespace to bind tracks against). */ id?: string; /** * The measurer to snapshot part geometry with. Defaults to the source's * injected measurer, then the process fallback — exactly the chain the other * Text geometry getters use. */ measurer?: TextMeasurer; /** * measurer-fail-loud OPT-OUT. By DEFAULT a split with no real measurer THROWS * `MeasurerRequiredError` (the silent per-character estimate drifts from render). * Set `estimate: true` to accept the rough estimate instead (a deterministic, * deliberately-rough snapshot) — the SOLE opt-in. Default false (fail loud). */ estimate?: boolean; } /** One part of a split, in the source Text's draw space (group-local coords). */ interface SplitPart { /** * The part's registered node id — `${id}/${i}`, the SAME string the child * Text was constructed with. Bind a track straight against it: * `parts.map((p) => p.id + '/revealFraction')` (or use `result.targets(prop)`). */ id: string; /** The part's text (a word, a full line, or a single grapheme). */ text: string; /** The generated child node (a left-aligned Text positioned at the part). */ node: Text; /** Laid-out line index the part came from. */ line: number; /** Part ink box, in the source's draw space (== the child's group-local box). */ box: { x: number; y: number; w: number; h: number; }; } interface SplitTextResult { /** The wrapping group (`id`), positioned where the source sat — draw THIS. */ node: Group; /** The generated part children, in reading order. */ children: Text[]; /** Per-part geometry + node, in reading order. */ parts: SplitPart[]; /** * Ready-to-bind track targets — `[`${id}/0/${prop}`, `${id}/1/${prop}`, …]` * in reading order. The blessed kinetic-typography recipe is one line: * `tl.stagger(result.targets('revealFraction'), { from: 0, to: 1 }, { each: 0.1 })`. */ targets(prop: string): string[]; } declare class SplitTextError extends Error { constructor(message: string); } /** * Expand a Text (instance or props) into a `Group` of positioned per-part child * Texts — one per word / line / grapheme. PURE build-time expansion to ordinary * nodes; the part geometry is a STATIC snapshot of the source's current layout. * * const split = splitText(title, { by: 'word', id: 'title', measurer }); * // scene children: [split.node] (REPLACES the original title) * // animate each word: split.targets('revealFraction') === ['title/0/revealFraction', …] * tl.stagger(split.targets('revealFraction'), { from: 0, to: 1 }, { each: 0.1 }); * * Bind tracks against `split.targets(prop)` (ready ids, reading order) or * `parts[i].id` / `parts[i].node` directly. `{ measurer }` is required for exact * part geometry — see the dev-warning footgun below. */ declare function splitText(source: Text | TextProps, opts?: SplitTextOpts): SplitTextResult; interface FitTextOpts { /** wrap/measure width (px). Required — text wraps to this and never exceeds it. */ maxW: number; /** cap the wrapped height (px). Optional — combine with maxLines. */ maxH?: number; /** cap the number of wrapped lines. Optional. */ maxLines?: number; /** never shrink below this (px). Default 6. Below it, fitText throws (fail loud). */ minPx?: number; /** if the text can't fit even at minPx: 'throw' (default) or 'clamp' to minPx. */ onOverflow?: 'throw' | 'clamp'; /** measurer for exact fit — pass one (or call setTextMeasurer first). With NO real * measurer, fitText THROWS `MeasurerRequiredError` (measurer-fail-loud) unless * `{ estimate: true }` opts into the rough estimate. */ measurer?: TextMeasurer; /** * measurer-fail-loud OPT-OUT (as splitText): accept the rough per-character * estimate instead of throwing when no real measurer is available. The SOLE * opt-in; default false (fail loud). */ estimate?: boolean; } /** * The largest integer-px fontSize ≤ the text's current size at which it fits the * box — via a binary search over `measureWrappedText` (pure, no runtime state). * The build-time answer to "shrink this to fit its container" the hand-rolled * shrink loops re-implemented per component. */ declare function fitTextSize(text: Text, opts: FitTextOpts): number; /** * Shrink `text` to fit its box: sets its `fontSize` to `fitTextSize(...)` and * returns it (a plain `signal.set`, so a later explicit bind still wins — the * Grid()/splitText() mutate-and-return convention). Also sets `width` to maxW so * the node wraps to the same box it was fitted against. */ declare function fitText(text: Text, opts: FitTextOpts): Text; /** * Fit several texts to ONE shared size — the largest px at which EVERY text fits * its box — so a row/list of labels renders uniformly (kills the "same list, three * different sizes" ragged-headers bug). Each text may carry its own maxW; a single * `maxW` applies to all. Returns the shared size. */ declare function fitTextGroup(texts: readonly Text[], opts: FitTextOpts): number; /** Thrown by the kinetic-type presets on a fail-loud condition (missing id, an * out-of-range emphasize index). Same fail-loud class as {@link SplitTextError}. */ declare class KineticTypeError extends Error { constructor(message: string); } interface TypeOnOpts { /** Seconds per grapheme (keystroke cadence). Default 0.06 (typewriter's default). */ perChar?: number; /** Absolute timeline start of the first keystroke (seconds). Default 0. */ start?: number; /** * OPT-IN: attach a {@link TextCursor} sibling that rides the reveal/type head. * RENDER-ONLY (custom draw) — NOT bundled by default so the default typeOn stays * Lottie-faithful; the exporter warns+drops the caret node. Add BOTH `.node` and * `.cursor` to the scene (`children: [r.node, r.cursor]`). */ cursor?: boolean; /** * OPT-IN: reveal via the grapheme MASK (`Text.reveal`) instead of the string * track. RENDER-ONLY — Skia-identical but the exporter drops+warns "reveal is * not exported" (the 0.55 trap, carried honestly). The default (mask off) uses * the string hold-key track, which ROUND-TRIPS as stepped Lottie text documents. */ mask?: boolean; /** Caret width px when `cursor: true` (passthrough to textCursor). */ cursorWidth?: number; /** Caret blink period seconds when `cursor: true` (passthrough to textCursor). */ blinkPeriod?: number; /** * Caret COLOR when `cursor: true` (passthrough to the textCursor sibling's `fill`). * Default '' = follow the Text's own fill; set a hex/PropInit for a deliberately * contrasting caret (bindable via the `/cursor/fill` track). Ignored without `cursor`. */ cursorFill?: TextCursorProps['fill']; /** * Escape hatch: any other {@link TextCursor} construction prop (e.g. `blinkPeriod`, * `width`, or a NodeProp) forwarded to the caret sibling when `cursor: true`. The * explicit `cursorWidth`/`blinkPeriod`/`cursorFill` options win over the matching * key here, and the caret's `id` (`/cursor`) is always set by typeOn. Ignored * without `cursor`. */ cursorProps?: Omit; } interface TypeOnResult { /** The Text to draw. In the DEFAULT (string-track) mode its `text` is driven by * `track`; in `mask` mode it keeps its full text and `reveal` masks it. */ node: Text; /** Present only with `{ cursor: true }` — the caret sibling (render-only). */ cursor?: TextCursor; /** The single track to inject (`tl.tracks([r.track])`): a STRING hold-key track * on `/text` (default) or a NUMBER `/reveal` grapheme-mask track (mask). */ track: Track; /** Every keystroke (insert), for keystroke SFX — see keystrokeClips. */ marks: EditMark[]; /** Time of the last keystroke — the performance's end (seconds). */ duration: number; } /** * One-call typewriter. Wraps the shipped `typewriter()` so a whole Text types in * with a single call, optionally with a caret and/or a grapheme mask. * * const t = typeOn({ id: 'prompt', text: 'make it pop', fontSize: 40 }, { cursor: true }); * // scene children: [t.node, t.cursor] timeline: tl.tracks([t.track]) * * DEFAULT (no mask) = the STRING hold-key track on `/text` (delegated to * `typewriter()`); it ROUND-TRIPS to Lottie as stepped text documents. `mask:true` * swaps to a `/reveal` grapheme mask (render-only, export warns). `cursor:true` * adds a render-only caret sibling (export warns). */ declare function typeOn(source: Text | TextProps, opts?: TypeOnOpts): TypeOnResult; /** Direction a part enters from in `revealWords`/`revealLines`. */ type RevealFrom = 'below' | 'above' | 'fade'; interface RevealOpts { /** Per-part cascade delay (seconds). Default 0.08. */ each?: number; /** Entrance style: rise from `'below'`, drop from `'above'`, or `'fade'` only. Default 'fade'. */ from?: RevealFrom; /** Position offset px for 'below'/'above'. Default 24. */ distance?: number; /** Per-part tween duration (seconds). Default 0.4. */ duration?: number; /** Arriving ease. Default 'easeOutCubic'. */ ease?: EaseSpec; /** Absolute start of the cascade (seconds). Default 0. */ at?: number; /** Stable id namespace (else the source Text's id; throws if neither). */ id?: string; /** Measurer for exact part geometry (like splitText). */ measurer?: TextMeasurer; /** measurer-fail-loud opt-out (like splitText): accept the rough estimate instead * of throwing when no real measurer is available. Default false (fail loud). */ estimate?: boolean; } interface RevealResult { /** The splitText Group — draw THIS (never the source: the .node-not-.source * footgun is hidden here). */ node: Group; /** Real opacity (+ position) tracks — inject with `tl.tracks(result)`. ✅ round-trips. */ tracks: Track[]; } /** Split a Text into WORDS and cascade each in (opacity, optionally rising/dropping * into place). Additive first-class — REAL tracks, so it round-trips to Lottie. */ declare function revealWords(source: Text | TextProps, opts?: RevealOpts): RevealResult; /** Split a Text into LINES and cascade each in. Real tracks; round-trips to Lottie. */ declare function revealLines(source: Text | TextProps, opts?: RevealOpts): RevealResult; interface EmphasizeOpts { /** Peak scale of the pulse. Default 1.15. */ scale?: number; /** Per-word pulse duration (seconds). Default 0.4. */ duration?: number; /** Delay between successive emphasized words (seconds). Default 0.12. */ each?: number; /** Ease of the up/down halves. Default 'easeInOutSine'. */ ease?: EaseSpec; /** Absolute start (seconds). Default 0. */ at?: number; /** Split unit to index against. Default 'word'. */ by?: 'word' | 'grapheme'; /** Stable id namespace (else the source Text's id). */ id?: string; /** Measurer for exact part geometry. */ measurer?: TextMeasurer; /** measurer-fail-loud opt-out (like splitText): accept the rough estimate instead * of throwing when no real measurer is available. Default false (fail loud). */ estimate?: boolean; } /** * Pulse (scale up-and-back) the words at `indices` in reading order, cascaded. * FAIL-LOUD: an out-of-range or non-integer index THROWS (never silently ignored). * Real scale tracks → round-trips to Lottie. */ declare function emphasizeWords(source: Text | TextProps, indices: readonly number[], opts?: EmphasizeOpts): RevealResult; //#endregion export { EmphasizeOpts, FitTextOpts, type GraphemeBox, KineticTypeError, type LineBox, MeasurerRequiredError, RevealFrom, RevealOpts, RevealResult, SplitBy, SplitPart, SplitTextError, SplitTextOpts, SplitTextResult, type SplitToFitOpts, TextFitError, TypeOnOpts, TypeOnResult, type WordBox, emphasizeWords, fitText, fitTextGroup, fitTextSize, revealLines, revealWords, splitText, splitToFit, typeOn };