SVG: non-finite viewBox and nested sizes no longer panic the importer

This commit is contained in:
webdevtodayjason
2026-10-12 13:12:51 -05:00
parent 470ea7a3af
commit 85a269b609
2 changed files with 43 additions and 4 deletions
+4 -4
View File
@@ -789,8 +789,8 @@ impl<'a, 'i> Parser<'a, 'i> {
// Nested viewport.
let x = self.attr_len(n, "x", 'x', &st);
let y = self.attr_len(n, "y", 'y', &st);
let w = n.attribute("width").and_then(|v| self.length(v, 'x', st.font_size)).unwrap_or(self.vw);
let h = n.attribute("height").and_then(|v| self.length(v, 'y', st.font_size)).unwrap_or(self.vh);
let w = n.attribute("width").and_then(|v| self.length(v, 'x', st.font_size)).filter(|v| v.is_finite()).unwrap_or(self.vw);
let h = n.attribute("height").and_then(|v| self.length(v, 'y', st.font_size)).filter(|v| v.is_finite()).unwrap_or(self.vh);
let vb = viewbox_transform(n, w, h);
let mut children = vec![];
let saved = (self.vw, self.vh);
@@ -926,7 +926,7 @@ impl<'a, 'i> Parser<'a, 'i> {
}
"rect" => {
let (w, h) = (l("width", 'x'), l("height", 'y'));
if w <= 0.0 || h <= 0.0 {
if !(w > 0.0 && h > 0.0) {
return None;
}
let rx_a = n.attribute("rx").and_then(|v| self.length(v, 'x', st.font_size));
@@ -981,7 +981,7 @@ impl<'a, 'i> Parser<'a, 'i> {
fn viewbox(n: roxmltree::Node) -> Option<([f64; 2], f64, f64)> {
let v = pathdata::numbers(n.attribute("viewBox")?);
(v.len() == 4 && v[2] > 0.0 && v[3] > 0.0).then(|| ([v[0], v[1]], v[2], v[3]))
(v.len() == 4 && v.iter().all(|x| x.is_finite()) && v[2] > 0.0 && v[3] > 0.0).then(|| ([v[0], v[1]], v[2], v[3]))
}
/// The viewBox → viewport transform (`preserveAspectRatio`, default `xMidYMid meet`).
+39
View File
@@ -119,6 +119,45 @@ fn nested_svg_percentages_use_the_nearest_viewport() {
assert_eq!(px(&img, 55, 65)[3], 0.0);
}
#[test]
fn non_finite_viewboxes_do_not_panic_or_produce_nan_geometry() {
for attrs in ["viewBox=\"0 0 1e400 100\"", "viewBox=\"0 0 100 1e400\"", "viewBox=\"-1e400 0 100 100\""] {
for shape in ["<rect width=\"0%\" height=\"50%\"/>", "<circle r=\"0%\"/>"] {
let svg = format!(r#"<svg xmlns="http://www.w3.org/2000/svg" {attrs}>{shape}<rect width="50%" height="50%"/></svg>"#);
let d = parse(svg.as_bytes()).unwrap();
assert_eq!((d.width, d.height), (300.0, 150.0));
let shapes = d.flatten();
assert_eq!(shapes.len(), 1);
assert!(matches!(shapes[0].2.geom, Geom::Rect { x, y, w, h, rx, ry }
if [x, y, w, h, rx, ry].iter().all(|v| v.is_finite()) && w == 150.0 && h == 75.0));
assert!(shapes[0].0.as_coeffs().iter().all(|v| v.is_finite()));
}
}
}
#[test]
fn non_finite_nested_viewports_do_not_panic_or_produce_nan_geometry() {
for attrs in ["width=\"1e400\" height=\"10\"", "width=\"10\" height=\"1e400\""] {
for shape in ["<rect width=\"0%\" height=\"50%\"/>", "<circle r=\"0%\"/>"] {
let svg = format!(r#"<svg xmlns="http://www.w3.org/2000/svg"><svg {attrs}>{shape}<rect width="50%" height="50%"/></svg></svg>"#);
let d = parse(svg.as_bytes()).unwrap();
let shapes = d.flatten();
assert_eq!(shapes.len(), 1);
assert!(matches!(shapes[0].2.geom, Geom::Rect { x, y, w, h, rx, ry }
if [x, y, w, h, rx, ry].iter().all(|v| v.is_finite()) && w > 0.0 && h > 0.0));
assert!(shapes[0].0.as_coeffs().iter().all(|v| v.is_finite()));
}
}
}
#[test]
fn zero_size_nested_viewport_draws_nothing() {
for attrs in ["width=\"0\" height=\"10\"", "width=\"10\" height=\"0\""] {
let svg = format!(r#"<svg xmlns="http://www.w3.org/2000/svg"><svg {attrs}><rect width="50%" height="50%"/></svg></svg>"#);
assert!(parse(svg.as_bytes()).unwrap().flatten().is_empty(), "{attrs}");
}
}
#[test]
fn malformed_input() {
assert!(parse(b"<html/>").is_err());