Skip to content

CommandScreen::TextSize estimate causes compounding label overlap (jumbled text) at deep zoom — regression vs v5 real font metrics #56

Description

@willwade

User-visible symptom (Dasher-Windows v0.1.14 → v0.1.15, user reports Aug 17 + Aug 21 2026)

"When I type a long sentence, the next letters to form a word get all jumbled up. Sometimes it gets so bad that I can't type any further."
"As a sentence grows, the letters and predictive boxes are still getting completely squished, jumbled, and cut off right along the right-hand border of the canvas."

The same user runs Dasher v5 on Windows without any problem — this is a v6 regression. She uses Direct Entry as her primary mode (power user, long sentences = deepest exposure).

Root cause

All C-API frontends render the command buffer, but label layout decisions happen engine-side using CDasherScreen::TextSize(). The CAPI implementation is a crude estimate:

src/CAPI.cpp:221

std::pair<screenint, screenint> TextSize(Label* label, unsigned int iFontSize) override {
    return {label->m_strText.size() * iFontSize / 2, iFontSize};
}

Two defects:

  1. Per-glyph advance underestimate. size/2 assumes every glyph advances 0.5em. Segoe UI / common UI fonts: 'w'/'m' ≈ 0.75–0.85em, uppercase ≈ 0.65–1.0em, digits ≈ 0.55em. Deep-zoom labels are single characters, so the worst glyphs are ~2× wider than estimated.
  2. Bytes, not code points. m_strText.size() counts UTF-8 bytes: 'é' counts twice (over-shove), fine only for pure ASCII.

The estimate feeds the label anti-overlap "shunting" in DasherViewSquare.cpp DoDelayedText():

const screenint iRight = x + textDims.first;          // estimated right edge
...
pChild->m_ix = std::max(pChild->m_ix, iRight);        // shove child past parent
DoDelayedText(pChild, ...);                            // child shoves ITS children...

Every generation shifts only by the parent's estimated width, so errors compound along the ancestor label chain. As the user writes more context, the visible subtree deepens, the label chains lengthen, and the right-hand frontier (where the next-letter boxes live) degenerates into overlapping text — matching "as a sentence grows … so bad I can't type any further".

Why v5 was fine

v5's platform screens implemented TextSize with real font metrics (GTK: Pango; Win32: GetTextExtentPoint32; macOS: NSString size). The rewrite moved rendering behind the command buffer and replaced metrics with the estimate — documented as a known limitation in docs/C_API.md ("No font rendering — text width is estimated as characters × fontSize / 2") but never connected to this user-visible failure.

Impact

Frontend Affected?
Dasher-Windows (Avalonia) Yes — reported (Segoe UI)
Dasher-GTK (Cairo) Yes in principle (shared estimate; severity depends on font/glyph mix)
Dasher-Android (Compose) Yes in principle
dasher-web Yes in principle
v5 No — real metrics

Fix options

  1. Frontend-supplied metrics (proper fix, matches v5): add a C API for the frontend to provide text measurement — e.g. dasher_set_text_size_callback(ctx, int (*measure)(const char* text, int font_size, int* width, int* height)) — so the engine's layout decisions use the actual font the canvas will draw with. One callback, called only when labels change size bucket (cacheable in CDasherScreen::Label, which already exists for exactly this purpose).
  2. Better constant (band-aid): count UTF-8 code points and raise the factor to ~0.6, with a wider multiplier for uppercase/wide glyphs. Reduces but cannot eliminate overlap (real fonts vary ±30% per glyph).
  3. Strand 2 (RFC 0013): frontends that take over rendering entirely (dasher_get_visible_nodes) can measure text themselves — but the default command-buffer path still needs 1 or 2.

Suggested: option 1. CDasherScreen::Label was designed as the metrics cache seam; the C-API layer just never wired it to the frontend.

Repro (any command-buffer frontend)

  1. Alphabet: English with limited punctuation; font size default (22).
  2. Write a long sentence (30+ chars) continuously without pausing.
  3. Watch the right-hand frontier: descendant labels progressively overlap until individual next-letter boxes are illegible.

Severity: high for power users (primary text-entry path); cosmetic at short text.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions