zed-industries/zed · error

Canvas returned a negative advance

Error message

Canvas returned a negative advance

What it means

The Canvas-based glyph measurement path calls canvas_text::measure() to obtain a glyph's typographic metrics and caches the result. The library requires a non-negative advance width; if the browser Canvas measurement returns a negative advance (e.g. a degenerate/invalid font or glyph string), this error is thrown rather than letting a negative advance corrupt text layout.

Solutions

  1. Inspect the glyph's text and ensure it is a measurable, non-empty string (skip measurement for control characters)
  2. Validate the css_font() descriptor (family loaded via document.fonts, finite positive size) before calling canvas_text::measure
  3. Clamp or special-case negative advances to 0.0 upstream if the glyph is legitimately zero-width
  4. Check the browser/Canvas implementation; some headless browsers return degenerate metrics for unloaded fonts

Example fix

// before
let metrics = canvas_text::measure(&glyph.text, &font.css_font(font_size, glyph.color)?)?;
ensure!(metrics.advance >= 0.0, "Canvas returned a negative advance");
// after
let metrics = canvas_text::measure(&glyph.text, &font.css_font(font_size, glyph.color)?)?;
if metrics.advance < 0.0 {
    // treat as zero-width glyph instead of erroring
    return Ok(Metrics { advance: 0.0, ..metrics });
}
Defensive patterns

Strategy: try-catch

Validate before calling

fn advance_is_plausible(metrics: &Metrics) -> bool {
    metrics.advance.is_finite() && metrics.advance >= 0.0
}

Type guard

fn is_zero_width_char(c: char) -> bool {
    matches!(u32::from(c), 0x200B..=0x200F | 0xFEFF) || c.is_control()
}

Try / catch

match text_system.typographic_bounds(font_id, glyph_id, font_size) {
    Ok(bounds) => bounds,
    Err(err) if err.to_string().contains("negative advance") => {
        // fall back to zero-width metrics for this glyph
        Bounds::default()
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Calling measure, typographic_bounds, advance, or glyph_raster_bounds (via fallback_glyph) on a Canvas font/glyph for which the browser's canvas text measurement yields metrics.advance < 0.0 — typically when the glyph's text is empty or the CSS font string produced by css_font() is invalid and the browser falls back oddly.

Common situations: Measuring a zero-width or control-character glyph, an emoji/rare fallback glyph the browser cannot actually resolve, or a font-size/descriptor combination that serializes to an invalid CSS font shorthand (browser silently substitutes a default and returns bogus metrics).

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of zed-industries/zed@916fc2b8cb (2026-09-19). Data as JSON: /api/errors/91c6a639bf67ab6d. Report an issue: GitHub.

Appendix: source

Thrown at crates/gpui_web/src/text_system.rs:159

            .context("invalid Canvas glyph ID")
    }

    fn measure(
        &self,
        font_id: FontId,
        glyph_id: GlyphId,
        font_size: Pixels,
    ) -> Result<CanvasTextMetrics> {
        let key = (font_id, glyph_id, font_size);
        if let Some(metrics) = self.state.read().measurements.get(&key) {
            return Ok(*metrics);
        }
        let font = self
            .canvas_font(font_id)
            .context("invalid Canvas font ID")?;
        let glyph = self.canvas_glyph(font_id, glyph_id)?;
        let metrics = canvas_text::measure(&glyph.text, &font.css_font(font_size, glyph.color)?)?;
        ensure!(metrics.advance >= 0.0, "Canvas returned a negative advance");
        let mut state = self.state.write();
        if state.measurements.len() >= MAX_MEASUREMENTS {
            state.measurements.clear();
        }
        state.measurements.insert(key, metrics);
        Ok(metrics)
    }

    fn fallback_glyph(
        &self,
        native_id: FontId,
        text: &str,
        color: bool,
        font_size: Pixels,
    ) -> Result<(FontId, GlyphId, CanvasTextMetrics)> {
        if let Some(cached) = self
            .state
            .read()

View on GitHub (pinned to 916fc2b8cb)