Skip to content

Text layout optimization: port pretext-style algorithms #122

@Moon-DaeSeung

Description

@Moon-DaeSeung

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.tsIntl.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)

  • Add two-level Map<fontString, Map<segmentText, number>> cache in getTextSize.ts
  • Pool font strings — avoid reconstructing "${weight} ${size}px ${family}" every call
  • Add cache size limit (LRU or periodic eviction) to prevent memory leaks
  • pnpm run chromatic passes — no visual regression

Phase 2: Prepare/Layout Separation (Core Refactor)

  • Introduce prepare(text, style) phase in TextPainter that pre-measures all segments and stores width arrays
  • Refactor Paragraph.layout(width) to operate on cached width arrays only — zero measureText() calls
  • Ensure resize/re-layout only runs the cheap arithmetic path
  • pnpm run chromatic passes

Phase 3: TextField Optimization

  • Stop splitting text into per-character TextSpans in TextField.ts:240-246
  • Use single TextSpan + character position calculation from cached segment widths
  • Verify keystroke performance improvement (N measureText → 0 on cached text)
  • pnpm run chromatic passes

Phase 4: Word Boundary & i18n (Pretext Port)

  • Replace /\S+|\s+/g regex with Intl.Segmenter (granularity: 'word')
  • Add CJK per-grapheme breaking support
  • Add kinsoku shori rules (Japanese line-start/end prohibition)
  • Add soft hyphen support
  • Fallback for browsers without Intl.Segmenter
  • pnpm run chromatic passes

Phase 5: maxLines & Ellipsis Implementation

  • Implement maxLines enforcement in Paragraph.layout() — stop layout after N lines
  • Implement TextOverflow.ellipsis — truncate last visible line and append ellipsis
  • Skip measurement of text beyond truncation point (perf win)
  • pnpm run chromatic passes

Phase 6: Intrinsic Size Caching

  • Cache getIntrinsicWidth() / getIntrinsicHeight() results per RenderParagraph
  • Invalidate cache only when text content or style actually changes
  • pnpm run chromatic passes

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions