1#[derive(Debug, Clone, Copy, PartialEq)]
2pub struct SvgViewBox {
3 pub min_x: f64,
4 pub min_y: f64,
5 pub width: f64,
6 pub height: f64,
7}
8
9impl SvgViewBox {
10 #[must_use]
11 pub fn new(min_x: f64, min_y: f64, width: f64, height: f64) -> Self {
12 Self {
13 min_x,
14 min_y,
15 width,
16 height,
17 }
18 }
19}
20
21#[must_use]
22pub fn parse_view_box(input: &str) -> Option<SvgViewBox> {
23 let components: Vec<_> = input
24 .split(|ch: char| ch.is_ascii_whitespace() || ch == ',')
25 .filter(|component| !component.is_empty())
26 .collect();
27
28 if components.len() != 4 {
29 return None;
30 }
31
32 let min_x = components[0].parse::<f64>().ok()?;
33 let min_y = components[1].parse::<f64>().ok()?;
34 let width = components[2].parse::<f64>().ok()?;
35 let height = components[3].parse::<f64>().ok()?;
36
37 if !min_x.is_finite()
38 || !min_y.is_finite()
39 || !width.is_finite()
40 || !height.is_finite()
41 || width < 0.0
42 || height < 0.0
43 {
44 return None;
45 }
46
47 Some(SvgViewBox::new(min_x, min_y, width, height))
48}
49
50#[must_use]
51pub fn format_view_box(view_box: SvgViewBox) -> String {
52 format!(
53 "{} {} {} {}",
54 format_number(view_box.min_x),
55 format_number(view_box.min_y),
56 format_number(view_box.width),
57 format_number(view_box.height)
58 )
59}
60
61pub(crate) fn format_number(value: f64) -> String {
62 if value.abs() < f64::EPSILON {
63 return "0".to_string();
64 }
65
66 let rounded = value.round();
67 if (value - rounded).abs() < 1.0e-12 {
68 return format!("{}", rounded as i64);
69 }
70
71 let mut formatted = value.to_string();
72 if formatted.contains('.') {
73 while formatted.ends_with('0') {
74 formatted.pop();
75 }
76 if formatted.ends_with('.') {
77 formatted.pop();
78 }
79 }
80
81 formatted
82}