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
- Inspect the glyph's text and ensure it is a measurable, non-empty string (skip measurement for control characters)
- Validate the css_font() descriptor (family loaded via document.fonts, finite positive size) before calling canvas_text::measure
- Clamp or special-case negative advances to 0.0 upstream if the glyph is legitimately zero-width
- 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
- Preload web fonts via document.fonts before measuring so Canvas does not substitute a fallback with bad metrics
- Skip measurement for control/zero-width characters
- Log the glyph text and css_font string when measurement fails to spot invalid CSS font shorthands
- Pin to browser versions known to return sane canvas measureText metrics (headless browsers vary)
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
- invalid Canvas font size
- Canvas text bounds exceed device coordinates
- Canvas font ID space exhausted
- Canvas readback option could not be set
- Canvas returned non-finite text metrics
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)