{"record":{"id":"91c6a639bf67ab6d","repo":"zed-industries/zed","slug":"canvas-returned-a-negative-advance","errorCode":null,"errorMessage":"Canvas returned a negative advance","messagePattern":"Canvas returned a negative advance","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gpui_web/src/text_system.rs","lineNumber":159,"sourceCode":"            .context(\"invalid Canvas glyph ID\")\n    }\n\n    fn measure(\n        &self,\n        font_id: FontId,\n        glyph_id: GlyphId,\n        font_size: Pixels,\n    ) -> Result<CanvasTextMetrics> {\n        let key = (font_id, glyph_id, font_size);\n        if let Some(metrics) = self.state.read().measurements.get(&key) {\n            return Ok(*metrics);\n        }\n        let font = self\n            .canvas_font(font_id)\n            .context(\"invalid Canvas font ID\")?;\n        let glyph = self.canvas_glyph(font_id, glyph_id)?;\n        let metrics = canvas_text::measure(&glyph.text, &font.css_font(font_size, glyph.color)?)?;\n        ensure!(metrics.advance >= 0.0, \"Canvas returned a negative advance\");\n        let mut state = self.state.write();\n        if state.measurements.len() >= MAX_MEASUREMENTS {\n            state.measurements.clear();\n        }\n        state.measurements.insert(key, metrics);\n        Ok(metrics)\n    }\n\n    fn fallback_glyph(\n        &self,\n        native_id: FontId,\n        text: &str,\n        color: bool,\n        font_size: Pixels,\n    ) -> Result<(FontId, GlyphId, CanvasTextMetrics)> {\n        if let Some(cached) = self\n            .state\n            .read()","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/zed-industries/zed/blob/916fc2b8cb3a815cbef4a3b40e13081be72036b6/crates/gpui_web/src/text_system.rs#L141-L177","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\nlet metrics = canvas_text::measure(&glyph.text, &font.css_font(font_size, glyph.color)?)?;\nensure!(metrics.advance >= 0.0, \"Canvas returned a negative advance\");\n// after\nlet metrics = canvas_text::measure(&glyph.text, &font.css_font(font_size, glyph.color)?)?;\nif metrics.advance < 0.0 {\n    // treat as zero-width glyph instead of erroring\n    return Ok(Metrics { advance: 0.0, ..metrics });\n}","handlingStrategy":"try-catch","validationCode":"fn advance_is_plausible(metrics: &Metrics) -> bool {\n    metrics.advance.is_finite() && metrics.advance >= 0.0\n}","typeGuard":"fn is_zero_width_char(c: char) -> bool {\n    matches!(u32::from(c), 0x200B..=0x200F | 0xFEFF) || c.is_control()\n}","tryCatchPattern":"match text_system.typographic_bounds(font_id, glyph_id, font_size) {\n    Ok(bounds) => bounds,\n    Err(err) if err.to_string().contains(\"negative advance\") => {\n        // fall back to zero-width metrics for this glyph\n        Bounds::default()\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["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)"],"tags":["fonts","canvas","metrics","text-layout"],"backgroundTag":"internal-invariant-violation","analyzedSha":"916fc2b8cb3a815cbef4a3b40e13081be72036b6","analyzedAt":"2026-09-19T19:09:50.599Z","contentChangedAt":"2026-09-19T19:09:50.599Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}