usvg/parser/
units.rs

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