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 | |
5 | use std::sync::Arc; |
6 | |
7 | use svgtypes::Length; |
8 | use tiny_skia_path::Path; |
9 | |
10 | use super::svgtree::{AId, EId, SvgNode}; |
11 | use super::{converter, units}; |
12 | use crate::{ApproxEqUlps, IsValidLength, Rect}; |
13 | |
14 | pub(crate) fn convert(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
15 | match node.tag_name()? { |
16 | EId::Rect => convert_rect(node, state), |
17 | EId::Circle => convert_circle(node, state), |
18 | EId::Ellipse => convert_ellipse(node, state), |
19 | EId::Line => convert_line(node, state), |
20 | EId::Polyline => convert_polyline(node), |
21 | EId::Polygon => convert_polygon(node), |
22 | EId::Path => convert_path(node), |
23 | _ => None, |
24 | } |
25 | } |
26 | |
27 | pub(crate) fn convert_path(node: SvgNode) -> Option<Arc<Path>> { |
28 | let value: &str = node.attribute(AId::D)?; |
29 | let mut builder = tiny_skia_path::PathBuilder::new(); |
30 | for segment in svgtypes::SimplifyingPathParser::from(value) { |
31 | let segment = match segment { |
32 | Ok(v) => v, |
33 | Err(_) => break, |
34 | }; |
35 | |
36 | match segment { |
37 | svgtypes::SimplePathSegment::MoveTo { x, y } => { |
38 | builder.move_to(x as f32, y as f32); |
39 | } |
40 | svgtypes::SimplePathSegment::LineTo { x, y } => { |
41 | builder.line_to(x as f32, y as f32); |
42 | } |
43 | svgtypes::SimplePathSegment::Quadratic { x1, y1, x, y } => { |
44 | builder.quad_to(x1 as f32, y1 as f32, x as f32, y as f32); |
45 | } |
46 | svgtypes::SimplePathSegment::CurveTo { |
47 | x1, |
48 | y1, |
49 | x2, |
50 | y2, |
51 | x, |
52 | y, |
53 | } => { |
54 | builder.cubic_to( |
55 | x1 as f32, y1 as f32, x2 as f32, y2 as f32, x as f32, y as f32, |
56 | ); |
57 | } |
58 | svgtypes::SimplePathSegment::ClosePath => { |
59 | builder.close(); |
60 | } |
61 | } |
62 | } |
63 | |
64 | builder.finish().map(Arc::new) |
65 | } |
66 | |
67 | fn convert_rect(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
68 | // 'width' and 'height' attributes must be positive and non-zero. |
69 | let width = node.convert_user_length(AId::Width, state, Length::zero()); |
70 | let height = node.convert_user_length(AId::Height, state, Length::zero()); |
71 | if !width.is_valid_length() { |
72 | log::warn!( |
73 | "Rect ' {}' has an invalid 'width' value. Skipped." , |
74 | node.element_id() |
75 | ); |
76 | return None; |
77 | } |
78 | if !height.is_valid_length() { |
79 | log::warn!( |
80 | "Rect ' {}' has an invalid 'height' value. Skipped." , |
81 | node.element_id() |
82 | ); |
83 | return None; |
84 | } |
85 | |
86 | let x = node.convert_user_length(AId::X, state, Length::zero()); |
87 | let y = node.convert_user_length(AId::Y, state, Length::zero()); |
88 | |
89 | let (mut rx, mut ry) = resolve_rx_ry(node, state); |
90 | |
91 | // Clamp rx/ry to the half of the width/height. |
92 | // |
93 | // Should be done only after resolving. |
94 | if rx > width / 2.0 { |
95 | rx = width / 2.0; |
96 | } |
97 | if ry > height / 2.0 { |
98 | ry = height / 2.0; |
99 | } |
100 | |
101 | // Conversion according to https://www.w3.org/TR/SVG11/shapes.html#RectElement |
102 | let path = if rx.approx_eq_ulps(&0.0, 4) { |
103 | tiny_skia_path::PathBuilder::from_rect(Rect::from_xywh(x, y, width, height)?) |
104 | } else { |
105 | let mut builder = tiny_skia_path::PathBuilder::new(); |
106 | builder.move_to(x + rx, y); |
107 | |
108 | builder.line_to(x + width - rx, y); |
109 | builder.arc_to(rx, ry, 0.0, false, true, x + width, y + ry); |
110 | |
111 | builder.line_to(x + width, y + height - ry); |
112 | builder.arc_to(rx, ry, 0.0, false, true, x + width - rx, y + height); |
113 | |
114 | builder.line_to(x + rx, y + height); |
115 | builder.arc_to(rx, ry, 0.0, false, true, x, y + height - ry); |
116 | |
117 | builder.line_to(x, y + ry); |
118 | builder.arc_to(rx, ry, 0.0, false, true, x + rx, y); |
119 | |
120 | builder.close(); |
121 | |
122 | builder.finish()? |
123 | }; |
124 | |
125 | Some(Arc::new(path)) |
126 | } |
127 | |
128 | fn resolve_rx_ry(node: SvgNode, state: &converter::State) -> (f32, f32) { |
129 | let mut rx_opt = node.attribute::<Length>(AId::Rx); |
130 | let mut ry_opt = node.attribute::<Length>(AId::Ry); |
131 | |
132 | // Remove negative values first. |
133 | if let Some(v) = rx_opt { |
134 | if v.number.is_sign_negative() { |
135 | rx_opt = None; |
136 | } |
137 | } |
138 | if let Some(v) = ry_opt { |
139 | if v.number.is_sign_negative() { |
140 | ry_opt = None; |
141 | } |
142 | } |
143 | |
144 | // Resolve. |
145 | match (rx_opt, ry_opt) { |
146 | (None, None) => (0.0, 0.0), |
147 | (Some(rx), None) => { |
148 | let rx = units::convert_user_length(rx, node, AId::Rx, state); |
149 | (rx, rx) |
150 | } |
151 | (None, Some(ry)) => { |
152 | let ry = units::convert_user_length(ry, node, AId::Ry, state); |
153 | (ry, ry) |
154 | } |
155 | (Some(rx), Some(ry)) => { |
156 | let rx = units::convert_user_length(rx, node, AId::Rx, state); |
157 | let ry = units::convert_user_length(ry, node, AId::Ry, state); |
158 | (rx, ry) |
159 | } |
160 | } |
161 | } |
162 | |
163 | fn convert_line(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
164 | let x1: f32 = node.convert_user_length(AId::X1, state, def:Length::zero()); |
165 | let y1: f32 = node.convert_user_length(AId::Y1, state, def:Length::zero()); |
166 | let x2: f32 = node.convert_user_length(AId::X2, state, def:Length::zero()); |
167 | let y2: f32 = node.convert_user_length(AId::Y2, state, def:Length::zero()); |
168 | |
169 | let mut builder: PathBuilder = tiny_skia_path::PathBuilder::new(); |
170 | builder.move_to(x:x1, y:y1); |
171 | builder.line_to(x:x2, y:y2); |
172 | builder.finish().map(Arc::new) |
173 | } |
174 | |
175 | fn convert_polyline(node: SvgNode) -> Option<Arc<Path>> { |
176 | let builder: PathBuilder = points_to_path(node, eid:"Polyline" )?; |
177 | builder.finish().map(Arc::new) |
178 | } |
179 | |
180 | fn convert_polygon(node: SvgNode) -> Option<Arc<Path>> { |
181 | let mut builder: PathBuilder = points_to_path(node, eid:"Polygon" )?; |
182 | builder.close(); |
183 | builder.finish().map(Arc::new) |
184 | } |
185 | |
186 | fn points_to_path(node: SvgNode, eid: &str) -> Option<tiny_skia_path::PathBuilder> { |
187 | use svgtypes::PointsParser; |
188 | |
189 | let mut builder = tiny_skia_path::PathBuilder::new(); |
190 | match node.attribute::<&str>(AId::Points) { |
191 | Some(text) => { |
192 | for (x, y) in PointsParser::from(text) { |
193 | if builder.is_empty() { |
194 | builder.move_to(x as f32, y as f32); |
195 | } else { |
196 | builder.line_to(x as f32, y as f32); |
197 | } |
198 | } |
199 | } |
200 | _ => { |
201 | log::warn!( |
202 | " {} ' {}' has an invalid 'points' value. Skipped." , |
203 | eid, |
204 | node.element_id() |
205 | ); |
206 | return None; |
207 | } |
208 | }; |
209 | |
210 | // 'polyline' and 'polygon' elements must contain at least 2 points. |
211 | if builder.len() < 2 { |
212 | log::warn!( |
213 | " {} ' {}' has less than 2 points. Skipped." , |
214 | eid, |
215 | node.element_id() |
216 | ); |
217 | return None; |
218 | } |
219 | |
220 | Some(builder) |
221 | } |
222 | |
223 | fn convert_circle(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
224 | let cx: f32 = node.convert_user_length(AId::Cx, state, def:Length::zero()); |
225 | let cy: f32 = node.convert_user_length(AId::Cy, state, def:Length::zero()); |
226 | let r: f32 = node.convert_user_length(AId::R, state, def:Length::zero()); |
227 | |
228 | if !r.is_valid_length() { |
229 | log::warn!( |
230 | "Circle ' {}' has an invalid 'r' value. Skipped." , |
231 | node.element_id() |
232 | ); |
233 | return None; |
234 | } |
235 | |
236 | ellipse_to_path(cx, cy, rx:r, ry:r) |
237 | } |
238 | |
239 | fn convert_ellipse(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> { |
240 | let cx: f32 = node.convert_user_length(AId::Cx, state, def:Length::zero()); |
241 | let cy: f32 = node.convert_user_length(AId::Cy, state, def:Length::zero()); |
242 | let (rx: f32, ry: f32) = resolve_rx_ry(node, state); |
243 | |
244 | if !rx.is_valid_length() { |
245 | log::warn!( |
246 | "Ellipse ' {}' has an invalid 'rx' value. Skipped." , |
247 | node.element_id() |
248 | ); |
249 | return None; |
250 | } |
251 | |
252 | if !ry.is_valid_length() { |
253 | log::warn!( |
254 | "Ellipse ' {}' has an invalid 'ry' value. Skipped." , |
255 | node.element_id() |
256 | ); |
257 | return None; |
258 | } |
259 | |
260 | ellipse_to_path(cx, cy, rx, ry) |
261 | } |
262 | |
263 | fn ellipse_to_path(cx: f32, cy: f32, rx: f32, ry: f32) -> Option<Arc<Path>> { |
264 | let mut builder: PathBuilder = tiny_skia_path::PathBuilder::new(); |
265 | builder.move_to(x:cx + rx, y:cy); |
266 | builder.arc_to(rx, ry, x_axis_rotation:0.0, large_arc:false, sweep:true, x:cx, y:cy + ry); |
267 | builder.arc_to(rx, ry, x_axis_rotation:0.0, large_arc:false, sweep:true, x:cx - rx, y:cy); |
268 | builder.arc_to(rx, ry, x_axis_rotation:0.0, large_arc:false, sweep:true, x:cx, y:cy - ry); |
269 | builder.arc_to(rx, ry, x_axis_rotation:0.0, large_arc:false, sweep:true, x:cx + rx, y:cy); |
270 | builder.close(); |
271 | builder.finish().map(Arc::new) |
272 | } |
273 | |
274 | trait PathBuilderExt { |
275 | fn arc_to( |
276 | &mut self, |
277 | rx: f32, |
278 | ry: f32, |
279 | x_axis_rotation: f32, |
280 | large_arc: bool, |
281 | sweep: bool, |
282 | x: f32, |
283 | y: f32, |
284 | ); |
285 | } |
286 | |
287 | impl PathBuilderExt for tiny_skia_path::PathBuilder { |
288 | fn arc_to( |
289 | &mut self, |
290 | rx: f32, |
291 | ry: f32, |
292 | x_axis_rotation: f32, |
293 | large_arc: bool, |
294 | sweep: bool, |
295 | x: f32, |
296 | y: f32, |
297 | ) { |
298 | let prev = match self.last_point() { |
299 | Some(v) => v, |
300 | None => return, |
301 | }; |
302 | |
303 | let svg_arc = kurbo::SvgArc { |
304 | from: kurbo::Point::new(prev.x as f64, prev.y as f64), |
305 | to: kurbo::Point::new(x as f64, y as f64), |
306 | radii: kurbo::Vec2::new(rx as f64, ry as f64), |
307 | x_rotation: (x_axis_rotation as f64).to_radians(), |
308 | large_arc, |
309 | sweep, |
310 | }; |
311 | |
312 | match kurbo::Arc::from_svg_arc(&svg_arc) { |
313 | Some(arc) => { |
314 | arc.to_cubic_beziers(0.1, |p1, p2, p| { |
315 | self.cubic_to( |
316 | p1.x as f32, |
317 | p1.y as f32, |
318 | p2.x as f32, |
319 | p2.y as f32, |
320 | p.x as f32, |
321 | p.y as f32, |
322 | ); |
323 | }); |
324 | } |
325 | None => { |
326 | self.line_to(x, y); |
327 | } |
328 | } |
329 | } |
330 | } |
331 | |