Thing/docs/research/thing2-text-input-a11y-prior-art.md
Meta Vens cbb59574b0 Research: Thing 2.0 text, IME, focus, a11y and GPU glyph prior art (Eyes, 2026-10-10)
Serves question thing❓thing2-text-ownership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:34:08 +02:00

23 KiB

Thing 2.0: text shaping, text input/IME, focus, accessibility, GPU glyphs: prior art (Eyes pass, 2026-10-10)

Serves the fork thing2-text-ownership: who owns text shaping, text editing/IME, keyboard focus and accessibility under the ruling thing:ruling:thing2-one-solver-taffy (typed retained document, one Taffy solver, closed render-command list, thin backends). Options: (a) Thing owns all, backends only draw; (b) hybrid, solver owns geometry and backends place native controls/a11y nodes at solved rects; (c) something unnamed. Item 6 (GPU glyph rasterization) is an operator scope addition.

Read-only facts with evidence pointers. No recommendation. Does not repeat thing2-foundation-prior-art.md (Clay/Taffy/Yoga basics, AccessKit web "planned"). Marks: (inferred) = my juxtaposition; [background] = prior knowledge, not re-fetched. Versions/dates are crates.io max_stable_version/updated_at and GitHub API, read 2026-10-10.


1. Rust text stacks that pair with Taffy

Crate Latest (date) Licence Role
parley 0.12.0 (2026-10-09) Apache-2.0 OR MIT (emoji crate MIT-only) rich-text layout, selection/editing, IME primitives
fontique 0.12.0 (2026-10-09) [background: same repo, Apache-2.0 OR MIT] font enumeration, fallback
cosmic-text 0.19.0 (2026-04-22) Apache-2.0 OR MIT shaping + layout + editor in one crate
harfrust 0.14.0 (2026-10-04) [background: MIT] HarfBuzz port; shaping
rustybuzz 0.20.1 (2024-11-12; no release since) [background: MIT] older HarfBuzz port
swash 0.2.10 (2026-07-17) [background: Apache-2.0 OR MIT] scaling/rasterising
fontdb 0.24.0 (2026-07-29) [background: MIT] font DB
taffy 0.14.0 (2026-08-24) MIT layout
  • parley github.com/linebender/parley README: "provides an API for implementing rich text layout"; shaping by HarfRust, bidi by ICU4X, line breaking in Parley, "utilities for text selection and editing", fontique for fallback, skrifa for outlines, ICU4X for segmentation. 753 stars, MSRV 1.88 (README). Repo tree has parley/src/editing/{editor,selection}.rs and examples/editor/src/{accessibility,access_ids}.rs (AccessKit text example). PRs: "Implement IME primitives for PlainEditor" #136 (closed 2025-01-21), "Add editor features required for Android IME" #334 (2025-06), IME baseline-jitter / text-doubling fixes #599, #601 (2026-04/05). Slint's accessibility-text feature enables parley?/accesskit.
  • cosmic-text github.com/pop-os/cosmic-text: 2.2k stars, 841 commits. README: HarfRust shaping, bidi, swash rendering, own font fallback built from Chromium/Firefox lists; roadmap lists text editing, selection, bidi selection, copy/paste, click detection (page does not say which are done). IME not mentioned. Cargo.toml: harfrust 0.5, skrifa 0.40, fontdb 0.24, unicode-bidi, unicode-linebreak, unicode-segmentation; features no_std, wasm-web.
  • Who uses which (manifests on main, read today):
    • Bevy: bevy_ui -> taffy 0.14, accesskit 0.25; bevy_text -> parley 0.11, swash 0.2.6. IME PRs #23795 / #23841 "Text input IME support" closed 2026-04-21; #24421 "Fix IME text input wrapping" closed 2026-06-06. [background: Bevy used cosmic-text before.]
    • Slint: Taffy 0.10, flexbox feature only (internal/core/Cargo.toml); shared-parley is a default feature; parley 0.11.1; accessibility-text feature; accesskit 0.24, accesskit_winit 0.33.
    • Blitz (DioxusLabs): Stylo (CSS) + Taffy (box layout) + Parley (text layout) per README; blitz-dom has events/ime.rs and accessibility.rs; feature accessibility = ["accesskit"]; blitz-shell = winit + AccessKit + muda. README: "beta ... many bugs and missing features".
    • Xilem/Masonry: README: winit, Parley/Fontique, AccessKit. masonry_core depends on accesskit and parley; widgets text_area.rs, text_input.rs. masonry/xilem 0.4.0 on crates.io are 2025-10-29 (repo pushed 2026-09-14). README: "experimental".
    • GPUI (Zed): crates/gpui depends on taffy and accesskit; font-kit default feature; per-platform text systems (gpui_apple/src/text_system.rs, gpui_wgpu with font-kit, gpui_web/src/{text_system,canvas_text}.rs). AccessKit PRs #54398, #56065, #64212, #64214 all closed (merged vs not not checked), latest 2026-09-24.
    • COSMIC / iced: cosmic-text is pop-os's own crate [background].
    • iocraft (terminal): Taffy 0.5.2 flexbox, unicode-width 0.1.13 (section 5).
    • egui: accesskit workspace dep; own text layout [background].
  • Taffy has no text layout; leaf measure functions supply sizes (prior-art doc 2.2).

