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