{"record":{"id":"caf0b34414513e69","repo":"GraphiteEditor/Graphite","slug":"failed-to-measure-the-text-dimensions","errorCode":null,"errorMessage":"Failed to measure the text dimensions","messagePattern":"Failed to measure the text dimensions","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"editor/src/messages/portfolio/document/overlays/utility_types_web.rs","lineNumber":1057,"sourceCode":"\t\t// └──┴──┴──┴──┘\n\t\tlet pixels = [(0, 0), (2, 2)];\n\t\tfor &(x, y) in &pixels {\n\t\t\tlet index = (x + y * PATTERN_WIDTH) * 4;\n\t\t\tdata[index..index + 4].copy_from_slice(&rgba);\n\t\t}\n\n\t\tlet image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();\n\t\tpattern_context.put_image_data(&image_data, 0, 0).unwrap();\n\t\tlet pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, \"repeat\").unwrap().unwrap();\n\n\t\tself.push_path(subpaths, transform);\n\n\t\tself.render_context.set_fill_style_canvas_pattern(&pattern);\n\t\tself.render_context.fill();\n\t}\n\n\tpub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {\n\t\tlet metrics = self.render_context.measure_text(text).expect(\"Failed to measure the text dimensions\");\n\t\tlet x = match pivot[0] {\n\t\t\tPivot::Start => padding,\n\t\t\tPivot::Middle => -(metrics.actual_bounding_box_right() + metrics.actual_bounding_box_left()) / 2.,\n\t\t\tPivot::End => -padding - metrics.actual_bounding_box_right() + metrics.actual_bounding_box_left(),\n\t\t};\n\t\tlet y = match pivot[1] {\n\t\t\tPivot::Start => padding + metrics.font_bounding_box_ascent() - metrics.font_bounding_box_descent(),\n\t\t\tPivot::Middle => (metrics.font_bounding_box_ascent() + metrics.font_bounding_box_descent()) / 2.,\n\t\t\tPivot::End => -padding,\n\t\t};\n\n\t\tlet [a, b, c, d, e, f] = (DAffine2::from_scale(DVec2::splat(self.viewport.scale())) * transform * DAffine2::from_translation(DVec2::new(x, y))).to_cols_array();\n\t\tself.render_context.set_transform(a, b, c, d, e, f).expect(\"Failed to rotate the render context to the specified angle\");\n\n\t\tif let Some(background) = background_color {\n\t\t\tself.render_context.set_fill_style_str(background);\n\t\t\tself.render_context.fill_rect(\n\t\t\t\t-padding,","sourceCodeStart":1039,"sourceCodeEnd":1075,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/portfolio/document/overlays/utility_types_web.rs#L1039-L1075","documentation":"The overlay text routine calls render_context.measure_text(text).expect(\"Failed to measure the text dimensions\"); wasm-bindgen types measure_text as Result<TextMetrics, JsValue>, so any JS-level throw (invalidated/detached context, engine error) becomes a WASM panic. Per spec measureText itself does not throw for missing fonts (it returns fallback metrics), so in practice this expect fires only when the context is no longer usable.","triggerScenarios":"Calling OverlayResource::text (every overlay label: angles, translation boxes, layer names) after the canvas context has been invalidated, removed, or replaced between frames.","commonSituations":"Canvas element removed from the DOM while overlay rendering is still scheduled; context loss during heavy GPU pressure; devtools 'discard/restore' of the tab.","solutions":["Stop drawing overlays for a frame when the canvas is detached or the context is lost, and rebuild the context before resuming","Replace the expect with a match that logs and returns early (no label is worth aborting the editor)","Keep font setup (set_font) before measure_text so engines return real metrics instead of erroring on odd state"],"exampleFix":"// before\nlet metrics = self.render_context.measure_text(text).expect(\"Failed to measure the text dimensions\");\n\n// after\nlet Ok(metrics) = self.render_context.measure_text(text) else {\n\tlog::error!(\"Failed to measure text {text:?}\");\n\treturn;\n};","handlingStrategy":"try-catch","validationCode":"// cheap sanity check before measuring: the context must still be usable\n// (wasm-bindgen exposes no isContextLost on this type; guard by owning the canvas lifetime)\nassert!(self.render_context.canvas().map(|c| c.width() > 0).unwrap_or(true));","typeGuard":null,"tryCatchPattern":"let metrics = match self.render_context.measure_text(text) {\n\tOk(m) => m,\n\tErr(js_err) => {\n\t\tlog::error!(\"measure_text failed for {text:?}: {js_err:?}\");\n\t\treturn;\n\t}\n};","preventionTips":["Keep the overlay canvas alive as long as any overlay render is scheduled","Handle measure_text Err by skipping the label rather than expecting","Call set_font before measuring so metrics and drawing use identical font state"],"tags":["rust","wasm-bindgen","canvas-2d","text-metrics","panic"],"backgroundTag":"canvas-2d-exception","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}