Drop XML-forbidden control characters from math SVG text

Found while testing crates/engine/src/math.rs:909 (to_svg, the `esc` closure used for each <text> glyph)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-31 14:07:33 +01:00
co-authored by Claude Haiku 5.5
parent ea214dd44d
commit 9caae9c542
2 changed files with 11 additions and 0 deletions
+4
View File
@@ -916,6 +916,10 @@ pub fn to_svg(latex: &str, size: f64) -> (Vec<u8>, (f64, f64)) {
let face = f.face(c, *italic);
let fam = if std::ptr::eq(face, &*f.fallback) { FALLBACK } else { FAMILY };
let it = *italic && !std::ptr::eq(face, &*f.fallback);
// XML 1.0 forbids most control characters and U+FFFE/U+FFFF.
if !matches!(c, '\t' | '\n' | '\r' | '\u{20}'..='\u{D7FF}' | '\u{E000}'..='\u{FFFD}' | '\u{10000}'..='\u{10FFFF}') {
continue;
}
let _ = write!(
svg,
"<text x=\"{:.3}\" y=\"{:.3}\" font-family=\"'{fam}'\" font-size=\"{size:.3}\"{}>{}</text>",
+7
View File
@@ -474,3 +474,10 @@ fn deep_nodes_drop_when_replaced_or_owned_by_unwinding_continuations() {
});
assert!(result.is_err(), "the intentional panic must actually unwind the owned frames");
}
#[test]
fn svg_drops_control_characters_forbidden_by_xml() {
let (svg, _) = to_svg("a\u{1}b\u{c}", 12.0);
let svg = String::from_utf8(svg).unwrap();
assert!(!svg.chars().any(|c| c < ' ' && !matches!(c, '\t' | '\n' | '\r')), "control character in SVG: {svg:?}");
}