usvg/parser/
marker.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 strict_num::NonZeroPositiveF32;
8use svgtypes::Length;
9use tiny_skia_path::Point;
10
11use super::converter;
12use super::svgtree::{AId, EId, SvgNode};
13use crate::{
14    ApproxEqUlps, ApproxZeroUlps, ClipPath, Fill, Group, Node, NonZeroRect, Path, Size, Transform,
15    ViewBox,
16};
17
18// Similar to `tiny_skia_path::PathSegment`, but without the `QuadTo`.
19#[derive(Copy, Clone, Debug)]
20enum Segment {
21    MoveTo(Point),
22    LineTo(Point),
23    CubicTo(Point, Point, Point),
24    Close,
25}
26
27pub(crate) fn is_valid(node: SvgNode) -> bool {
28    // `marker-*` attributes cannot be set on shapes inside a `clipPath`.
29    if node
30        .ancestors()
31        .any(|n| n.tag_name() == Some(EId::ClipPath))
32    {
33        return false;
34    }
35
36    let start = node.find_attribute::<SvgNode>(AId::MarkerStart);
37    let mid = node.find_attribute::<SvgNode>(AId::MarkerMid);
38    let end = node.find_attribute::<SvgNode>(AId::MarkerEnd);
39    start.is_some() || mid.is_some() || end.is_some()
40}
41
42pub(crate) fn convert(
43    node: SvgNode,
44    path: &tiny_skia_path::Path,
45    state: &converter::State,
46    cache: &mut converter::Cache,
47    parent: &mut Group,
48) {
49    let list = [
50        (AId::MarkerStart, MarkerKind::Start),
51        (AId::MarkerMid, MarkerKind::Middle),
52        (AId::MarkerEnd, MarkerKind::End),
53    ];
54
55    for (aid, kind) in &list {
56        let mut marker = None;
57        if let Some(link) = node.find_attribute::<SvgNode>(*aid) {
58            if link.tag_name() == Some(EId::Marker) {
59                marker = Some(link);
60            }
61        }
62
63        if let Some(marker) = marker {
64            // TODO: move to svgtree
65            // Check for recursive marker.
66            if state.parent_markers.contains(&marker) {
67                log::warn!("Recursive marker detected: {}", marker.element_id());
68                continue;
69            }
70
71            resolve(node, path, marker, *kind, state, cache, parent);
72        }
73    }
74}
75
76#[derive(Clone, Copy)]
77enum MarkerKind {
78    Start,
79    Middle,
80    End,
81}
82
83enum MarkerOrientation {
84    Auto,
85    AutoStartReverse,
86    Angle(f32),
87}
88
89fn resolve(
90    shape_node: SvgNode,
91    path: &tiny_skia_path::Path,
92    marker_node: SvgNode,
93    marker_kind: MarkerKind,
94    state: &converter::State,
95    cache: &mut converter::Cache,
96    parent: &mut Group,
97) -> Option<()> {
98    let stroke_scale = stroke_scale(shape_node, marker_node, state)?.get();
99
100    let r = convert_rect(marker_node, state)?;
101
102    let view_box = marker_node.parse_viewbox().map(|vb| ViewBox {
103        rect: vb,
104        aspect: marker_node
105            .attribute(AId::PreserveAspectRatio)
106            .unwrap_or_default(),
107    });
108
109    let has_overflow = {
110        let overflow = marker_node.attribute(AId::Overflow);
111        // `overflow` is `hidden` by default.
112        overflow.is_none() || overflow == Some("hidden") || overflow == Some("scroll")
113    };
114
115    let clip_path = if has_overflow {
116        let clip_rect = if let Some(vbox) = view_box {
117            vbox.rect
118        } else {
119            r.size().to_non_zero_rect(0.0, 0.0)
120        };
121
122        let mut clip_path = ClipPath::empty(cache.gen_clip_path_id());
123
124        let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect(
125            clip_rect.to_rect(),
126        )))?;
127        path.fill = Some(Fill::default());
128
129        clip_path.root.children.push(Node::Path(Box::new(path)));
130
131        Some(Arc::new(clip_path))
132    } else {
133        None
134    };
135
136    // TODO: avoid allocation
137    let mut segments: Vec<Segment> = Vec::with_capacity(path.len());
138    let mut prev = Point::zero();
139    let mut prev_move = Point::zero();
140    for seg in path.segments() {
141        match seg {
142            tiny_skia_path::PathSegment::MoveTo(p) => {
143                segments.push(Segment::MoveTo(p));
144                prev = p;
145                prev_move = p;
146            }
147            tiny_skia_path::PathSegment::LineTo(p) => {
148                segments.push(Segment::LineTo(p));
149                prev = p;
150            }
151            tiny_skia_path::PathSegment::QuadTo(p1, p) => {
152                let (p1, p2, p) = quad_to_curve(prev, p1, p);
153                segments.push(Segment::CubicTo(p1, p2, p));
154                prev = p;
155            }
156            tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
157                segments.push(Segment::CubicTo(p1, p2, p));
158                prev = p;
159            }
160            tiny_skia_path::PathSegment::Close => {
161                segments.push(Segment::Close);
162                prev = prev_move;
163            }
164        }
165    }
166
167    let draw_marker = |p: tiny_skia_path::Point, idx: usize| {
168        let mut ts = Transform::from_translate(p.x, p.y);
169
170        let angle = match convert_orientation(marker_node) {
171            MarkerOrientation::AutoStartReverse if idx == 0 => {
172                (calc_vertex_angle(&segments, idx) + 180.0) % 360.0
173            }
174            MarkerOrientation::Auto | MarkerOrientation::AutoStartReverse => {
175                calc_vertex_angle(&segments, idx)
176            }
177            MarkerOrientation::Angle(angle) => angle,
178        };
179
180        if !angle.approx_zero_ulps(4) {
181            ts = ts.pre_rotate(angle);
182        }
183
184        if let Some(vbox) = view_box {
185            let size = Size::from_wh(r.width() * stroke_scale, r.height() * stroke_scale).unwrap();
186            let vbox_ts = vbox.to_transform(size);
187            let (sx, sy) = vbox_ts.get_scale();
188            ts = ts.pre_scale(sx, sy);
189        } else {
190            ts = ts.pre_scale(stroke_scale, stroke_scale);
191        }
192
193        ts = ts.pre_translate(-r.x(), -r.y());
194
195        // TODO: do not create a group when no clipPath
196        let mut g = Group {
197            transform: ts,
198            abs_transform: parent.abs_transform.pre_concat(ts),
199            clip_path: clip_path.clone(),
200            ..Group::empty()
201        };
202
203        let mut marker_state = state.clone();
204        marker_state.parent_markers.push(marker_node);
205        converter::convert_children(marker_node, &marker_state, cache, &mut g);
206        g.calculate_bounding_boxes();
207
208        if g.has_children() {
209            parent.children.push(Node::Group(Box::new(g)));
210        }
211    };
212
213    draw_markers(&segments, marker_kind, draw_marker);
214
215    Some(())
216}
217
218fn stroke_scale(
219    path_node: SvgNode,
220    marker_node: SvgNode,
221    state: &converter::State,
222) -> Option<NonZeroPositiveF32> {
223    match marker_node.attribute(AId::MarkerUnits) {
224        Some("userSpaceOnUse") => NonZeroPositiveF32::new(1.0),
225        _ => path_node.resolve_valid_length(AId::StrokeWidth, state, 1.0),
226    }
227}
228
229fn draw_markers<P>(path: &[Segment], kind: MarkerKind, mut draw_marker: P)
230where
231    P: FnMut(tiny_skia_path::Point, usize),
232{
233    match kind {
234        MarkerKind::Start => {
235            if let Some(Segment::MoveTo(p)) = path.first().cloned() {
236                draw_marker(p, 0);
237            }
238        }
239        MarkerKind::Middle => {
240            let total = path.len() - 1;
241            let mut i = 1;
242            while i < total {
243                let p = match path[i] {
244                    Segment::MoveTo(p) => p,
245                    Segment::LineTo(p) => p,
246                    Segment::CubicTo(_, _, p) => p,
247                    _ => {
248                        i += 1;
249                        continue;
250                    }
251                };
252
253                draw_marker(p, i);
254
255                i += 1;
256            }
257        }
258        MarkerKind::End => {
259            let idx = path.len() - 1;
260            match path.last().cloned() {
261                Some(Segment::LineTo(p)) => {
262                    draw_marker(p, idx);
263                }
264                Some(Segment::CubicTo(_, _, p)) => {
265                    draw_marker(p, idx);
266                }
267                Some(Segment::Close) => {
268                    let p = get_subpath_start(path, idx);
269                    draw_marker(p, idx);
270                }
271                _ => {}
272            }
273        }
274    }
275}
276
277fn calc_vertex_angle(path: &[Segment], idx: usize) -> f32 {
278    if idx == 0 {
279        // First segment.
280
281        debug_assert!(path.len() > 1);
282
283        let seg1 = path[0];
284        let seg2 = path[1];
285
286        match (seg1, seg2) {
287            (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y),
288            (Segment::MoveTo(pm), Segment::CubicTo(p1, _, p)) => {
289                if pm.x.approx_eq_ulps(&p1.x, 4) && pm.y.approx_eq_ulps(&p1.y, 4) {
290                    calc_line_angle(pm.x, pm.y, p.x, p.y)
291                } else {
292                    calc_line_angle(pm.x, pm.y, p1.x, p1.y)
293                }
294            }
295            _ => 0.0,
296        }
297    } else if idx == path.len() - 1 {
298        // Last segment.
299
300        let seg1 = path[idx - 1];
301        let seg2 = path[idx];
302
303        match (seg1, seg2) {
304            (_, Segment::MoveTo(_)) => 0.0, // unreachable
305            (_, Segment::LineTo(p)) => {
306                let prev = get_prev_vertex(path, idx);
307                calc_line_angle(prev.x, prev.y, p.x, p.y)
308            }
309            (_, Segment::CubicTo(p1, p2, p)) => {
310                if p2.x.approx_eq_ulps(&p.x, 4) && p2.y.approx_eq_ulps(&p.y, 4) {
311                    calc_line_angle(p1.x, p1.y, p.x, p.y)
312                } else {
313                    calc_line_angle(p2.x, p2.y, p.x, p.y)
314                }
315            }
316            (Segment::LineTo(p), Segment::Close) => {
317                let next = get_subpath_start(path, idx);
318                calc_line_angle(p.x, p.y, next.x, next.y)
319            }
320            (Segment::CubicTo(_, p2, p), Segment::Close) => {
321                let prev = get_prev_vertex(path, idx);
322                let next = get_subpath_start(path, idx);
323                calc_curves_angle(
324                    prev.x, prev.y, p2.x, p2.y, p.x, p.y, next.x, next.y, next.x, next.y,
325                )
326            }
327            (_, Segment::Close) => 0.0,
328        }
329    } else {
330        // Middle segments.
331
332        let seg1 = path[idx];
333        let seg2 = path[idx + 1];
334
335        // TODO: Not sure if there is a better way.
336        match (seg1, seg2) {
337            (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y),
338            (Segment::MoveTo(pm), Segment::CubicTo(p1, _, _)) => {
339                calc_line_angle(pm.x, pm.y, p1.x, p1.y)
340            }
341            (Segment::LineTo(p1), Segment::LineTo(p2)) => {
342                let prev = get_prev_vertex(path, idx);
343                calc_angle(prev.x, prev.y, p1.x, p1.y, p1.x, p1.y, p2.x, p2.y)
344            }
345            (Segment::CubicTo(_, c1_p2, c1_p), Segment::CubicTo(c2_p1, _, c2_p)) => {
346                let prev = get_prev_vertex(path, idx);
347                calc_curves_angle(
348                    prev.x, prev.y, c1_p2.x, c1_p2.y, c1_p.x, c1_p.y, c2_p1.x, c2_p1.y, c2_p.x,
349                    c2_p.y,
350                )
351            }
352            (Segment::LineTo(pl), Segment::CubicTo(p1, _, p)) => {
353                let prev = get_prev_vertex(path, idx);
354                calc_curves_angle(
355                    prev.x, prev.y, prev.x, prev.y, pl.x, pl.y, p1.x, p1.y, p.x, p.y,
356                )
357            }
358            (Segment::CubicTo(_, p2, p), Segment::LineTo(pl)) => {
359                let prev = get_prev_vertex(path, idx);
360                calc_curves_angle(prev.x, prev.y, p2.x, p2.y, p.x, p.y, pl.x, pl.y, pl.x, pl.y)
361            }
362            (Segment::LineTo(p), Segment::MoveTo(_)) => {
363                let prev = get_prev_vertex(path, idx);
364                calc_line_angle(prev.x, prev.y, p.x, p.y)
365            }
366            (Segment::CubicTo(_, p2, p), Segment::MoveTo(_)) => {
367                if p.x.approx_eq_ulps(&p2.x, 4) && p.y.approx_eq_ulps(&p2.y, 4) {
368                    let prev = get_prev_vertex(path, idx);
369                    calc_line_angle(prev.x, prev.y, p.x, p.y)
370                } else {
371                    calc_line_angle(p2.x, p2.y, p.x, p.y)
372                }
373            }
374            (Segment::LineTo(p), Segment::Close) => {
375                let prev = get_prev_vertex(path, idx);
376                let next = get_subpath_start(path, idx);
377                calc_angle(prev.x, prev.y, p.x, p.y, p.x, p.y, next.x, next.y)
378            }
379            (_, Segment::Close) => {
380                let prev = get_prev_vertex(path, idx);
381                let next = get_subpath_start(path, idx);
382                calc_line_angle(prev.x, prev.y, next.x, next.y)
383            }
384            (_, Segment::MoveTo(_)) | (Segment::Close, _) => 0.0,
385        }
386    }
387}
388
389fn calc_line_angle(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
390    calc_angle(x1, y1, x2, y2, x1, y1, x2, y2)
391}
392
393fn calc_curves_angle(
394    px: f32,
395    py: f32, // previous vertex
396    cx1: f32,
397    cy1: f32, // previous control point
398    x: f32,
399    y: f32, // current vertex
400    cx2: f32,
401    cy2: f32, // next control point
402    nx: f32,
403    ny: f32, // next vertex
404) -> f32 {
405    if cx1.approx_eq_ulps(&x, 4) && cy1.approx_eq_ulps(&y, 4) {
406        calc_angle(px, py, x, y, x, y, cx2, cy2)
407    } else if x.approx_eq_ulps(&cx2, 4) && y.approx_eq_ulps(&cy2, 4) {
408        calc_angle(cx1, cy1, x, y, x, y, nx, ny)
409    } else {
410        calc_angle(cx1, cy1, x, y, x, y, cx2, cy2)
411    }
412}
413
414fn calc_angle(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32) -> f32 {
415    use std::f32::consts::*;
416
417    fn normalize(rad: f32) -> f32 {
418        let v = rad % (PI * 2.0);
419        if v < 0.0 {
420            v + PI * 2.0
421        } else {
422            v
423        }
424    }
425
426    fn vector_angle(vx: f32, vy: f32) -> f32 {
427        let rad = vy.atan2(vx);
428        if rad.is_nan() {
429            0.0
430        } else {
431            normalize(rad)
432        }
433    }
434
435    let in_a = vector_angle(x2 - x1, y2 - y1);
436    let out_a = vector_angle(x4 - x3, y4 - y3);
437    let d = (out_a - in_a) * 0.5;
438
439    let mut angle = in_a + d;
440    if FRAC_PI_2 < d.abs() {
441        angle -= PI;
442    }
443
444    normalize(angle).to_degrees()
445}
446
447fn get_subpath_start(segments: &[Segment], idx: usize) -> tiny_skia_path::Point {
448    let offset = segments.len() - idx;
449    for seg in segments.iter().rev().skip(offset) {
450        if let Segment::MoveTo(p) = *seg {
451            return p;
452        }
453    }
454
455    tiny_skia_path::Point::zero()
456}
457
458fn get_prev_vertex(segments: &[Segment], idx: usize) -> tiny_skia_path::Point {
459    match segments[idx - 1] {
460        Segment::MoveTo(p) => p,
461        Segment::LineTo(p) => p,
462        Segment::CubicTo(_, _, p) => p,
463        Segment::Close => get_subpath_start(segments, idx),
464    }
465}
466
467fn convert_rect(node: SvgNode, state: &converter::State) -> Option<NonZeroRect> {
468    NonZeroRect::from_xywh(
469        node.convert_user_length(AId::RefX, state, Length::zero()),
470        node.convert_user_length(AId::RefY, state, Length::zero()),
471        node.convert_user_length(AId::MarkerWidth, state, Length::new_number(3.0)),
472        node.convert_user_length(AId::MarkerHeight, state, Length::new_number(3.0)),
473    )
474}
475
476fn convert_orientation(node: SvgNode) -> MarkerOrientation {
477    match node.attribute(AId::Orient) {
478        Some("auto") => MarkerOrientation::Auto,
479        Some("auto-start-reverse") => MarkerOrientation::AutoStartReverse,
480        _ => match node.attribute::<svgtypes::Angle>(AId::Orient) {
481            Some(angle) => MarkerOrientation::Angle(angle.to_degrees() as f32),
482            None => MarkerOrientation::Angle(0.0),
483        },
484    }
485}
486
487fn quad_to_curve(prev: Point, p1: Point, p: Point) -> (Point, Point, Point) {
488    #[inline]
489    fn calc(n1: f32, n2: f32) -> f32 {
490        (n1 + n2 * 2.0) / 3.0
491    }
492
493    (
494        Point::from_xy(calc(prev.x, p1.x), calc(prev.y, p1.y)),
495        Point::from_xy(calc(p.x, p1.x), calc(p.y, p1.y)),
496        p,
497    )
498}