2. How Taffy-based toolkits do text input and IME

  • Masonry/Xilem (desktop): IME flows from winit into TextArea. Issues in github.com/linebender/xilem: "Re-add IME support" #762 (closed 2024-11-30), "Ensure that IME events interact with focus as expected" #1004, "Improve IME area reporting and set IME area in TextArea" #785, open #1692 "TextArea: on Ime::Disabled use finish_compose()" (2026-03-26), baseline-jitter fixes #1716 / #1718 (2026-04). Editing buffer is Parley's PlainEditor (inferred from parley #136 + masonry text_area). xilem_web is a separate DOM backend (README: "a web backend and a Masonry backend"); no Masonry-on-canvas web build found.
  • Blitz: blitz-dom/src/events/ime.rs handle_ime_event sends BlitzImeEvent to the focused node's text_input_data_mut().apply_ime_event(font_ctx, layout_ctx, event): text input state lives in the DOM node, not a native control. Shell is winit; README lists no web target.
  • Bevy UI: IME merged 2026-04/06 (above); accessibility in bevy_a11y.
  • Slint: docs.slint.dev/latest/docs/slint/guide/platforms/web: "Accessibility features (such as screen readers) are not available" (WebGL canvas, no DOM); Wasm uses Winit backend + FemtoVG; "running Slint in the browser is currently not recommended for building general-purpose web applications". Text input / IME on web not addressed on that page.
  • GPUI web (crates/gpui_web/src/ime_mirror.rs header): "The hidden <textarea> that connects browser IMEs to GPUI". It mirrors a window of document text (UTF-16 units each side of the selection) into the textarea because IMEs "decide what backspace, autocorrect, and suggestions mean by inspecting the focused editable element's value and selection"; every write makes the browser restart the IME connection, so writes are coalesced to one per event-loop turn. canvas_text.rs rasterises glyphs via OffscreenCanvas 2D (4096 px max raster dimension).
  • egui web: crates/eframe/src/web/text_agent.rs: "a hidden <input> element used to capture IME and mobile keyboard input events", opacity 0, pointer-events none, 1x1 px, parked over the canvas corner "so that focusing it can never scroll some other part of the page into view". PR #8273 (closed 2026-08-23): the agent created "dead zones" swallowing clicks right of the cursor. #8078 (2026-04) "Delegate handling of IME interruptions to integrations to fix virtual keyboard flickering on web". eframe/src/web/screen_reader.rs is Web Speech speak(), not an a11y tree.
  • Flutter web (text) engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart: a real <input>/<textarea>, transparent color and caret, positioned by transform from framework-sent geometry; per-browser strategies (iOS moves it off-screen and re-places after 100 ms; Firefox listens keyup/select; Safari desktop skips focus and blur). Comments cite flutter issues 98817 (skip re-placing during IME composition), 136006, 71275 (Safari ignores zero-sized fields), 155265, 189744 (iOS blur during caret drag drops keyboard), 180652 (keep real field in its semantics node for tab order), 178743 (iOS no scroll-into-view in iframes); a TODO calls focus pulling "grabby" (166857). File read to char 100000 of 111416.
  • Pattern: egui, GPUI web, Flutter web all use a hidden/transparent native text element for IME with toolkit-pushed geometry (inferred from the three files above). Slint says nothing.

