usvg/parser/
units.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use svgtypes::{Length, LengthUnit as Unit};
6
7use super::converter;
8use super::svgtree::{AId, SvgNode};
9use crate::Units;
10
11#[inline(never)]
12pub(crate) fn convert_length(
13    length: Length,
14    node: SvgNode,
15    aid: AId,
16    object_units: Units,
17    state: &converter::State,
18) -> f32 {
19    let dpi = state.opt.dpi;
20    let n = length.number as f32;
21    match length.unit {
22        Unit::None | Unit::Px => n,
23        Unit::Em => n * resolve_font_size(node, state),
24        Unit::Ex => n * resolve_font_size(node, state) / 2.0,
25        Unit::In => n * dpi,
26        Unit::Cm => n * dpi / 2.54,
27        Unit::Mm => n * dpi / 25.4,
28        Unit::Pt => n * dpi / 72.0,
29        Unit::Pc => n * dpi / 6.0,
30        Unit::Percent => {
31            if object_units == Units::ObjectBoundingBox {
32                n / 100.0
33            } else {
34                let view_box = state.view_box;
35
36                match aid {
37                    AId::Cx
38                    | AId::Dx
39                    | AId::Fx
40                    | AId::MarkerWidth
41                    | AId::RefX
42                    | AId::Rx
43                    | AId::Width
44                    | AId::X
45                    | AId::X1
46                    | AId::X2 => convert_percent(length, view_box.width()),
47                    AId::Cy
48                    | AId::Dy
49                    | AId::Fy
50                    | AId::Height
51                    | AId::MarkerHeight
52                    | AId::RefY
53                    | AId::Ry
54                    | AId::Y
55                    | AId::Y1
56                    | AId::Y2 => convert_percent(length, view_box.height()),
57                    _ => {
58                        let mut vb_len = view_box.width().powi(2) + view_box.height().powi(2);
59                        vb_len = (vb_len / 2.0).sqrt();
60                        convert_percent(length, vb_len)
61                    }
62                }
63            }
64        }
65    }
66}
67
68pub(crate) fn convert_user_length(
69    length: Length,
70    node: SvgNode,
71    aid: AId,
72    state: &converter::State,
73) -> f32 {
74    convert_length(length, node, aid, Units::UserSpaceOnUse, state)
75}
76
77#[inline(never)]
78pub(crate) fn convert_list(node: SvgNode, aid: AId, state: &converter::State) -> Option<Vec<f32>> {
79    if let Some(text) = node.attribute::<&str>(aid) {
80        let mut num_list = Vec::new();
81        for length in svgtypes::LengthListParser::from(text).flatten() {
82            num_list.push(convert_user_length(length, node, aid, state));
83        }
84
85        Some(num_list)
86    } else {
87        None
88    }
89}
90
91fn convert_percent(length: Length, base: f32) -> f32 {
92    base * (length.number as f32) / 100.0
93}
94
95#[inline(never)]
96pub(crate) fn resolve_font_size(node: SvgNode, state: &converter::State) -> f32 {
97    let nodes: Vec<_> = node.ancestors().collect();
98    let mut font_size = state.opt.font_size;
99    for n in nodes.iter().rev().skip(1) {
100        // skip Root
101        if let Some(length) = n.try_attribute::<Length>(AId::FontSize) {
102            let dpi = state.opt.dpi;
103            let n = length.number as f32;
104            font_size = match length.unit {
105                Unit::None | Unit::Px => n,
106                Unit::Em => n * font_size,
107                Unit::Ex => n * font_size / 2.0,
108                Unit::In => n * dpi,
109                Unit::Cm => n * dpi / 2.54,
110                Unit::Mm => n * dpi / 25.4,
111                Unit::Pt => n * dpi / 72.0,
112                Unit::Pc => n * dpi / 6.0,
113                Unit::Percent => {
114                    // If `font-size` has percent units that it's value
115                    // is relative to the parent node `font-size`.
116                    length.number as f32 * font_size * 0.01
117                }
118            }
119        } else if let Some(name) = n.attribute(AId::FontSize) {
120            font_size = convert_named_font_size(name, font_size);
121        }
122    }
123
124    font_size
125}
126
127fn convert_named_font_size(name: &str, parent_font_size: f32) -> f32 {
128    let factor = match name {
129        "xx-small" => -3,
130        "x-small" => -2,
131        "small" => -1,
132        "medium" => 0,
133        "large" => 1,
134        "x-large" => 2,
135        "xx-large" => 3,
136        "smaller" => -1,
137        "larger" => 1,
138        _ => {
139            log::warn!("Invalid 'font-size' value: '{}'.", name);
140            0
141        }
142    };
143
144    // 'On a computer screen a scaling factor of 1.2 is suggested between adjacent indexes.'
145    parent_font_size * 1.2f32.powi(factor)
146}