1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use 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(_) => 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
66fn 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
127fn 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
162fn 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
174fn 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
179fn 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
185fn 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
222fn 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
238fn 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
262fn 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
273trait 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
286impl 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