3. Accessibility and EditContext

  • AccessKit github.com/AccessKit/accesskit: BSD-3-Clause (GitHub API; some crates Apache-2.0, e.g. accesskit_xplat), 1.5k stars. accesskit 0.25.1 (2026-09-25), accesskit_winit 0.34.1, accesskit_unix 0.24.0. README released adapters: Android, iOS, macOS, Unix (AT-SPI), Windows (UIA). Web still under "Planned adapters": "for applications that render their own UI elements to a canvas". No web-adapter issue found (search "web" returned #561 "How closely to follow the WAI-ARIA roles spec/naming?" open since 2025-05; #705 "Add a property to allow exposing DOM ID" closed 2026-08-25; #565 "iOS adapter" open; docs PRs #753/#760 "Document ARIA mappings" 2026-07/08). Inference: groundwork for DOM mapping, not an adapter.
  • Integrators with Taffy-driven trees: Bevy (bevy_a11y), Blitz (BaseDocument::build_ accessibility_tree -> accesskit::TreeUpdate; tests accessibility_roles.rs, accessibility_hidden.rs), Masonry, Slint (desktop only), GPUI (annotation API PRs), egui. Blitz ships accesskit_xplat 0.2.1 (winit-free adapter) whose README warns that AccessKit developers "have noted that the approaches used by both this crates and accesskit_winit may not be the optimal way" (accesskit PR #656 comment).
  • Flutter web semantics docs.flutter.dev/ui/accessibility/web-accessibility: Semantics tree is translated to an accessible HTML DOM with ARIA roles; "For performance reasons, Flutter's web accessibility is not on by default"; enabled by an invisible aria-label="Enable accessibility" button or SemanticsBinding.instance.ensureSemantics(). No cost quantified. Open github.com/flutter/flutter issues read 2026-10-10: #194011 "Clip semantics DOM text labels to stop stealing taps" (2026-10-08); #192466 "[iOS Safari] Long Semantics label overflows its button and intercepts taps on a later sliver" (2026-10-08); #191529 "prefer pointer events over semantics tap for mouse clicks on web"; #194109 "Remove the a11y placeholder only after the gesture ends"; #172206 "excludeSemantics breaks aria-label on flutter web input for TextFormField"; #98160 "Enable accessibility" read by screen reader even when auto-enabled; #118723 focus ring disappears when enabled in Chrome; #77283 not navigable with vimium-c. Pattern: semantics DOM layered over the canvas intercepts pointers and needs clipping (inferred).
  • EditContext developer.mozilla.org/en-US/docs/Web/API/EditContext_API: "Limited availability", experimental, standards track. MDN browser-compat-data api/EditContext.json: Chrome 121+ (and Chromium derivatives), Firefox not supported (bugzil.la/1904161), Safari not supported (webkit.org/b/269922). MDN: you render the editable region yourself (canvas ok); EditContext holds text/selection/composition state; you must report selection and caret location (IME window placement) and apply format updates; MDN recommends an offscreen DOM view of the text for screen readers since canvas text is unreadable. Spec body: [background: W3C Editing WG draft].

4. Hosts that are not ours

Unity (UI Toolkit TextField)

  • docs.unity3d.com/Manual/UIE-uxml-element-TextField.html: keyboard-type, auto-correction, hide-mobile-input, select-all-on-focus, select-word-by-double-click, max-length, is-delayed, emoji-fallback-support, inherited language-direction. Page says nothing on IME, shaping or the advanced text generator.
  • Legacy Input.compositionString / compositionCursorPos docs: built-in GUI draws composition; for your own GUI "you need to take care of displaying the string at the current cursor position" and report position via compositionCursorPos. Docs call the Input class legacy.
  • Issue tracker: IN-147103 "Japanese IME candidate window overlaps the input field" (Unity 6, Win11 and macOS standalone; field type unstated); 1171328 predictive text far from field for CJK (fixed 2019.4/2020.1); UUM-100552 Japanese suggestion clears TextMeshPro field (fixed 6000.0.51f1). Whether UITK TextField is affected is not established.
  • Local: EveUnity UITK lowerer (prior-art doc 1.2); no IME/caret doc in Eve.

Flutter

  • TextInputConnection (api.flutter.dev/flutter/services/TextInputConnection-class.html): the framework sends the host setEditingState(TextEditingValue), updateConfig, setEditableSizeAndTransform(Size, Matrix4), setComposingRect, setCaretRect, setSelectionRects, setStyle/updateStyle (family, size, weight, direction, align), requestAutofill, show/close. This is the data a self-rendered field pushes to the platform IME. No wire format or mandatory subset given on the page.
  • HtmlElementView (docs.flutter.dev/platform-integration/web/web-content-in-flutter): reserves layout space for a DOM element; "underlying platform views might sometimes swallow pointer events before they can reach Flutter"; documented fix pointer_interceptor (transparent platform view behind overlapping widgets: buttons, drawers, dialogs, menus). Page silent on clipping, scroll, z-order, performance. flutter#115679 (open, updated 2025-10-14) "[web] (Potential) Performance regression when scrolling platform views".

Overlay failure modes with sources

  • Click interception by hidden/overlapping elements: egui #8273; Flutter docs; Flutter #194011, #192466.
  • Focus loss / keyboard: Flutter text_editing.dart comments (189744, 155265, 166857).
  • IME restarts on value writes: GPUI ime_mirror.rs header.
  • Page scroll on focus: egui text_agent comment; Flutter iOS 100 ms re-placement; 178743.
  • Safari ignoring zero-sized fields: Flutter 71275.
  • Scroll sync / clipping of overlays in scrolled canvas content: only flutter#115679 found; no first-hand scroll-sync source (not searched exhaustively).

5. TUI cell measurement

  • iocraft github.com/ccbrown/iocraft (0.9.1, 2026-09-04, 188k downloads; packages/iocraft/Cargo.toml): taffy 0.5.2 (flexbox + taffy_tree only; far behind 0.14), unicode-width 0.1.13, crossterm 0.29. components/text.rs: Taffy measure_func wraps content with s.wrap(width) and returns the max line.width() (UnicodeWidthStr); modes Wrap / NoWrap; MinContent wraps at width 1. canvas.rs: draws per character group, advances x += value.width().max(1), clips in cells; doc: "Character groups containing only zero-width marks reserve one column"; special case for U+FE0F with a compatibility() callback adjusting padding. text_input.rs keeps cursor_offset: State<usize>; no IME handling seen. Grep for "grapheme" in text.rs / canvas.rs / text_input.rs found none; where character groups are segmented was not traced.
  • ratatui (not Taffy): layout via kasuari 0.4.9 (Cassowary), unicode-width >= 0.2.0 (version pin explained in ratatui issue #1271), unicode-segmentation, unicode-truncate 3.
  • unicode-width 0.2.2 (2025-10-06); unicode-segmentation 1.13.3 (2026-06-01).
  • Local: EveTui/src/eve-tui-shell.mjs (297 lines); no cell-width logic read in this pass.
  • No second Taffy-based TUI toolkit examined.

6. GPU glyph rasterization (operator scope addition)

6a. Slug

  • Patent: terathon.com/blog/decade-slug.html: "effective today, I am permanently and irrevocably dedicating the Slug patent to the public domain", 2026-03-17; US 10,373,352 (granted 2019, would have run to 2038); USPTO SB/43 disclaimer filed.
  • Repo github.com/EricLengyel/Slug ("Reference code for the Slug Algorithm"), 1,547 stars, pushed 2026-04-15. Files: SlugPixelShader.hlsl, SlugVertexShader.hlsl, LICENSE, LICENSE-MIT, LICENSE-APACHE, NOTICE, README.md. Licence detail: LICENSE says dual MIT or Apache-2.0 at your option; shader headers say MIT, "Copyright 2017, by Eric Lengyel"; GitHub API reports NOASSERTION. README adds "may be freely used by anyone for any purpose ... If you do use this code in software that gets distributed in any way, then you are required to give credit." Blog says MIT.
  • Shaders cover: pixel shader SlugRender (curve texture 4096 wide, 4x16-bit float; band texture 2x16-bit uint; horizontal and vertical bands per glyph; per-band curves sorted by max coordinate; winding number per ray). Vertex shader includes dynamic dilation, which JCGT 2017 did not cover (blog). Blog: multi-colour emoji drawn as stacked glyph layers each with a bounding polygon (replaced a loop-in-shader design); adaptive supersampling removed; dilation addresses tiny and large text; the optional band split optimisation removed; band data reduced to two 16-bit components. Quadratic Beziers only (lines encoded {p1,p2,p2}). Hinting: README: "This algorithm ignores font hinting"; suggests picking size so size * sCapHeight is an integer. Repo holds shaders only: no layout, shaping, atlas or texture builder (README tips describe the CPU preprocessing the host must do).
  • JCGT 2017: Lengyel, "GPU-Centered Font Rendering Directly from Glyph Outlines", JCGT 6(2), jcgt.org/published/0006/02/02 (page did not extract; abstract paraphrased from a search result of the paper.pdf): antialiased text from outline data on the GPU, no precomputed textures or distance fields; fixes numerical-precision artifacts of prior art; avoids divergent branching; accurate under affine and projective transforms.
  • Open implementations (GitHub search 2026-10-10, not exhaustive):
    • gabdube/rust-slug-wgpu: MIT, 15 stars, pushed 2026-04-15, "demo, not a rust crate. The code is not production ready"; WebGPU with storage buffers instead of textures; dynamic dilation not ported.
    • zzstar17/ash-slug: MIT, 1 star, 2026-09-29: preprocessing for Slug shaders (Vulkan/ash).
    • countertype/three-text: MIT, 126 stars, 2026-07-04: Three.js/WebGL/WebGPU adapters, HarfBuzz (harfbuzzjs wasm) shaping, Knuth-Plass justification, glyph caching (Slug use not confirmed in the README lines read).
    • No published wgpu crate or WebGL2 reference port found.
  • Commercial Slug Library (sluglibrary.com): "Full source is included with every license" (source available, not open source); single-engineer licence one-time $1,500 USD, unlimited commercial products, free updates while solo. Beyond the algorithm: UTF-8 layout services (glyph positions); kerning, ligature replacement, combining-mark placement, character composition, alternates; OpenType stylistic alternates, small caps, oldstyle figures, sub/superscripts, fractions; full-colour emoji incl. skin tones and ZWJ sequences. Bidi/RTL: not mentioned; complex scripts not explicitly claimed; full shaping not claimed; dynamic effects and hinting not mentioned. APIs: Vulkan, D3D10/11/12, OpenGL 3.0-4.6, Metal, WebGL2, consoles. Blog names Activision, Blizzard, id, 2K, Ubisoft, Warner Bros., Insomniac, Zenimax, Adobe as licensees.

6b. Vello family (github.com/linebender/vello, Apache-2.0, 4.4k stars, pushed 2026-10-09)

  • README "Choose a renderer": Vello CPU (vello_cpu 0.3.0, 2026-10-02, 5.7M downloads) is "the most mature choice"; Vello GPU (vello_gpu 0.3.0, 2026-10-02, 4.8k downloads) does CPU-side path preprocessing, GPU rasterisation and compositing of sparse strips, "supports a native WebGL2 backend and a wgpu backend without requiring compute shaders", "intended to become the primary renderer for production GPU use cases as it matures; Vello CPU is currently overall more mature". Vello compute (vello 0.11.0, 2026-10-02, 983k downloads) lives under research/, "an experimental implementation for compute-capable GPUs".
  • vello_hybrid 0.2.0 (2026-08-07) is the earlier crate name of the sparse-strips GPU renderer (inferred from README + issue titles); 2026-09 open issues are WebGL polish (#1625, #1943, #1167). vello_gpu/src/lib.rs features: wgpu (default), webgl ("browser support, using GLSL"), text.
  • Text: glifo 0.4.0 (2026-10-02, Apache-2.0 OR MIT, 3.2M downloads): "APIs for efficiently rendering glyphs and paint styles"; README: "under rapid development. Consider it experimental for now". Takes glyphs + positions, caches rasterised glyphs in an atlas (glifo/src/atlas/*), has colr.rs (COLR colour glyphs), HintCache, FontEmbolden, optional png (bitmap glyphs); shares "the hinting instance and hinted advance" between shaper and renderer. Scene::glyph_run consumes runs. Parley (same org) produces runs (inferred; parley README does not name glifo).
  • GPU requirements: wgpu backend needs no compute shaders; WebGL2 backend is native GLSL; the compute renderer needs compute-capable GPUs (README wording).

6c. SDF / MSDF atlases (contrast)

  • msdfgen github.com/Chlumsky/msdfgen: MIT, 4,952 stars, pushed 2026-08-29: multi-channel signed distance field generator; MSDF uses "all three color channels" to reproduce sharp corners "almost perfectly"; mtsdf adds a true SDF in alpha. Per-glyph distance-field atlas sampled by a shader.
  • [background] Valve 2007 SIGGRAPH "Improved Alpha-Tested Magnification" introduced SDF text atlases; single-channel SDF rounds corners; MSDF fixes corners (Chlumsky thesis, CTU Prague 2015); none address hinting or very small sizes well; colour emoji need a separate bitmap/COLR path. Not re-fetched.
  • Contrast with Slug (README + blog above): Slug stores curve and band tables, not rasterised images; SDF/MSDF store pre-rasterised distance atlases.

Pointers

  • Prior Eyes doc: F:\Projects\Eve\docs\research\thing2-foundation-prior-art.md
  • Ruling: eureka mind thing:ruling:thing2-one-solver-taffy
  • Sources: github.com/linebender/{parley,vello,xilem}; github.com/pop-os/cosmic-text; github.com/DioxusLabs/blitz (packages/blitz-dom/src/{events/ime.rs,accessibility.rs}); github.com/zed-industries/zed crates/gpui_web/src/ime_mirror.rs; github.com/emilk/egui crates/eframe/src/web/text_agent.rs; github.com/flutter/flutter engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart; github.com/AccessKit/accesskit; github.com/ccbrown/iocraft packages/iocraft/src/{components/text.rs,canvas.rs}; github.com/EricLengyel/Slug; terathon.com/blog/decade-slug.html; sluglibrary.com; jcgt.org/published/0006/02/02/; MDN EditContext_API; docs.slint.dev web platform guide; docs.flutter.dev web-accessibility and web-content-in-flutter.

Gaps

  • No numbers for Flutter semantics DOM cost; flt-semantics structure not read.
  • No measured scroll-sync failure source for native-control overlays beyond flutter#115679.
  • Unity UITK TextField IME behaviour not established.
  • iocraft grapheme segmentation origin not traced; Slug licence README credit clause differs from the blog's plain "MIT".
  • No Masonry-on-web and no Taffy + AccessKit-web combination found.