← Extensions to GPUI

Text backend extension. Parley shaping and geometry; GPUI caches, atlas and native input.

xgpui-parley

Opt-in Parley 0.12/HarfRust text backend: flat-LTR compatibility, BiDi paragraphs, resolved-face metrics, precise variable-font axes and grayscale rasterization. GPUI retains platform input, frame caches and glyph atlas ownership.

GPUI public API changes: additions and compatibility

This crate adds Application::with_platform_and_text_system to select the backend before GPUI caches are built, optional PlatformTextSystem::shape_paragraph, paragraph layout/cache geometry, and paint_paragraph. The optional trait method defaults to Unsupported, so existing text backends need no required override. Existing application construction and native initialization retain their behavior. Additive method names can still collide with downstream extension methods.

Parley uses GPUI's existing glyph atlas; it does not require custom-draw scene variants or wide-gamut presentation. Its flat-line supported subset and fallback behavior are documented below; those are extension behavior, not required changes to existing GPUI callers.

GPUI public API changes: additions and compatibility.

Construction

[dependencies]
gpui = { package = "xgpui", version = "=3.0.0" }
xgpui-parley = { version = "=0.5.0", features = ["system-fonts"] }
use gpui::{Application, PlatformTextSystem};
use std::sync::Arc;
use xgpui_parley::ParleyTextSystem;

// Register application fonts in font_context before construction.
let backend = Arc::new(ParleyTextSystem::new(font_context, "IBM Plex Sans")?);
let app = Application::with_platform_and_text_system(
    gpui_platform::current_platform(false), backend,
);

The explicit default family must exist; .SystemUIFont maps to it. Ordered fallback uses FontFallbacks. system-fonts is optional and off by default. Native platform initialization is unchanged. Native and Parley font-controls labs/tests are verified on macOS and Windows. Linux runtime acceptance and paragraph conversion IMEs remain separate checks.

Variable fonts and inspection

Version 0.4.0 accepts Font::variations in flat-line and paragraph shaping and retains the actual resolved coordinates for metrics and rasterization. Use FontTag::{WEIGHT, WIDTH, SLANT, OPTICAL_SIZE, ITALIC} for standard axes, and validated tag parsing for custom axes; see the typed styling example. Explicit signed/fractional axes, clamping, fallback and instance isolation share core's font-controls contract. font_axes, opentype_features, and font_capabilities are available through GPUI's text system. Parley supplies raw resolved-face tables through the backend hook; the parsers remain private in core.

Inspect axes and OpenType features

After registering the font with the selected backend, query it through the window's text system:

use gpui::{FontQuery, font};

let text_system = window.text_system();
let capabilities = text_system.font_capabilities();
let font_id = text_system.resolve_font(&font("Roboto Flex"));

if capabilities.axis_introspection {
    match text_system.font_axes(font_id)? {
        FontQuery::Unsupported => { /* Hide the axis inspector. */ }
        FontQuery::Supported(axes) => {
            for axis in axes {
                println!("{}: {} .. {} (default {}, hidden={})",
                    axis.tag, axis.min_value, axis.max_value,
                    axis.default_value, axis.hidden);
            }
        }
    }
}

if capabilities.feature_introspection {
    match text_system.opentype_features(font_id)? {
        FontQuery::Unsupported => { /* Hide the feature inspector. */ }
        FontQuery::Supported(features) => {
            for feature in features {
                println!("{}: GSUB={}, GPOS={}",
                    feature.tag, feature.substitution, feature.positioning);
            }
        }
    }
}

A successful empty list differs from unsupported inspection; ? propagates read/parsing errors. Results describe the resolved face, including any font substitution performed by resolve_font. Use IDs from this same text system. Feature presence does not guarantee an effect on every script or string.

Set axes with typed tags

use gpui::{FontTag, FontVariations, div, prelude::*};

let axes = FontVariations::new([
    (FontTag::WEIGHT, 650.0),
    (FontTag::WIDTH, 85.0),
    (FontTag::SLANT, -7.5),
    (FontTag::OPTICAL_SIZE, 14.0),
])?;

