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
@@ -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");@@ -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>");