{"record":{"id":"0df9c7ad7f7b6258","repo":"a-b-street/abstreet","slug":"render-line","errorCode":null,"errorMessage":"render_line({}): {}","messagePattern":"render_line\\((.+?)\\): (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"widgetry/src/text.rs","lineNumber":512,"sourceCode":"            if span.underlined {\n                \"text-decoration=\\\"underline\\\"\"\n            } else {\n                \"\"\n            },\n            if let Some(c) = span.outline_color {\n                format!(\"stroke=\\\"{}\\\"\", c.as_hex())\n            } else {\n                String::new()\n            },\n            htmlescape::encode_minimal(&span.text)\n        )\n        .unwrap();\n    }\n    write!(&mut svg, \"{}</text></svg>\", contents).unwrap();\n\n    let mut svg_tree = match usvg::Tree::from_str(&svg, &usvg::Options::default()) {\n        Ok(t) => t,\n        Err(err) => panic!(\"render_line({}): {}\", contents, err),\n    };\n    svg_tree.convert_text(&assets.fontdb.borrow());\n    let mut batch = GeomBatch::new();\n    match crate::svg::add_svg_inner(&mut batch, svg_tree, tolerance) {\n        Ok(_) => batch,\n        Err(err) => {\n            error!(\"render_line({}): {}\", contents, err);\n            // We'll just wind up with a blank line\n            batch\n        }\n    }\n}\n\npub trait TextExt {\n    fn text_widget(self, ctx: &EventCtx) -> Widget;\n    fn batch_text(self, ctx: &EventCtx) -> Widget;\n}\n","sourceCodeStart":494,"sourceCodeEnd":530,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/widgetry/src/text.rs#L494-L530","documentation":"render_line builds an SVG string wrapping the line's text, then parses it with usvg; if usvg::Tree::from_str fails it panics with the contents and the parser error. This usually means the generated <svg>/<text> markup is invalid, typically because text content broke the XML.","triggerScenarios":"Calling render_line (directly or via inner_render/inner_wrap_to_pixels) with text contents containing characters that break the generated SVG markup — unescaped XML, invalid control characters, or malformed fmt sequences; also usvg internal parse failures.","commonSituations":"Rendering user-supplied strings or logs containing raw '<'/'>'/'&' or invalid control bytes; extremely large strings; text with embedded NUL bytes from binary data.","solutions":["Sanitize/escape the text (XML entities) before passing to render_line; htmlescape::encode_minimal is used elsewhere in this codebase for curvey.","Strip control characters and invalid XML chars from the string.","Log the failing `contents` from the panic message and test the string in isolation."],"exampleFix":"// before\ncanvas.draw_text(serde_json::to_string(&value).unwrap());\n// after\ncanvas.draw_text(&htmlescape::encode_minimal(&serde_json::to_string(&value).unwrap()));","handlingStrategy":"validation","validationCode":"fn is_safe_label(s: &str) -> bool {\n    s.chars().all(|c| !c.is_control()) && xml::Writer::new(Vec::new()).write(&s).is_ok()\n}","typeGuard":null,"tryCatchPattern":"// Sanitize at the call site:\ncanvas.draw_text(&htmlescape::encode_minimal(&raw));","preventionTips":["Always XML-escape dynamic strings before text rendering calls","Strip control characters and lone surrogates from user input","Add a unit test rendering adversarial strings (angles, ampersands, control chars)"],"tags":["rust","svg","text-rendering","parsing"],"backgroundTag":"xml-parse-error","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}