Advanced 12 min

Text fidelity across colors

Understand why glyphs can change apparent weight on colored surfaces and how Argui keeps their edges consistent.

A glyph edge is coverage, not a pre-painted gray pixel

The glyph atlas stores geometric coverage: zero outside the shape, one inside it, and fractional values around each edge. The final edge color is created only when that mask is blended between the text color and the pixels already behind it.

A GPU normally blends shader colors in linear light and converts the finished framebuffer to sRGB for display. Human perception and browser-style font coverage are not linear, so reusing one alpha correction chosen only from the foreground can make white text look thinner on saturated blue than on black, even when the font, size, weight, and glyph mask are identical.

Compute the edge that should be seen

When the backdrop is known, Argui first computes the desired edge in sRGB perceptual space. It then converts that target back to linear space and reconstructs a straight-alpha source color whose normal GPU blend lands on the target. Raising alpha only when required keeps the reconstructed color inside the renderable gamut.

Backdrop-aware coverage
edge_srgb = mix(to_srgb(backdrop), to_srgb(foreground), coverage)
target_linear = to_linear(edge_srgb)
(source_rgb, source_alpha) = reconstruct(target_linear, backdrop)
output = vec4(source_rgb, source_alpha)

Carry exact backdrop information through paint

Layout resolves the solid color behind each text command while it walks the retained tree. Opaque fills replace the inherited backdrop, transparent fills preserve it, and translucent solid fills are composed when their parent is already known and opaque. The resolved linear RGB value travels with the text display command and glyph instances to the shader.

Argui deliberately stops claiming certainty when the pixels depend on a gradient, image, filter, native desktop backdrop, or another unknown surface. Those cases keep the existing foreground-polarity correction instead of sampling or guessing a false background color.

BackdropRenderer pathReason
Opaque solidBackdrop-aware reconstructionExact color is known
Translucent solid over known opaque colorPrecomposed backdrop-aware reconstructionThe resulting solid color is exact
Gradient, image, filter, or desktop blurForeground-polarity fallbackThe color varies per pixel or is owned by the host

Keep moving text aligned with the pixel grid

Color-correct edges cannot rescue text that is continuously translated between device pixels. A fractional transform changes the sampling phase of every glyph and can make a selected row look temporarily soft.

The Spotlight result now keeps its content transform at identity and animates the highlight background and corner radius instead. If text must move, prefer whole-device-pixel destinations at the active scale and inspect the animation at several scale factors.

Verify the math and the pixels separately

The renderer test exercises black, white, saturated blue, and colored foregrounds across a range of coverage values. It checks that ordinary linear alpha blending reconstructs the intended sRGB edge and that every source channel remains in gamut. Shader validation catches invalid WGSL independently.

A native capture is still required because a correct equation does not prove that layout supplied the right backdrop or that an animation stayed pixel-aligned. Compare the same text settings over light, dark, and saturated surfaces at native scale, and test WebAssembly separately because browser and operating-system presentation paths differ.

  • Keep font family, size, weight, and device scale identical in comparison samples.
  • Inspect edge weight on saturated colors instead of checking only black and white.
  • Test both the resting frame and intermediate animation frames.
  • Fall back conservatively whenever the exact backdrop is not provable.

Reference files

These are the implementation and guide files used for this chapter.

docs/rendering/primitives.mdcrates/argui-layout/src/paint/backdrop.rscrates/argui-render/src/shaders/primitives/text.wgslcrates/argui-render/tests/lib.rs
Compiled example

Identical glyphs over light, dark, and saturated backdrops

The Rust file below is imported verbatim by this page and compiled into the WebAssembly application running underneath it.

Open exact source
app_examples/docs-examples/src/examples/text_fidelity.rs
use argui::{
    core::Color,
    paint::{Border, CornerRadii},
    runtime::{Context, Render},
    text::TextStyle,
    ui::{AlignItems, Element, FlexWrap, JustifyContent, Sides, length, percent},
    widgets::default_theme,
};

/// Exact-source example comparing identical glyphs over several solid backdrops.
#[derive(Default)]
pub struct Example;

/// Builds one color sample with identical typography and an explicit solid backdrop.
fn sample(label: &str, colors: &str, background: Color, foreground: Color) -> Element {
    Element::column([
        Element::text("Ag 0123 · Crisp type").text_style(TextStyle {
            color: foreground,
            font_size: 17.0,
            line_height: 23.0,
            weight: 560,
            ..TextStyle::default()
        }),
        Element::text(label).text_style(TextStyle {
            color: foreground.with_alpha(0.78),
            font_size: 12.0,
            line_height: 17.0,
            weight: 600,
            ..TextStyle::default()
        }),
        Element::text(colors).text_style(TextStyle {
            color: foreground.with_alpha(0.62),
            font_size: 10.0,
            line_height: 15.0,
            weight: 500,
            ..TextStyle::default()
        }),
    ])
    .grow(1.0)
    .min_width(length(190.0))
    .height(length(116.0))
    .padding(Sides::length(18.0))
    .gap(5.0)
    .align_items(AlignItems::START)
    .justify_content(JustifyContent::CENTER)
    .background(background)
    .radius(CornerRadii::all(14.0))
}

impl Render for Example {
    fn render(&mut self, cx: &mut Context<Self>) -> Element {
        let themes = default_theme(cx.environment());
        let theme = themes.resolve(cx.environment().color_scheme);
        let samples = Element::row([
            sample("Dark on light", "#18181B on #FFFFFF", Color::WHITE, Color::from_srgb8(24, 24, 27)),
            sample("Light on dark", "#FFFFFF on #18181B", Color::from_srgb8(24, 24, 27), Color::WHITE),
            sample("Light on blue", "#FFFFFF on #2563EB", Color::from_srgb8(37, 99, 235), Color::WHITE),
        ])
        .width(percent(1.0))
        .flex_wrap(FlexWrap::Wrap)
        .gap(12.0);

        Element::column([
            Element::text("Text fidelity across color").text_style(TextStyle {
                color: theme.foreground,
                font_size: 20.0,
                line_height: 27.0,
                weight: 720,
                ..TextStyle::default()
            }),
            Element::text(
                "The same size and weight are used in every sample. Argui resolves each solid backdrop so glyph edges keep a consistent optical weight.",
            )
            .text_style(TextStyle {
                color: theme.muted_foreground,
                font_size: 13.0,
                line_height: 19.0,
                ..TextStyle::default()
            }),
            samples,
            Element::text(
                "Tip: animate the highlight surface, not a fractional transform on the text itself.",
            )
            .padding(Sides::length(14.0))
            .border(Border::all(1.0, theme.border))
            .radius(CornerRadii::all(10.0))
            .text_style(TextStyle {
                color: theme.muted_foreground,
                font_size: 12.0,
                line_height: 18.0,
                weight: 550,
                ..TextStyle::default()
            }),
        ])
        .width(percent(1.0))
        .height(percent(1.0))
        .padding(Sides::length(28.0))
        .gap(14.0)
        .background(theme.background)
    }
}