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