let specimen = div()
    .font_family("Roboto Flex")
    .font_variations(axes)
    .child("Hamburgefonts 012345");

Check capabilities.static_variations before offering axis editing. Standard tags need no parsing; coordinates remain validated and clamp per resolved face.

Interactive previews

VariationPreview is an optional extension utility for a fixed font descriptor: configure axis-specific intervals and a finite capacity, then obtain quantized GPUI fonts before resolution. It works with native text backends too. Retain the controller throughout the inspector session; keep precise author values separate. It never recycles font IDs or evicts atlas resources. See the preview policy and live lab.

Paragraphs and input

// FontRuns contain descriptor IDs resolved by this application's TextSystem.
let paragraph = window.text_system().layout_paragraph(
    text, gpui::px(24.0), &font_runs, width,
)?;
let hit = paragraph.hit_test(mouse_position - bounds.origin)?;
let caret = paragraph.caret_bounds(hit)?;
let fragments = paragraph.selection_bounds(anchor, hit)?;

// Explicit base direction and language use the extension API directly.
// `parley` is the Arc<ParleyTextSystem> installed as the app's text backend.
let options = xgpui_parley::ParagraphOptions {
    direction: xgpui_parley::TextDirection::RightToLeft,
    language: Some(xgpui_parley::TextLanguage::parse("ur")?),
};
let urdu = parley
    .prepare_paragraph_with_options(&text, gpui::px(24.0), &font_runs, options)?
    .layout(width)?; // Cache shapes/reflows yourself; GPUI's cache has no options.

// During element paint:
window.handle_input(&focus, gpui::ElementInputHandler::new(bounds, entity), cx);
gpui::paint_paragraph(paragraph.as_ref(), bounds.origin, color_at, window)?;

The input entity implements EntityInputHandler. It translates UTF-16 edits to source UTF-8 and uses the painted paragraph for IME geometry. Candidate anchors use the requested caret/fragment; marked text uses selection fragments. Edits invalidate geometry until repaint. Apply origin/scrolling once at the UI edge.

The typeset() element

typeset("…") wraps the paragraph path as an ordinary GPUI element, using only public GPUI APIs (Window::request_measured_layout, a GPUI global and paint_paragraph). Install it with the backend, then use it like text:

xgpui_parley::typeset::install(backend.clone(), cx); // the installed Arc<ParleyTextSystem>

div().w(px(150.0)).truncate().child(
    xgpui_parley::typeset("تقرير الإيرادات").rtl().language("ar".parse()?),
)

It inherits GPUI's text style, including white_space, all three text_overflow modes, line_clamp and text_align (Left/Right act as start/end). Mixed styles use GPUI's model: with_highlights and with_runs, as on StyledText, including per-run fonts, colors, backgrounds, underlines and strikethroughs; styles follow truncated text. Wrapping text reports min-content and max-content widths, as in taffy's Parley example. Truncation is logical and verified by reshaping. Shapes, reflows and fits are cached in the engine. Without install it renders as GPUI text. Under this backend, plain GPUI text uses the left-to-right-only flat-line path, so right-to-left text needs typeset(). The text lab's Paragraph panel draws every label with typeset(): truncated cells, a tooltip and a clamp, plus a UI-language toggle (en/he/ar/fa/ur) that mirrors the panel for right-to-left languages.

GPUI versus extension APIs

Added to GPUIPurpose
Application::with_platform_and_text_system(Rc<dyn Platform>, Arc<dyn PlatformTextSystem>)Select the backend before cache construction
PlatformTextSystem::shape_paragraph(ParagraphRequest)Optional provider hook; default returns Unsupported
PlatformParagraphShape::layout(width)Immutable width-dependent reflow
PlatformParagraphLayoutSource, size, visual glyph runs, hit testing, caret, movement and selection geometry
ParagraphRequest, ParagraphPosition, TextAffinity, VisualDirectionBackend-neutral request and interaction types
WindowTextSystem::layout_paragraph(text, size, runs, width)GPUI-owned shaping/reflow cache entry point
paint_paragraph(layout, origin, color_at, window)Visual glyph painting through the existing atlas

