Context
Flitter's text layout pipeline (TextPainter → Paragraph.layout()) re-measures every word via canvas.measureText() on every layout pass with zero caching. chenglou/pretext (MIT, TypeScript) demonstrates a proven two-phase architecture: expensive prepare() once, then cheap pure-arithmetic layout() on resize. We should port its core strategies into Flitter's text engine.
Current Pain Points
getTextWidth() calls measureText() per word, every layout pass, no cache
- TextField splits text into per-character TextSpans → N measureText calls per keystroke
- Font string (
"400 14px Arial") reconstructed on every measurement
maxLines / ellipsis properties stored but never implemented
- Intrinsic size (
getIntrinsicWidth/Height) calls layout() unconditionally, no caching
- Word boundary splitting uses naive regex (
/\S+|\s+/g) — no CJK, hyphen, or kinsoku support
Key Files
packages/core/src/utils/getTextSize.ts — measureText calls (line 38-67)
packages/core/src/type/_types/text-painter.ts — layout engine (464 lines)
packages/core/src/component/base/BaseRichText.ts — RenderParagraph (343 lines)
packages/core/src/component/TextField.ts — per-char span splitting (line 240-246)
packages/core/src/type/_types/text-span.ts — InlineSpan (64 lines)
Reference: Pretext Architecture
src/measurement.ts — two-level cache Map<font, Map<text, width>>
src/analysis.ts — Intl.Segmenter for language-aware word boundaries, CJK/kinsoku
src/line-break.ts — greedy line breaker with pending-break tracking, soft hyphen, overflow-wrap
src/layout.ts — prepare/layout two-phase orchestration
Checklist
Phase 1: Measurement Caching (High Impact, Low Risk)
Phase 2: Prepare/Layout Separation (Core Refactor)
Phase 3: TextField Optimization
Phase 4: Word Boundary & i18n (Pretext Port)
Phase 5: maxLines & Ellipsis Implementation
Phase 6: Intrinsic Size Caching
Completion Criteria
- All checkboxes above completed
pnpm run chromatic passes at every phase (no side effects)
- No visual regression in existing text rendering
- Text-heavy scenarios (charts with many labels, TextField input) show measurable perf improvement
Context
Flitter's text layout pipeline (
TextPainter → Paragraph.layout()) re-measures every word viacanvas.measureText()on every layout pass with zero caching. chenglou/pretext (MIT, TypeScript) demonstrates a proven two-phase architecture: expensiveprepare()once, then cheap pure-arithmeticlayout()on resize. We should port its core strategies into Flitter's text engine.Current Pain Points
getTextWidth()callsmeasureText()per word, every layout pass, no cache"400 14px Arial") reconstructed on every measurementmaxLines/ellipsisproperties stored but never implementedgetIntrinsicWidth/Height) callslayout()unconditionally, no caching/\S+|\s+/g) — no CJK, hyphen, or kinsoku supportKey Files
packages/core/src/utils/getTextSize.ts— measureText calls (line 38-67)packages/core/src/type/_types/text-painter.ts— layout engine (464 lines)packages/core/src/component/base/BaseRichText.ts— RenderParagraph (343 lines)packages/core/src/component/TextField.ts— per-char span splitting (line 240-246)packages/core/src/type/_types/text-span.ts— InlineSpan (64 lines)Reference: Pretext Architecture
src/measurement.ts— two-level cacheMap<font, Map<text, width>>src/analysis.ts—Intl.Segmenterfor language-aware word boundaries, CJK/kinsokusrc/line-break.ts— greedy line breaker with pending-break tracking, soft hyphen, overflow-wrapsrc/layout.ts— prepare/layout two-phase orchestrationChecklist
Phase 1: Measurement Caching (High Impact, Low Risk)
Map<fontString, Map<segmentText, number>>cache ingetTextSize.ts"${weight} ${size}px ${family}"every callpnpm run chromaticpasses — no visual regressionPhase 2: Prepare/Layout Separation (Core Refactor)
prepare(text, style)phase in TextPainter that pre-measures all segments and stores width arraysParagraph.layout(width)to operate on cached width arrays only — zeromeasureText()callspnpm run chromaticpassesPhase 3: TextField Optimization
TextField.ts:240-246pnpm run chromaticpassesPhase 4: Word Boundary & i18n (Pretext Port)
/\S+|\s+/gregex withIntl.Segmenter(granularity: 'word')Intl.Segmenterpnpm run chromaticpassesPhase 5: maxLines & Ellipsis Implementation
maxLinesenforcement inParagraph.layout()— stop layout after N linesTextOverflow.ellipsis— truncate last visible line and append ellipsispnpm run chromaticpassesPhase 6: Intrinsic Size Caching
getIntrinsicWidth()/getIntrinsicHeight()results per RenderParagraphpnpm run chromaticpassesCompletion Criteria
pnpm run chromaticpasses at every phase (no side effects)