usvg/parser/
marker.rs

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