Bug
Cursor position in TextField drifts rightward when typing wide Cyrillic characters (ы, ш, щ, ж). Narrow Latin characters track correctly. After 10+ wide characters the drift reaches 5-10px.
Reproduce
go run ./examples/gallery/
- Click TextField
- Type
ыыыыыыыыыыыыыыыыыы (hold ы key)
- Cursor visually drifts ahead of the actual insertion point
Root Cause
Two separate bugs in two repos:
1. Base offset rounding mismatch (ui)
DrawText rounds the starting X position: math.Round(contentRect.Min.X) (internal/render/canvas.go:488).
CursorX uses raw contentRect.Min.X without rounding (internal/textmetrics/textmetrics.go:35).
Creates a constant cursor-to-text offset of up to 0.5px.
2. Per-glyph hinted/raw advance mismatch (gg#479)
MeasureText → face.Advance() returns raw unhinted per-glyph advances (text/face.go:88).
DrawText → drawGlyphs → hintedOrRawAdvance() returns TT-hinted advances (text/draw.go:136).
Hinting adjusts wide Cyrillic characters (ш: 3 vertical stems, щ: 4, ж: 5) to pixel grid. Each glyph 0.5-1px wider/narrower than raw advance. After 10 characters → 5-10px cumulative drift.
GPU GlyphMask path is NOT affected — both measurement and rendering use raw advances.
Enterprise reference
Flutter (TextPainter + HarfBuzz), Qt (QFontMetrics + QTextEngine), Chrome (Font::width + ShapeResult) — all use a single shaped result for both rendering and cursor positioning.
Fix
Environment
- Windows 10, Vulkan backend
- v0.1.50+ (main, post SceneCache refactoring)
- Inter font (has TT bytecode hints)
- Default hinting:
HintingFull
Bug
Cursor position in TextField drifts rightward when typing wide Cyrillic characters (ы, ш, щ, ж). Narrow Latin characters track correctly. After 10+ wide characters the drift reaches 5-10px.
Reproduce
go run ./examples/gallery/ыыыыыыыыыыыыыыыыыы(hold ы key)Root Cause
Two separate bugs in two repos:
1. Base offset rounding mismatch (ui)
DrawTextrounds the starting X position:math.Round(contentRect.Min.X)(internal/render/canvas.go:488).CursorXuses rawcontentRect.Min.Xwithout rounding (internal/textmetrics/textmetrics.go:35).Creates a constant cursor-to-text offset of up to 0.5px.
2. Per-glyph hinted/raw advance mismatch (gg#479)
MeasureText→face.Advance()returns raw unhinted per-glyph advances (text/face.go:88).DrawText→drawGlyphs→hintedOrRawAdvance()returns TT-hinted advances (text/draw.go:136).Hinting adjusts wide Cyrillic characters (ш: 3 vertical stems, щ: 4, ж: 5) to pixel grid. Each glyph 0.5-1px wider/narrower than raw advance. After 10 characters → 5-10px cumulative drift.
GPU GlyphMask path is NOT affected — both measurement and rendering use raw advances.
Enterprise reference
Flutter (TextPainter + HarfBuzz), Qt (QFontMetrics + QTextEngine), Chrome (Font::width + ShapeResult) — all use a single shaped result for both rendering and cursor positioning.
Fix
CursorXandRuneIndexFromXto matchDrawText. ~20 LOC.Advance()must return hinted advances when hinting is active. ~40-60 LOC.Environment
HintingFull