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
5use std::sync::Arc;
6
7use svgtypes::Length;
8use tiny_skia_path::Path;
9
10use super::svgtree::{AId, EId, SvgNode};
11use super::{converter, units};
12use crate::{ApproxEqUlps, IsValidLength, Rect};
13
14pub(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
27pub(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
67fn 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
128fn 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
163fn 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
175fn 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
180fn 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
186fn 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
223fn 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
239fn 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
263fn 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
274trait 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
287impl 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