Skip to main content

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, 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_ts = marker_node
102        .parse_viewbox()
103        .map(|vb| {
104            ViewBox {
105                rect: vb,
106                aspect: marker_node
107                    .attribute(AId::PreserveAspectRatio)
108                    .unwrap_or_default(),
109            }
110            .to_transform(r.size())
111        })
112        .unwrap_or_default();
113
114    let has_overflow = {
115        let overflow = marker_node.attribute(AId::Overflow);
116        // `overflow` is `hidden` by default.
117        overflow.is_none() || overflow == Some("hidden") || overflow == Some("scroll")
118    };
119
120    let clip_path = if has_overflow {
121        let clip_rect = r
122            .size()
123            .to_non_zero_rect(0.0, 0.0)
124            .transform(view_box_ts.invert()?)?;
125
126        let mut clip_path = ClipPath::empty(cache.gen_clip_path_id());
127
128        let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect(
129            clip_rect.to_rect(),
130        )))?;
131        path.fill = Some(Fill::default());
132
133        clip_path.root.children.push(Node::Path(Box::new(path)));
134        clip_path.root.calculate_bounding_boxes();
135
136        Some(Arc::new(clip_path))
137    } else {
138        None
139    };
140
141    // TODO: avoid allocation
142    let mut segments: Vec<Segment> = Vec::with_capacity(path.len());
143    let mut prev = Point::zero();
144    let mut prev_move = Point::zero();
145    for seg in path.segments() {
146        match seg {
147            tiny_skia_path::PathSegment::MoveTo(p) => {
148                segments.push(Segment::MoveTo(p));
149                prev = p;
150                prev_move = p;
151            }
152            tiny_skia_path::PathSegment::LineTo(p) => {
153                segments.push(Segment::LineTo(p));
154                prev = p;
155            }
156            tiny_skia_path::PathSegment::QuadTo(p1, p) => {
157                let (p1, p2, p) = quad_to_curve(prev, p1, p);
158                segments.push(Segment::CubicTo(p1, p2, p));
159                prev = p;
160            }
161            tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
162                segments.push(Segment::CubicTo(p1, p2, p));
163                prev = p;
164            }
165            tiny_skia_path::PathSegment::Close => {
166                segments.push(Segment::Close);
167                prev = prev_move;
168            }
169        }
170    }
171
172    let draw_marker = |p: tiny_skia_path::Point, idx: usize| {
173        let mut ts = Transform::from_translate(p.x, p.y);
174
175        let angle = match convert_orientation(marker_node) {
176            MarkerOrientation::AutoStartReverse if idx == 0 => {
177                (calc_vertex_angle(&segments, idx) + 180.0) % 360.0
178            }
179            MarkerOrientation::Auto | MarkerOrientation::AutoStartReverse => {
180                calc_vertex_angle(&segments, idx)
181            }
182            MarkerOrientation::Angle(angle) => angle,
183        };
184
185        if !angle.approx_zero_ulps(4) {
186            ts = ts.pre_rotate(angle);
187        }
188
189        let (sx, sy) = view_box_ts.get_scale();
190        ts = ts.pre_scale(sx * stroke_scale, sy * stroke_scale);
191        ts = ts.pre_translate(-r.x(), -r.y());
192
193        // TODO: do not create a group when no clipPath
194        let mut g = Group {
195            transform: ts,
196            abs_transform: parent.abs_transform.pre_concat(ts),
197            clip_path: clip_path.clone(),
198            ..Group::empty()
199        };
200
201        let mut marker_state = state.clone();
202        marker_state.parent_markers.push(marker_node);
203        converter::convert_children(marker_node, &marker_state, cache, &mut g);
204        g.calculate_bounding_boxes();
205
206        if g.has_children() {
207            parent.children.push(Node::Group(Box::new(g)));
208        }
209    };
210
211    draw_markers(&segments, marker_kind, draw_marker);
212
213    Some(())
214}
215
216fn stroke_scale(
217    path_node: SvgNode,
218    marker_node: SvgNode,
219    state: &converter::State,
220) -> Option<NonZeroPositiveF32> {
221    match marker_node.attribute(AId::MarkerUnits) {
222        Some("userSpaceOnUse") => NonZeroPositiveF32::new(1.0),
223        _ => path_node.resolve_valid_length(AId::StrokeWidth, state, 1.0),
224    }
225}
226
227fn draw_markers<P>(path: &[Segment], kind: MarkerKind, mut draw_marker: P)
228where
229    P: FnMut(tiny_skia_path::Point, usize),
230{
231    match kind {
232        MarkerKind::Start => {
233            if let Some(Segment::MoveTo(p)) = path.first().cloned() {
234                draw_marker(p, 0);
235            }
236        }
237        MarkerKind::Middle => {
238            let total = path.len() - 1;
239            let mut i = 1;
240            while i < total {
241                let p = match path[i] {
242                    Segment::MoveTo(p) => p,
243                    Segment::LineTo(p) => p,
244                    Segment::CubicTo(_, _, p) => p,
245                    _ => {
246                        i += 1;
247                        continue;
248                    }
249                };
250
251                draw_marker(p, i);
252
253                i += 1;
254            }
255        }
256        MarkerKind::End => {
257            let idx = path.len() - 1;
258            match path.last().cloned() {
259                Some(Segment::LineTo(p)) => {
260                    draw_marker(p, idx);
261                }
262                Some(Segment::CubicTo(_, _, p)) => {
263                    draw_marker(p, idx);
264                }
265                Some(Segment::Close) => {
266                    let p = get_subpath_start(path, idx);
267                    draw_marker(p, idx);
268                }
269                _ => {}
270            }
271        }
272    }
273}
274
275fn calc_vertex_angle(path: &[Segment], idx: usize) -> f32 {
276    if idx == 0 {
277        // First segment.
278
279        debug_assert!(path.len() > 1);
280
281        let seg1 = path[0];
282        let seg2 = path[1];
283
284        match (seg1, seg2) {
285            (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y),
286            (Segment::MoveTo(pm), Segment::CubicTo(p1, _, p)) => {
287                if pm.x.approx_eq_ulps(&p1.x, 4) && pm.y.approx_eq_ulps(&p1.y, 4) {
288                    calc_line_angle(pm.x, pm.y, p.x, p.y)
289                } else {
290                    calc_line_angle(pm.x, pm.y, p1.x, p1.y)
291                }
292            }
293            _ => 0.0,
294        }
295    } else if idx == path.len() - 1 {
296        // Last segment.
297
298        let seg1 = path[idx - 1];
299        let seg2 = path[idx];
300
301        match (seg1, seg2) {
302            (_, Segment::MoveTo(_)) => 0.0, // unreachable
303            (_, Segment::LineTo(p)) => {
304                let prev = get_prev_vertex(path, idx);
305                calc_line_angle(prev.x, prev.y, p.x, p.y)
306            }
307            (_, Segment::CubicTo(p1, p2, p)) => {
308                if p2.x.approx_eq_ulps(&p.x, 4) && p2.y.approx_eq_ulps(&p.y, 4) {
309                    calc_line_angle(p1.x, p1.y, p.x, p.y)
310                } else {
311                    calc_line_angle(p2.x, p2.y, p.x, p.y)
312                }
313            }
314            (Segment::LineTo(p), Segment::Close) => {
315                let next = get_subpath_start(path, idx);
316                calc_line_angle(p.x, p.y, next.x, next.y)
317            }
318            (Segment::CubicTo(_, p2, p), Segment::Close) => {
319                let prev = get_prev_vertex(path, idx);
320                let next = get_subpath_start(path, idx);
321                calc_curves_angle(
322                    prev.x, prev.y, p2.x, p2.y, p.x, p.y, next.x, next.y, next.x, next.y,
323                )
324            }
325            (_, Segment::Close) => 0.0,
326        }
327    } else {
328        // Middle segments.
329
330        let seg1 = path[idx];
331        let seg2 = path[idx + 1];
332
333        // TODO: Not sure if there is a better way.
334        match (seg1, seg2) {
335            (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y),
336            (Segment::MoveTo(pm), Segment::CubicTo(p1, _, _)) => {
337                calc_line_angle(pm.x, pm.y, p1.x, p1.y)
338            }
339            (Segment::LineTo(p1), Segment::LineTo(p2)) => {
340                let prev = get_prev_vertex(path, idx);
341                calc_angle(prev.x, prev.y, p1.x, p1.y, p1.x, p1.y, p2.x, p2.y)
342            }
343            (Segment::CubicTo(_, c1_p2, c1_p), Segment::CubicTo(c2_p1, _, c2_p)) => {
344                let prev = get_prev_vertex(path, idx);
345                calc_curves_angle(
346                    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,
347                    c2_p.y,
348                )
349            }
350            (Segment::LineTo(pl), Segment::CubicTo(p1, _, p)) => {
351                let prev = get_prev_vertex(path, idx);
352                calc_curves_angle(
353                    prev.x, prev.y, prev.x, prev.y, pl.x, pl.y, p1.x, p1.y, p.x, p.y,
354                )
355            }
356            (Segment::CubicTo(_, p2, p), Segment::LineTo(pl)) => {
357                let prev = get_prev_vertex(path, idx);
358                calc_curves_angle(prev.x, prev.y, p2.x, p2.y, p.x, p.y, pl.x, pl.y, pl.x, pl.y)
359            }
360            (Segment::LineTo(p), Segment::MoveTo(_)) => {
361                let prev = get_prev_vertex(path, idx);
362                calc_line_angle(prev.x, prev.y, p.x, p.y)
363            }
364            (Segment::CubicTo(_, p2, p), Segment::MoveTo(_)) => {
365                if p.x.approx_eq_ulps(&p2.x, 4) && p.y.approx_eq_ulps(&p2.y, 4) {
366                    let prev = get_prev_vertex(path, idx);
367                    calc_line_angle(prev.x, prev.y, p.x, p.y)
368                } else {
369                    calc_line_angle(p2.x, p2.y, p.x, p.y)
370                }
371            }
372            (Segment::LineTo(p), Segment::Close) => {
373                let prev = get_prev_vertex(path, idx);
374                let next = get_subpath_start(path, idx);
375                calc_angle(prev.x, prev.y, p.x, p.y, p.x, p.y, next.x, next.y)
376            }
377            (_, Segment::Close) => {
378                let prev = get_prev_vertex(path, idx);
379                let next = get_subpath_start(path, idx);
380                calc_line_angle(prev.x, prev.y, next.x, next.y)
381            }
382            (_, Segment::MoveTo(_)) | (Segment::Close, _) => 0.0,
383        }
384    }
385}
386
387fn calc_line_angle(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
388    calc_angle(x1, y1, x2, y2, x1, y1, x2, y2)
389}
390
391fn calc_curves_angle(
392    px: f32,
393    py: f32, // previous vertex
394    cx1: f32,
395    cy1: f32, // previous control point
396    x: f32,
397    y: f32, // current vertex
398    cx2: f32,
399    cy2: f32, // next control point
400    nx: f32,
401    ny: f32, // next vertex
402) -> f32 {
403    if cx1.approx_eq_ulps(&x, 4) && cy1.approx_eq_ulps(&y, 4) {
404        calc_angle(px, py, x, y, x, y, cx2, cy2)
405    } else if x.approx_eq_ulps(&cx2, 4) && y.approx_eq_ulps(&cy2, 4) {
406        calc_angle(cx1, cy1, x, y, x, y, nx, ny)
407    } else {
408        calc_angle(cx1, cy1, x, y, x, y, cx2, cy2)
409    }
410}
411
412fn calc_angle(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32) -> f32 {
413    use std::f32::consts::*;
414
415    fn normalize(rad: f32) -> f32 {
416        let v = rad % (PI * 2.0);
417        if v < 0.0 { v + PI * 2.0 } else { v }
418    }
419
420    fn vector_angle(vx: f32, vy: f32) -> f32 {
421        let rad = vy.atan2(vx);
422        if rad.is_nan() { 0.0 } else { normalize(rad) }
423    }
424
425    let in_a = vector_angle(x2 - x1, y2 - y1);
426    let out_a = vector_angle(x4 - x3, y4 - y3);
427    let d = (out_a - in_a) * 0.5;
428
429    let mut angle = in_a + d;
430    if FRAC_PI_2 < d.abs() {
431        angle -= PI;
432    }
433
434    normalize(angle).to_degrees()
435}
436
437fn get_subpath_start(segments: &[Segment], idx: usize) -> tiny_skia_path::Point {
438    let offset = segments.len() - idx;
439    for seg in segments.iter().rev().skip(offset) {
440        if let Segment::MoveTo(p) = *seg {
441            return p;
442        }
443    }
444
445    tiny_skia_path::Point::zero()
446}
447
448fn get_prev_vertex(segments: &[Segment], idx: usize) -> tiny_skia_path::Point {
449    match segments[idx - 1] {
450        Segment::MoveTo(p) => p,
451        Segment::LineTo(p) => p,
452        Segment::CubicTo(_, _, p) => p,
453        Segment::Close => get_subpath_start(segments, idx),
454    }
455}
456
457fn convert_rect(node: SvgNode, state: &converter::State) -> Option<NonZeroRect> {
458    NonZeroRect::from_xywh(
459        node.convert_user_length(AId::RefX, state, Length::zero()),
460        node.convert_user_length(AId::RefY, state, Length::zero()),
461        node.convert_user_length(AId::MarkerWidth, state, Length::new_number(3.0)),
462        node.convert_user_length(AId::MarkerHeight, state, Length::new_number(3.0)),
463    )
464}
465
466fn convert_orientation(node: SvgNode) -> MarkerOrientation {
467    match node.attribute(AId::Orient) {
468        Some("auto") => MarkerOrientation::Auto,
469        Some("auto-start-reverse") => MarkerOrientation::AutoStartReverse,
470        _ => match node.attribute::<svgtypes::Angle>(AId::Orient) {
471            Some(angle) => MarkerOrientation::Angle(angle.to_degrees() as f32),
472            None => MarkerOrientation::Angle(0.0),
473        },
474    }
475}
476
477fn quad_to_curve(prev: Point, p1: Point, p: Point) -> (Point, Point, Point) {
478    #[inline]
479    fn calc(n1: f32, n2: f32) -> f32 {
480        (n1 + n2 * 2.0) / 3.0
481    }
482
483    (
484        Point::from_xy(calc(prev.x, p1.x), calc(prev.y, p1.y)),
485        Point::from_xy(calc(p.x, p1.x), calc(p.y, p1.y)),
486        p,
487    )
488}