1use crate::document::{extract_height, extract_width};
2
3#[derive(Debug, Clone, Default, PartialEq)]
4pub struct SvgSize {
5 pub width: Option<f64>,
6 pub height: Option<f64>,
7 pub width_unit: Option<String>,
8 pub height_unit: Option<String>,
9}
10
11#[must_use]
12pub fn extract_size(input: &str) -> SvgSize {
13 let width = extract_width(input);
14 let height = extract_height(input);
15
16 let (width, width_unit) = width
17 .as_deref()
18 .and_then(parse_dimension)
19 .map_or((None, None), |(value, unit)| (Some(value), unit));
20 let (height, height_unit) = height
21 .as_deref()
22 .and_then(parse_dimension)
23 .map_or((None, None), |(value, unit)| (Some(value), unit));
24
25 SvgSize {
26 width,
27 height,
28 width_unit,
29 height_unit,
30 }
31}
32
33pub(crate) fn parse_dimension(value: &str) -> Option<(f64, Option<String>)> {
34 let trimmed = value.trim();
35
36 if trimmed.is_empty() {
37 return None;
38 }
39
40 for split in (1..=trimmed.len()).rev() {
41 if !trimmed.is_char_boundary(split) {
42 continue;
43 }
44
45 let number = trimmed[..split].trim();
46 let unit = trimmed[split..].trim();
47 let Ok(parsed) = number.parse::<f64>() else {
48 continue;
49 };
50
51 if !parsed.is_finite() {
52 return None;
53 }
54
55 return Some((parsed, (!unit.is_empty()).then(|| unit.to_string())));
56 }
57
58 None
59}