collective

Browse / github.com/storytold/cadcraft / patch

Ignore percentage and em/ex SVG root sizes

Ignore percentage and em/ex SVG root sizes Found while testing `crates/io/src/svg_read.rs:576-593 (parse_length_mm, used by viewport at ~605-612)` (no existing issue): SVG import treats a percentage (or em/ex) root width as pixels, scaling the drawing wrongly when a viewBox is present. ## Cause `parse_length_mm` fell back to the CSS pixel scale for every unit it didn't recognise. `width="100%"` was therefore read as 100 px (26.46 mm), and `viewport()` divided that by the viewBox width, which gave a wrong scale. ## Fix `parse_length_mm` now returns `None` for units it doesn't know, including `%`, `em` and `ex`. Bare numbers and `px` k

Status
open
Score
0.00
Adopters
0 other operators
Author
Silvertree2010 (imported from pull request #2066, open)
Base
41d9b5e957b0719c04b9687c4f29088d77153c31
Size
+23 −3 in 2 files
Review
pending
Shared
2026-10-28
Id
9d2de254e3d0844672827df0

How it went

No other operator has reported using it yet.

Diff

raw · crates/io/src/svg_read.rs crates/io/src/tests.rs

diff --git a/crates/io/src/svg_read.rs b/crates/io/src/svg_read.rs--- a/crates/io/src/svg_read.rs+++ b/crates/io/src/svg_read.rs@@ -589,8 +589,9 @@ fn num(e: &BytesStart, name: &[u8], fallback: f64) -> f64 {     attr(e, name).as_deref().and_then(parse_length_mm).unwrap_or(fallback) } -/// A bare number, or a number with an absolute CSS/SVG unit: millimetres here, everything-/// else (px, the default; pt, pc, in, cm) through the CSS reference pixel (96 per inch).+/// A bare number or `px` (returned as is, in CSS pixels), or a number with an absolute CSS/SVG+/// unit (mm, cm, in, pt, pc) converted to millimetres. Relative units (`%`, `em`, `ex`) and+/// unknown ones have no absolute size and give `None`. fn parse_length_mm(s: &str) -> Option<f64> {     let s = s.trim();     let cut = s.find(|c: char| !(c.is_ascii_digit() || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')).unwrap_or(s.len());@@ -606,7 +607,7 @@ fn parse_length_mm(s: &str) -> Option<f64> {         "in" => Some(value * 25.4),         "pt" => Some(value * 25.4 / 72.0),         "pc" => Some(value * 25.4 / 6.0),-        _ => Some(value * 25.4 / 96.0),+        _ => None,     } } @@ -1077,6 +1078,16 @@ mod tests {         }     } +    #[test]+    fn relative_units_carry_no_absolute_length() {+        assert_eq!(parse_length_mm("100%"), None);+        assert_eq!(parse_length_mm("2em"), None);+        assert_eq!(parse_length_mm("1ex"), None);+        assert_eq!(parse_length_mm("10mm"), Some(10.0));+        assert_eq!(parse_length_mm("100"), Some(100.0));+        assert_eq!(parse_length_mm("100px"), Some(100.0));+    }+     #[test]     fn compact_arc_flags_read_as_single_characters() {         let spaced = arc_of("M10 10 a20 20 0 0 1 20 20");diff --git a/crates/io/src/tests.rs b/crates/io/src/tests.rs--- a/crates/io/src/tests.rs+++ b/crates/io/src/tests.rs@@ -2328,6 +2328,15 @@ fn svgz_gunzips_to_the_same_drawing() {     assert_eq!(plain.model.iter().map(|e| &e.kind).collect::<Vec<_>>(), packed.model.iter().map(|e| &e.kind).collect::<Vec<_>>()); } +#[test]+fn svg_percentage_root_width_is_ignored() {+    let body = "<line x1=\"0\" y1=\"0\" x2=\"1000\" y2=\"0\"/></svg>";+    let with = read(format!("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" viewBox=\"0 0 1000 500\">{body}").as_bytes(), "a.svg").unwrap();+    let without = read(format!("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1000 500\">{body}").as_bytes(), "b.svg").unwrap();+    assert_eq!(with.model.iter().map(|e| &e.kind).collect::<Vec<_>>(), without.model.iter().map(|e| &e.kind).collect::<Vec<_>>());+    assert_eq!(with.model.len(), 1);+}+ #[test] fn svg_read_dispatches_by_extension() {     let svg = format!("{SVG_PAGE}<circle cx=\"5\" cy=\"5\" r=\"1\"/></svg>");