Skip to main content

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(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    // 'width' and 'height' attributes must be positive and non-zero.
71    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    // Clamp rx/ry to the half of the width/height.
94    //
95    // Should be done only after resolving.
96    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    // Conversion according to https://www.w3.org/TR/SVG11/shapes.html#RectElement
104    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    // Remove negative values first.
135    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    // Resolve.
147    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    // 'polyline' and 'polygon' elements must contain at least 2 points.
213    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}