usvg/parser/
shapes.rs

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 = node.convert_user_length(AId::X1, state, Length::zero());
165    let y1 = node.convert_user_length(AId::Y1, state, Length::zero());
166    let x2 = node.convert_user_length(AId::X2, state, Length::zero());
167    let y2 = node.convert_user_length(AId::Y2, state, Length::zero());
168
169    let mut builder = tiny_skia_path::PathBuilder::new();
170    builder.move_to(x1, y1);
171    builder.line_to(x2, y2);
172    builder.finish().map(Arc::new)
173}
174
175fn convert_polyline(node: SvgNode) -> Option<Arc<Path>> {
176    let builder = points_to_path(node, "Polyline")?;
177    builder.finish().map(Arc::new)
178}
179
180fn convert_polygon(node: SvgNode) -> Option<Arc<Path>> {
181    let mut builder = points_to_path(node, "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 = node.convert_user_length(AId::Cx, state, Length::zero());
225    let cy = node.convert_user_length(AId::Cy, state, Length::zero());
226    let r = node.convert_user_length(AId::R, state, 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, r, r)
237}
238
239fn convert_ellipse(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> {
240    let cx = node.convert_user_length(AId::Cx, state, Length::zero());
241    let cy = node.convert_user_length(AId::Cy, state, Length::zero());
242    let (rx, ry) = 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 = tiny_skia_path::PathBuilder::new();
265    builder.move_to(cx + rx, cy);
266    builder.arc_to(rx, ry, 0.0, false, true, cx, cy + ry);
267    builder.arc_to(rx, ry, 0.0, false, true, 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.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}