{"record":{"id":"00a2b8064977732d","repo":"zed-industries/zed","slug":"canvas-font-id-space-exhausted","errorCode":null,"errorMessage":"Canvas font ID space exhausted","messagePattern":"Canvas font ID space exhausted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gpui_web/src/text_system.rs","lineNumber":110,"sourceCode":"                .get(&native_id)\n                .cloned()\n                .context(\"missing source font descriptor for Canvas fallback\")?\n        };\n        let monospace = match (\n            self.native.glyph_for_char(native_id, 'i'),\n            self.native.glyph_for_char(native_id, 'm'),\n        ) {\n            (Some(narrow), Some(wide)) => {\n                self.native.advance(native_id, narrow)?.width\n                    == self.native.advance(native_id, wide)?.width\n            }\n            _ => false,\n        };\n        let mut state = self.state.write();\n        if let Some(font_id) = state.canvas_font_ids.get(&native_id) {\n            return Ok(*font_id);\n        }\n        ensure!(\n            state.canvas_fonts.len() < CANVAS_FONT_BIT,\n            \"Canvas font ID space exhausted\"\n        );\n        let font_id = FontId(CANVAS_FONT_BIT | state.canvas_fonts.len());\n        state.canvas_fonts.push(Arc::new(CanvasFont {\n            native_id,\n            descriptor,\n            monospace,\n        }));\n        state.canvas_font_ids.insert(native_id, font_id);\n        Ok(font_id)\n    }\n\n    fn register_glyph(&self, font_id: FontId, text: &str, color: bool) -> Result<GlyphId> {\n        if let Some(glyph_id) = self.state.read().glyphs.id_for(font_id, text, color) {\n            return Ok(glyph_id);\n        }\n        // Another thread may register the same glyph between the read and write locks.","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/zed-industries/zed/blob/916fc2b8cb3a815cbef4a3b40e13081be72036b6/crates/gpui_web/src/text_system.rs#L92-L128","documentation":"This error comes from the web (WASM/Canvas) GPUI text system when registering a new Canvas-backed font. The web text system splits the FontId space: native platform fonts use normal IDs, and Canvas fallback fonts get IDs with the high CANVAS_FONT_BIT set. Before allocating a new Canvas font ID, it ensures the canvas_fonts vector length is still below the CANVAS_FONT_BIT boundary; if it is not, the font ID space is exhausted and the error is thrown instead of returning a colliding ID.","triggerScenarios":"Calling font_id/fallback_glyph for a font that is not available natively (so it must be registered as a Canvas font) when state.canvas_fonts.len() has reached CANVAS_FONT_BIT (1 << (usize::BITS - 1)), i.e. an astronomical number of distinct Canvas-registered fonts in one process. Effectively unreachable in practice on 64-bit targets unless IDs are corrupted or the constant/bit layout changed.","commonSituations":"A corrupted or already-tagged native_id being re-registered repeatedly (cache lookup via canvas_font_ids missing), a 32-bit/16-bit reduction of the ID space, or a change to CANVAS_FONT_BIT that makes the canvas half of the space small. Essentially an internal invariant, not something ordinary font usage triggers.","solutions":["Verify canvas_font_ids caching is hit for repeated native_ids so duplicate fonts are not registered each call","Check that native_id values passed in are untagged (do not already contain CANVAS_FONT_BIT), which would corrupt the split of the ID space","Reduce the number of distinct dynamically registered fonts if running on a narrow usize target (e.g. wasm32 with modified constants)","File/inspect a bug: on 64-bit targets reaching 2^63 canvas fonts indicates an internal accounting defect"],"exampleFix":"// before\nensure!(\n    state.canvas_fonts.len() < CANVAS_FONT_BIT,\n    \"Canvas font ID space exhausted\"\n);\n// after\n// cache lookup must run first so repeated fonts reuse an ID\nif let Some(font_id) = state.canvas_font_ids.get(&native_id) {\n    return Ok(*font_id);\n}\nensure!(\n    state.canvas_fonts.len() < CANVAS_FONT_BIT,\n    \"Canvas font ID space exhausted (len={}, limit={})\",\n    state.canvas_fonts.len(),\n    CANVAS_FONT_BIT\n);","handlingStrategy":"validation","validationCode":"fn can_register_canvas_font(canvas_fonts_len: usize) -> bool {\n    canvas_fonts_len < CANVAS_FONT_BIT\n}\n// call before requesting a font id for a non-native font","typeGuard":"fn is_canvas_font_id(id: FontId) -> bool {\n    id.0 & CANVAS_FONT_BIT != 0\n}","tryCatchPattern":null,"preventionTips":["Always resolve fonts through the text system API so canvas_font_ids caching dedupes registrations","Never construct FontId values manually or persist them across sessions","Avoid dynamically loading thousands of distinct font families; reuse a small set","Report to maintainers if hit on a 64-bit target — it indicates ID-space accounting corruption"],"tags":["fonts","wasm","resource-exhaustion","id-space"],"backgroundTag":"value-out-of-range","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"}