1use std::sync::Arc;
4
5use usvg::roxmltree;
6
7#[derive(Debug, Clone, Copy, Default)]
13pub struct SvgIntrinsicDimensions {
14 pub width: Option<svgtypes::Length>,
16 pub height: Option<svgtypes::Length>,
18 pub view_box: Option<usvg::NonZeroRect>,
20 pub preserve_aspect_ratio: svgtypes::AspectRatio,
22 pub degenerate_view_box: bool,
25}
26
27impl SvgIntrinsicDimensions {
28 pub fn from_xmltree(doc: &roxmltree::Document) -> Self {
34 let root = doc.root_element();
35
36 let parse_length = |name: &str| -> Option<svgtypes::Length> {
37 root.attribute(name)?.parse::<svgtypes::Length>().ok()
38 };
39 let view_box_dims = root.attribute("viewBox").and_then(|s| {
43 let mut numbers = svgtypes::NumberListParser::from(s);
44 let x = numbers.next()?.ok()? as f32;
45 let y = numbers.next()?.ok()? as f32;
46 let w = numbers.next()?.ok()? as f32;
47 let h = numbers.next()?.ok()? as f32;
48 (numbers.next().is_none() && w.is_finite() && h.is_finite() && w >= 0.0 && h >= 0.0)
49 .then_some((x, y, w, h))
50 });
51 let view_box =
52 view_box_dims.and_then(|(x, y, w, h)| usvg::NonZeroRect::from_xywh(x, y, w, h));
53 let degenerate_view_box = view_box_dims.is_some_and(|(_, _, w, h)| w == 0.0 || h == 0.0);
54 let preserve_aspect_ratio = root
55 .attribute("preserveAspectRatio")
56 .and_then(|s| s.parse().ok())
57 .unwrap_or_default();
58
59 Self {
60 width: parse_length("width"),
61 height: parse_length("height"),
62 view_box,
63 preserve_aspect_ratio,
64 degenerate_view_box,
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
78pub struct SvgImageData {
79 pub tree: Arc<usvg::Tree>,
81 pub intrinsic_dimensions: SvgIntrinsicDimensions,
83}
84
85impl SvgImageData {
86 pub fn from_data(data: &[u8], options: &usvg::Options) -> Result<Self, usvg::Error> {
93 let decompressed;
95 let data = if data.starts_with(&[0x1f, 0x8b]) {
96 decompressed = usvg::decompress_svgz(data)?;
97 decompressed.as_slice()
98 } else {
99 data
100 };
101
102 let text = std::str::from_utf8(data).map_err(|_| usvg::Error::NotAnUtf8Str)?;
103 let xml_options = roxmltree::ParsingOptions {
104 allow_dtd: true,
105 ..Default::default()
106 };
107 let doc = roxmltree::Document::parse_with_options(text, xml_options)
108 .map_err(usvg::Error::ParsingFailed)?;
109 let tree = usvg::Tree::from_xmltree(&doc, options)?;
110 Ok(Self {
111 tree: Arc::new(tree),
112 intrinsic_dimensions: SvgIntrinsicDimensions::from_xmltree(&doc),
113 })
114 }
115
116 pub fn intrinsic_width(&self) -> Option<f32> {
119 use svgtypes::LengthUnit;
120 let declared = self
121 .intrinsic_dimensions
122 .width
123 .is_some_and(|len| len.unit != LengthUnit::Percent);
124 declared.then(|| self.tree.size().width())
125 }
126
127 pub fn intrinsic_height(&self) -> Option<f32> {
130 use svgtypes::LengthUnit;
131 let declared = self
132 .intrinsic_dimensions
133 .height
134 .is_some_and(|len| len.unit != LengthUnit::Percent);
135 declared.then(|| self.tree.size().height())
136 }
137
138 pub fn viewbox_aspect_ratio(&self) -> Option<f32> {
140 self.intrinsic_dimensions
141 .view_box
142 .map(|view_box| view_box.width() / view_box.height())
143 }
144
145 pub fn viewport_transform(&self, width: f32, height: f32) -> usvg::Transform {
149 let size = self.tree.size();
150 let stretch = usvg::Transform::from_scale(width / size.width(), height / size.height());
151 let Some(view_box) = self.intrinsic_dimensions.view_box else {
152 return stretch;
153 };
154 let aspect = self.intrinsic_dimensions.preserve_aspect_ratio;
155 let view_box_to_tree = view_box_transform(view_box, aspect, size.width(), size.height());
157 match view_box_to_tree.invert() {
158 Some(tree_to_view_box) => {
159 view_box_transform(view_box, aspect, width, height).pre_concat(tree_to_view_box)
160 }
161 None => stretch,
162 }
163 }
164
165 pub fn resolved_width(&self, container_width: Option<f32>) -> Option<f32> {
174 use svgtypes::LengthUnit;
175 match self.intrinsic_dimensions.width {
176 Some(len) if len.unit != LengthUnit::Percent => Some(self.tree.size().width()),
177 Some(len) => container_width.map(|cw| cw * (len.number as f32) / 100.0),
178 None => None,
179 }
180 }
181
182 pub fn resolved_height(&self, container_height: Option<f32>) -> Option<f32> {
185 use svgtypes::LengthUnit;
186 match self.intrinsic_dimensions.height {
187 Some(len) if len.unit != LengthUnit::Percent => Some(self.tree.size().height()),
188 Some(len) => container_height.map(|ch| ch * (len.number as f32) / 100.0),
189 None => None,
190 }
191 }
192
193 pub fn aspect_ratio(&self) -> f32 {
198 match (self.intrinsic_width(), self.intrinsic_height()) {
199 (Some(w), Some(h)) => w / h,
200 _ => self.viewbox_aspect_ratio().unwrap_or_else(|| {
201 let size = self.tree.size();
202 size.width() / size.height()
203 }),
204 }
205 }
206
207 pub fn intrinsic_size(&self) -> (f32, f32) {
212 let aspect_ratio = self.aspect_ratio();
213 match (self.intrinsic_width(), self.intrinsic_height()) {
214 (Some(w), Some(h)) => (w, h),
215 (Some(w), None) => (w, w / aspect_ratio),
216 (None, Some(h)) => (h * aspect_ratio, h),
217 (None, None) => {
218 if self.viewbox_aspect_ratio().is_some() {
222 let scale = (300.0 / aspect_ratio).min(150.0);
223 (scale * aspect_ratio, scale)
224 } else {
225 let size = self.tree.size();
226 (size.width(), size.height())
227 }
228 }
229 }
230 }
231}
232
233fn view_box_transform(
235 view_box: usvg::NonZeroRect,
236 aspect: svgtypes::AspectRatio,
237 width: f32,
238 height: f32,
239) -> usvg::Transform {
240 use svgtypes::Align;
241
242 let sx = width / view_box.width();
243 let sy = height / view_box.height();
244 let (sx, sy) = if aspect.align == Align::None {
245 (sx, sy)
246 } else if aspect.slice {
247 (sx.max(sy), sx.max(sy))
248 } else {
249 (sx.min(sy), sx.min(sy))
250 };
251 let (align_x, align_y) = match aspect.align {
252 Align::None | Align::XMinYMin => (0.0, 0.0),
253 Align::XMidYMin => (0.5, 0.0),
254 Align::XMaxYMin => (1.0, 0.0),
255 Align::XMinYMid => (0.0, 0.5),
256 Align::XMidYMid => (0.5, 0.5),
257 Align::XMaxYMid => (1.0, 0.5),
258 Align::XMinYMax => (0.0, 1.0),
259 Align::XMidYMax => (0.5, 1.0),
260 Align::XMaxYMax => (1.0, 1.0),
261 };
262 let tx = (width - view_box.width() * sx) * align_x - view_box.x() * sx;
263 let ty = (height - view_box.height() * sy) * align_y - view_box.y() * sy;
264 usvg::Transform::from_row(sx, 0.0, 0.0, sy, tx, ty)
265}