ParleyTextSystem, FaceRegistry, RegisteredFace, GlyphRasterizer, ParagraphShape, ParagraphLayout, VariationPreview, ParagraphOptions, TextDirection, TextLanguage, Typeset and typeset::install belong to xgpui-parley, not GPUI. Explicit paragraph direction and language add no GPUI API. Parley/HarfRust/Skrifa types do not enter the core signatures.

The paragraph additions change no existing public struct fields or required trait methods. Core 3.0 font controls separately add descriptor/style fields; see the migration table. Existing backend implementations need no paragraph override; Application::with_platform and PlatformTextSystem::layout_line retain their contracts. EntityInputHandler, ElementInputHandler and glyph/emoji paint methods are existing GPUI APIs. Internal changes add paragraph frame-cache entries and invalidation; the macOS variable-weight correction changes native font selection behavior without adding public API.

Contracts

BoundaryContract
Font identityRequested descriptors distinguish features/fallbacks/variations; shaped IDs retain actual blob, face index and normalized variation location
LifetimeIDs are not recycled; old layouts remain rasterizable after font registration
Shape cacheGPUI key: source, ordered FontRuns, size
Reflow cacheSame key plus width; immutable layout Arcs; width changes reuse shaping
InvalidationBoth cache levels follow frame retention/reuse and font generation; backend calls run outside cache locks
GeometryLogical UTF-8 plus affinity; visual glyph order; paragraph-local positions include baseline/glyph offsets
PaintExisting bounds cache and sprite atlas; colors resolve by logical index; decorations/backgrounds remain caller-owned
Native providersOptional paragraph hook defaults to Unsupported

Parley owns shaping, BiDi, breaking and interaction geometry. Reflow clones and rebreaks retained Parley layout. Skrifa metrics/rasterization use the same registered instance; a bounded LRU retains hinting setup and one staged outline avoids repeating extraction between matching bounds and raster calls. Shaping and rasterization use separate locks. No extension layout or bitmap cache is added. Core API additions.

Supported subset

Flat-line compatibility requires LTR, one size, scale 1 and start alignment. try_layout_line returns errors; the infallible hook substitutes .notdef with original indices and reports rejection outside the backend lock.

Paragraphs support mixed LTR/RTL, automatic direction (explicit direction and a paragraph language through prepare_paragraph_with_options), start alignment, one size, multiple font runs and U+2028 hard breaks. Callers split document paragraphs; the lab normalizes pasted line endings to U+2028.

Color emoji (COLR v0/v1, sbix and CBDT) are supported in plain GPUI text and typeset(); registered emoji families are automatic fallbacks. Synthetic styles, LCD, dilation, inline boxes, vertical text and split-style cluster fragments are unsupported. Raster size/bounds are capped at 4096 device pixels. Explicit alignment, per-run languages, advanced styling, accessibility and broader BiDi/UAX #14 conformance remain follow-ups.

Lab and validation

The Wasm font inspector runs the same GPUI axis controls with Parley shaping and rasterization in Wasm, through WebGPU or WebGL2. It displays axis ranges, GSUB/GPOS feature tags and precise static samples beside the live preview. This is separate from the default GPUI web text backend, whose explicit-axis requests remain unsupported.

just text --backend both
just text --workload paragraph
just text --workload paragraph --timings-only
just text --workload scroll --scroll-frames 640 --exit-after-scroll
just lab-test

The lab covers bundled fallback/variable fonts, mixed scripts, wrapping, caret, selection, clipboard and composition. First/warm/reflow CPU diagnostics separate cache hits from new-width layout; seed timings exclude GPU work. Native optical size and host fallback can differ, so matched fonts do not imply pixel identity.

Automated checks cover face/cache identity, visual conversion, atlas reuse, UTF-16 composition/candidate replacement and stale/wrapped IME geometry. Manual macOS results (2026-10-24): single-line native/Parley Japanese–Romaji popup selection/commit/cancel passed; paragraph U.S. accent-panel placement, selection, commit/cancel passed by user confirmation without keyboard-setting changes. The November 5, 2026 native/Parley font-controls labs and unit tests passed on macOS and Windows. Paragraph conversion IMEs and Linux runtime/IME acceptance remain unvalidated.