usvg/parser/
shapes.rs

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