rsvg/
marker.rs

1//! The `marker` element, and geometry computations for markers.
2
3use std::f64::consts::*;
4use std::ops::Deref;
5
6use cssparser::Parser;
7use markup5ever::{expanded_name, local_name, ns};
8
9use crate::angle::Angle;
10use crate::aspect_ratio::*;
11use crate::bbox::BoundingBox;
12use crate::borrow_element_as;
13use crate::document::AcquiredNodes;
14use crate::drawing_ctx::{DrawingCtx, Viewport};
15use crate::element::{DrawResult, ElementTrait, set_attribute};
16use crate::error::*;
17use crate::float_eq_cairo::ApproxEqCairo;
18use crate::layout::{self, Shape, StackingContext};
19use crate::length::*;
20use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw};
21use crate::parse_identifiers;
22use crate::parsers::{Parse, ParseValue};
23use crate::path_builder::{ArcParameterization, CubicBezierCurve, Path, PathCommand, arc_segment};
24use crate::rect::Rect;
25use crate::rsvg_log;
26use crate::session::Session;
27use crate::transform::Transform;
28use crate::viewbox::*;
29use crate::xml::Attributes;
30
31// markerUnits attribute: https://www.w3.org/TR/SVG/painting.html#MarkerElement
32#[derive(Debug, Default, Copy, Clone, PartialEq)]
33enum MarkerUnits {
34    UserSpaceOnUse,
35    #[default]
36    StrokeWidth,
37}
38
39impl Parse for MarkerUnits {
40    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<MarkerUnits, ParseError<'i>> {
41        Ok(parse_identifiers!(
42            parser,
43            "userSpaceOnUse" => MarkerUnits::UserSpaceOnUse,
44            "strokeWidth" => MarkerUnits::StrokeWidth,
45        )?)
46    }
47}
48
49// orient attribute: https://www.w3.org/TR/SVG/painting.html#MarkerElement
50#[derive(Debug, Copy, Clone, PartialEq)]
51enum MarkerOrient {
52    Auto,
53    AutoStartReverse,
54    Angle(Angle),
55}
56
57impl Default for MarkerOrient {
58    #[inline]
59    fn default() -> MarkerOrient {
60        MarkerOrient::Angle(Angle::new(0.0))
61    }
62}
63
64impl Parse for MarkerOrient {
65    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<MarkerOrient, ParseError<'i>> {
66        if parser
67            .try_parse(|p| p.expect_ident_matching("auto"))
68            .is_ok()
69        {
70            return Ok(MarkerOrient::Auto);
71        }
72
73        if parser
74            .try_parse(|p| p.expect_ident_matching("auto-start-reverse"))
75            .is_ok()
76        {
77            Ok(MarkerOrient::AutoStartReverse)
78        } else {
79            Angle::parse(parser).map(MarkerOrient::Angle)
80        }
81    }
82}
83
84pub struct Marker {
85    units: MarkerUnits,
86    ref_x: Length<Horizontal>,
87    ref_y: Length<Vertical>,
88    width: ULength<Horizontal>,
89    height: ULength<Vertical>,
90    orient: MarkerOrient,
91    aspect: AspectRatio,
92    vbox: Option<ViewBox>,
93}
94
95impl Default for Marker {
96    fn default() -> Marker {
97        Marker {
98            units: MarkerUnits::default(),
99            ref_x: Default::default(),
100            ref_y: Default::default(),
101            // the following two are per the spec
102            width: ULength::<Horizontal>::parse_str("3").unwrap(),
103            height: ULength::<Vertical>::parse_str("3").unwrap(),
104            orient: MarkerOrient::default(),
105            aspect: AspectRatio::default(),
106            vbox: None,
107        }
108    }
109}
110
111impl Marker {
112    fn render(
113        &self,
114        node: &Node,
115        acquired_nodes: &mut AcquiredNodes<'_>,
116        viewport: &Viewport,
117        draw_ctx: &mut DrawingCtx,
118        xpos: f64,
119        ypos: f64,
120        computed_angle: Angle,
121        line_width: f64,
122        clipping: bool,
123        marker_type: MarkerType,
124        marker: &layout::Marker,
125    ) -> DrawResult {
126        let mut cascaded = CascadedValues::new_from_node(node);
127        cascaded.context_fill = Some(marker.context_fill.clone());
128        cascaded.context_stroke = Some(marker.context_stroke.clone());
129
130        let values = cascaded.get();
131
132        let params = NormalizeParams::new(values, viewport);
133
134        let marker_width = self.width.to_user(&params);
135        let marker_height = self.height.to_user(&params);
136
137        if marker_width.approx_eq_cairo(0.0) || marker_height.approx_eq_cairo(0.0) {
138            // markerWidth or markerHeight set to 0 disables rendering of the element
139            // https://www.w3.org/TR/SVG/painting.html#MarkerWidthAttribute
140            return Ok(viewport.empty_bbox());
141        }
142
143        let rotation = match self.orient {
144            MarkerOrient::Auto => computed_angle,
145            MarkerOrient::AutoStartReverse => {
146                if marker_type == MarkerType::Start {
147                    computed_angle.flip()
148                } else {
149                    computed_angle
150                }
151            }
152            MarkerOrient::Angle(a) => a,
153        };
154
155        let mut transform = Transform::new_translate(xpos, ypos).pre_rotate(rotation);
156
157        if self.units == MarkerUnits::StrokeWidth {
158            transform = transform.pre_scale(line_width, line_width);
159        }
160
161        let content_viewport = if let Some(vbox) = self.vbox {
162            if vbox.is_empty() {
163                return Ok(viewport.empty_bbox());
164            }
165
166            let r = self
167                .aspect
168                .compute(&vbox, &Rect::from_size(marker_width, marker_height));
169
170            let (vb_width, vb_height) = vbox.size();
171            transform = transform.pre_scale(r.width() / vb_width, r.height() / vb_height);
172
173            viewport.with_view_box(vb_width, vb_height)
174        } else {
175            viewport.with_view_box(marker_width, marker_height)
176        };
177
178        let content_params = NormalizeParams::new(values, &content_viewport);
179
180        transform = transform.pre_translate(
181            -self.ref_x.to_user(&content_params),
182            -self.ref_y.to_user(&content_params),
183        );
184
185        // FIXME: This is the only place in the code where we pass a Some(rect) to
186        // StackingContext::new() for its clip_rect argument.  The effect is to clip to
187        // the viewport that the current marker should establish, but this code for
188        // drawing markers does not yet use the layout_viewport argument in the call to
189        // with_discrete_layer() below and instead does things by hand.  We should do all
190        // this with the layout_viewport instead of clip_rect, and then we can remove the
191        // clip_rect field in StackingContext.
192        let clip_rect = if values.is_overflow() {
193            None
194        } else if let Some(vbox) = self.vbox {
195            Some(*vbox)
196        } else {
197            Some(Rect::from_size(marker_width, marker_height))
198        };
199
200        let elt = node.borrow_element();
201        let stacking_ctx = Box::new(StackingContext::new(
202            draw_ctx.session(),
203            acquired_nodes,
204            &elt,
205            transform,
206            clip_rect,
207            values,
208        ));
209
210        draw_ctx.with_discrete_layer(
211            &stacking_ctx,
212            acquired_nodes,
213            &content_viewport,
214            None,
215            clipping,
216            &mut |an, dc, new_viewport| {
217                node.draw_children(an, &cascaded, new_viewport, dc, clipping)
218            }, // content_viewport
219        )
220    }
221}
222
223impl ElementTrait for Marker {
224    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
225        for (attr, value) in attrs.iter() {
226            match attr.expanded() {
227                expanded_name!("", "markerUnits") => {
228                    set_attribute(&mut self.units, attr.parse(value), session)
229                }
230                expanded_name!("", "refX") => {
231                    set_attribute(&mut self.ref_x, attr.parse(value), session)
232                }
233                expanded_name!("", "refY") => {
234                    set_attribute(&mut self.ref_y, attr.parse(value), session)
235                }
236                expanded_name!("", "markerWidth") => {
237                    set_attribute(&mut self.width, attr.parse(value), session)
238                }
239                expanded_name!("", "markerHeight") => {
240                    set_attribute(&mut self.height, attr.parse(value), session)
241                }
242                expanded_name!("", "orient") => {
243                    set_attribute(&mut self.orient, attr.parse(value), session)
244                }
245                expanded_name!("", "preserveAspectRatio") => {
246                    set_attribute(&mut self.aspect, attr.parse(value), session)
247                }
248                expanded_name!("", "viewBox") => {
249                    set_attribute(&mut self.vbox, attr.parse(value), session)
250                }
251                _ => (),
252            }
253        }
254    }
255}
256
257// Machinery to figure out marker orientations
258#[derive(Debug, PartialEq)]
259enum Segment {
260    Degenerate {
261        // A single lone point
262        x: f64,
263        y: f64,
264    },
265
266    LineOrCurve {
267        x1: f64,
268        y1: f64,
269        x2: f64,
270        y2: f64,
271        x3: f64,
272        y3: f64,
273        x4: f64,
274        y4: f64,
275    },
276}
277
278impl Segment {
279    fn degenerate(x: f64, y: f64) -> Segment {
280        Segment::Degenerate { x, y }
281    }
282
283    fn curve(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Segment {
284        Segment::LineOrCurve {
285            x1,
286            y1,
287            x2,
288            y2,
289            x3,
290            y3,
291            x4,
292            y4,
293        }
294    }
295
296    fn line(x1: f64, y1: f64, x2: f64, y2: f64) -> Segment {
297        Segment::curve(x1, y1, x2, y2, x1, y1, x2, y2)
298    }
299
300    // If the segment has directionality, returns two vectors (v1x, v1y, v2x, v2y); otherwise,
301    // returns None.  The vectors are the tangents at the beginning and at the end of the segment,
302    // respectively.  A segment does not have directionality if it is degenerate (i.e. a single
303    // point) or a zero-length segment, i.e. where all four control points are coincident (the first
304    // and last control points may coincide, but the others may define a loop - thus nonzero length)
305    fn get_directionalities(&self) -> Option<(f64, f64, f64, f64)> {
306        match *self {
307            Segment::Degenerate { .. } => None,
308
309            Segment::LineOrCurve {
310                x1,
311                y1,
312                x2,
313                y2,
314                x3,
315                y3,
316                x4,
317                y4,
318            } => {
319                let coincide_1_and_2 = points_equal(x1, y1, x2, y2);
320                let coincide_1_and_3 = points_equal(x1, y1, x3, y3);
321                let coincide_1_and_4 = points_equal(x1, y1, x4, y4);
322                let coincide_2_and_3 = points_equal(x2, y2, x3, y3);
323                let coincide_2_and_4 = points_equal(x2, y2, x4, y4);
324                let coincide_3_and_4 = points_equal(x3, y3, x4, y4);
325
326                if coincide_1_and_2 && coincide_1_and_3 && coincide_1_and_4 {
327                    None
328                } else if coincide_1_and_2 && coincide_1_and_3 {
329                    Some((x4 - x1, y4 - y1, x4 - x3, y4 - y3))
330                } else if coincide_1_and_2 && coincide_3_and_4 {
331                    Some((x4 - x1, y4 - y1, x4 - x1, y4 - y1))
332                } else if coincide_2_and_3 && coincide_2_and_4 {
333                    Some((x2 - x1, y2 - y1, x4 - x1, y4 - y1))
334                } else if coincide_1_and_2 {
335                    Some((x3 - x1, y3 - y1, x4 - x3, y4 - y3))
336                } else if coincide_3_and_4 {
337                    Some((x2 - x1, y2 - y1, x4 - x2, y4 - y2))
338                } else {
339                    Some((x2 - x1, y2 - y1, x4 - x3, y4 - y3))
340                }
341            }
342        }
343    }
344}
345
346fn points_equal(x1: f64, y1: f64, x2: f64, y2: f64) -> bool {
347    x1.approx_eq_cairo(x2) && y1.approx_eq_cairo(y2)
348}
349
350enum SegmentState {
351    Initial,
352    NewSubpath,
353    InSubpath,
354    ClosedSubpath,
355}
356
357#[derive(Debug, PartialEq)]
358struct Segments(Vec<Segment>);
359
360impl Deref for Segments {
361    type Target = [Segment];
362
363    fn deref(&self) -> &[Segment] {
364        &self.0
365    }
366}
367
368// This converts a path builder into a vector of curveto-like segments.
369// Each segment can be:
370//
371// 1. Segment::Degenerate => the segment is actually a single point (x, y)
372//
373// 2. Segment::LineOrCurve => either a lineto or a curveto (or the effective
374// lineto that results from a closepath).
375// We have the following points:
376//       P1 = (x1, y1)
377//       P2 = (x2, y2)
378//       P3 = (x3, y3)
379//       P4 = (x4, y4)
380//
381// The start and end points are P1 and P4, respectively.
382// The tangent at the start point is given by the vector (P2 - P1).
383// The tangent at the end point is given by the vector (P4 - P3).
384// The tangents also work if the segment refers to a lineto (they will
385// both just point in the same direction).
386impl From<&Path> for Segments {
387    fn from(path: &Path) -> Segments {
388        let mut last_x: f64;
389        let mut last_y: f64;
390
391        let mut cur_x: f64 = 0.0;
392        let mut cur_y: f64 = 0.0;
393        let mut subpath_start_x: f64 = 0.0;
394        let mut subpath_start_y: f64 = 0.0;
395
396        let mut segments = Vec::new();
397        let mut state = SegmentState::Initial;
398
399        for path_command in path.iter() {
400            last_x = cur_x;
401            last_y = cur_y;
402
403            match path_command {
404                PathCommand::MoveTo(x, y) => {
405                    cur_x = x;
406                    cur_y = y;
407
408                    subpath_start_x = cur_x;
409                    subpath_start_y = cur_y;
410
411                    match state {
412                        SegmentState::Initial | SegmentState::InSubpath => {
413                            // Ignore the very first moveto in a sequence (Initial state),
414                            // or if we were already drawing within a subpath, start
415                            // a new subpath.
416                            state = SegmentState::NewSubpath;
417                        }
418
419                        SegmentState::NewSubpath => {
420                            // We had just begun a new subpath (i.e. from a moveto) and we got
421                            // another moveto?  Output a stray point for the
422                            // previous moveto.
423                            segments.push(Segment::degenerate(last_x, last_y));
424                            state = SegmentState::NewSubpath;
425                        }
426
427                        SegmentState::ClosedSubpath => {
428                            // Cairo outputs a moveto after every closepath, so that subsequent
429                            // lineto/curveto commands will start at the closed vertex.
430                            // We don't want to actually emit a point (a degenerate segment) in
431                            // that artificial-moveto case.
432                            //
433                            // We'll reset to the Initial state so that a subsequent "real"
434                            // moveto will be handled as the beginning of a new subpath, or a
435                            // degenerate point, as usual.
436                            state = SegmentState::Initial;
437                        }
438                    }
439                }
440
441                PathCommand::LineTo(x, y) => {
442                    cur_x = x;
443                    cur_y = y;
444
445                    segments.push(Segment::line(last_x, last_y, cur_x, cur_y));
446
447                    state = SegmentState::InSubpath;
448                }
449
450                PathCommand::CurveTo(curve) => {
451                    let CubicBezierCurve {
452                        pt1: (x2, y2),
453                        pt2: (x3, y3),
454                        to,
455                    } = curve;
456                    cur_x = to.0;
457                    cur_y = to.1;
458
459                    segments.push(Segment::curve(last_x, last_y, x2, y2, x3, y3, cur_x, cur_y));
460
461                    state = SegmentState::InSubpath;
462                }
463
464                PathCommand::Arc(arc) => {
465                    cur_x = arc.to.0;
466                    cur_y = arc.to.1;
467
468                    match arc.center_parameterization() {
469                        ArcParameterization::CenterParameters {
470                            center,
471                            radii,
472                            theta1,
473                            delta_theta,
474                        } => {
475                            let rot = arc.x_axis_rotation;
476                            let theta2 = theta1 + delta_theta;
477                            let n_segs = (delta_theta / (PI * 0.5 + 0.001)).abs().ceil() as u32;
478                            let d_theta = delta_theta / f64::from(n_segs);
479
480                            let segment1 =
481                                arc_segment(center, radii, rot, theta1, theta1 + d_theta);
482                            let segment2 =
483                                arc_segment(center, radii, rot, theta2 - d_theta, theta2);
484
485                            let (x2, y2) = segment1.pt1;
486                            let (x3, y3) = segment2.pt2;
487                            segments
488                                .push(Segment::curve(last_x, last_y, x2, y2, x3, y3, cur_x, cur_y));
489
490                            state = SegmentState::InSubpath;
491                        }
492                        ArcParameterization::LineTo => {
493                            segments.push(Segment::line(last_x, last_y, cur_x, cur_y));
494
495                            state = SegmentState::InSubpath;
496                        }
497                        ArcParameterization::Omit => {}
498                    }
499                }
500
501                PathCommand::ClosePath => {
502                    cur_x = subpath_start_x;
503                    cur_y = subpath_start_y;
504
505                    segments.push(Segment::line(last_x, last_y, cur_x, cur_y));
506
507                    state = SegmentState::ClosedSubpath;
508                }
509            }
510        }
511
512        if let SegmentState::NewSubpath = state {
513            // Output a lone point if we started a subpath with a moveto
514            // command, but there are no subsequent commands.
515            segments.push(Segment::degenerate(cur_x, cur_y));
516        };
517
518        Segments(segments)
519    }
520}
521
522// The SVG spec 1.1 says http://www.w3.org/TR/SVG/implnote.html#PathElementImplementationNotes
523// Certain line-capping and line-joining situations and markers
524// require that a path segment have directionality at its start and
525// end points. Zero-length path segments have no directionality. In
526// these cases, the following algorithm is used to establish
527// directionality:  to determine the directionality of the start
528// point of a zero-length path segment, go backwards in the path
529// data specification within the current subpath until you find a
530// segment which has directionality at its end point (e.g., a path
531// segment with non-zero length) and use its ending direction;
532// otherwise, temporarily consider the start point to lack
533// directionality. Similarly, to determine the directionality of the
534// end point of a zero-length path segment, go forwards in the path
535// data specification within the current subpath until you find a
536// segment which has directionality at its start point (e.g., a path
537// segment with non-zero length) and use its starting direction;
538// otherwise, temporarily consider the end point to lack
539// directionality. If the start point has directionality but the end
540// point doesn't, then the end point uses the start point's
541// directionality. If the end point has directionality but the start
542// point doesn't, then the start point uses the end point's
543// directionality. Otherwise, set the directionality for the path
544// segment's start and end points to align with the positive x-axis
545// in user space.
546impl Segments {
547    fn find_incoming_angle_backwards(&self, start_index: usize) -> Option<Angle> {
548        // "go backwards ... within the current subpath until ... segment which has directionality
549        // at its end point"
550        for segment in self[..=start_index].iter().rev() {
551            match *segment {
552                Segment::Degenerate { .. } => {
553                    return None; // reached the beginning of the subpath as we ran into a standalone point
554                }
555
556                Segment::LineOrCurve { .. } => match segment.get_directionalities() {
557                    Some((_, _, v2x, v2y)) => {
558                        return Some(Angle::from_vector(v2x, v2y));
559                    }
560                    None => {
561                        continue;
562                    }
563                },
564            }
565        }
566
567        None
568    }
569
570    fn find_outgoing_angle_forwards(&self, start_index: usize) -> Option<Angle> {
571        // "go forwards ... within the current subpath until ... segment which has directionality at
572        // its start point"
573        for segment in &self[start_index..] {
574            match *segment {
575                Segment::Degenerate { .. } => {
576                    return None; // reached the end of a subpath as we ran into a standalone point
577                }
578
579                Segment::LineOrCurve { .. } => match segment.get_directionalities() {
580                    Some((v1x, v1y, _, _)) => {
581                        return Some(Angle::from_vector(v1x, v1y));
582                    }
583                    None => {
584                        continue;
585                    }
586                },
587            }
588        }
589
590        None
591    }
592}
593
594// From SVG's marker-start, marker-mid, marker-end properties
595#[derive(Debug, Copy, Clone, PartialEq)]
596enum MarkerType {
597    Start,
598    Middle,
599    End,
600}
601
602fn emit_marker_by_node(
603    viewport: &Viewport,
604    draw_ctx: &mut DrawingCtx,
605    acquired_nodes: &mut AcquiredNodes<'_>,
606    marker: &layout::Marker,
607    xpos: f64,
608    ypos: f64,
609    computed_angle: Angle,
610    line_width: f64,
611    clipping: bool,
612    marker_type: MarkerType,
613) -> DrawResult {
614    match acquired_nodes.acquire_ref(marker.node_ref.as_ref().unwrap()) {
615        Ok(acquired) => {
616            let node = acquired.get();
617
618            let marker_elt = borrow_element_as!(node, Marker);
619
620            marker_elt.render(
621                node,
622                acquired_nodes,
623                viewport,
624                draw_ctx,
625                xpos,
626                ypos,
627                computed_angle,
628                line_width,
629                clipping,
630                marker_type,
631                marker,
632            )
633        }
634
635        Err(e) => {
636            rsvg_log!(draw_ctx.session(), "could not acquire marker: {}", e);
637            Ok(viewport.empty_bbox())
638        }
639    }
640}
641
642#[derive(Debug, Copy, Clone, PartialEq)]
643enum MarkerEndpoint {
644    Start,
645    End,
646}
647
648fn emit_marker<E>(
649    segment: &Segment,
650    endpoint: MarkerEndpoint,
651    marker_type: MarkerType,
652    orient: Angle,
653    emit_fn: &mut E,
654) -> DrawResult
655where
656    E: FnMut(MarkerType, f64, f64, Angle) -> DrawResult,
657{
658    let (x, y) = match *segment {
659        Segment::Degenerate { x, y } => (x, y),
660
661        Segment::LineOrCurve { x1, y1, x4, y4, .. } => match endpoint {
662            MarkerEndpoint::Start => (x1, y1),
663            MarkerEndpoint::End => (x4, y4),
664        },
665    };
666
667    emit_fn(marker_type, x, y, orient)
668}
669
670pub fn render_markers_for_shape(
671    shape: &Shape,
672    viewport: &Viewport,
673    draw_ctx: &mut DrawingCtx,
674    acquired_nodes: &mut AcquiredNodes<'_>,
675    clipping: bool,
676) -> DrawResult {
677    if shape.stroke.width.approx_eq_cairo(0.0) {
678        return Ok(viewport.empty_bbox());
679    }
680
681    if shape.marker_start.node_ref.is_none()
682        && shape.marker_mid.node_ref.is_none()
683        && shape.marker_end.node_ref.is_none()
684    {
685        return Ok(viewport.empty_bbox());
686    }
687
688    emit_markers_for_path(
689        &shape.path.path,
690        viewport.empty_bbox(),
691        &mut |marker_type: MarkerType, x: f64, y: f64, computed_angle: Angle| {
692            let marker = match marker_type {
693                MarkerType::Start => &shape.marker_start,
694                MarkerType::Middle => &shape.marker_mid,
695                MarkerType::End => &shape.marker_end,
696            };
697
698            if marker.node_ref.is_some() {
699                emit_marker_by_node(
700                    viewport,
701                    draw_ctx,
702                    acquired_nodes,
703                    marker,
704                    x,
705                    y,
706                    computed_angle,
707                    shape.stroke.width,
708                    clipping,
709                    marker_type,
710                )
711            } else {
712                Ok(viewport.empty_bbox())
713            }
714        },
715    )
716}
717
718fn emit_markers_for_path<E>(
719    path: &Path,
720    empty_bbox: Box<BoundingBox>,
721    emit_fn: &mut E,
722) -> DrawResult
723where
724    E: FnMut(MarkerType, f64, f64, Angle) -> DrawResult,
725{
726    enum SubpathState {
727        NoSubpath,
728        InSubpath,
729    }
730
731    let mut bbox = empty_bbox;
732
733    // Convert the path to a list of segments and bare points
734    let segments = Segments::from(path);
735
736    let mut subpath_state = SubpathState::NoSubpath;
737
738    for (i, segment) in segments.iter().enumerate() {
739        match *segment {
740            Segment::Degenerate { .. } => {
741                if let SubpathState::InSubpath = subpath_state {
742                    assert!(i > 0);
743
744                    // Got a lone point after a subpath; render the subpath's end marker first
745                    let angle = segments
746                        .find_incoming_angle_backwards(i - 1)
747                        .unwrap_or_else(|| Angle::new(0.0));
748                    let marker_bbox = emit_marker(
749                        &segments[i - 1],
750                        MarkerEndpoint::End,
751                        MarkerType::End,
752                        angle,
753                        emit_fn,
754                    )?;
755                    bbox.insert(&marker_bbox);
756                }
757
758                // Render marker for the lone point; no directionality
759                let marker_bbox = emit_marker(
760                    segment,
761                    MarkerEndpoint::Start,
762                    MarkerType::Middle,
763                    Angle::new(0.0),
764                    emit_fn,
765                )?;
766                bbox.insert(&marker_bbox);
767
768                subpath_state = SubpathState::NoSubpath;
769            }
770
771            Segment::LineOrCurve { .. } => {
772                // Not a degenerate segment
773                match subpath_state {
774                    SubpathState::NoSubpath => {
775                        let angle = segments
776                            .find_outgoing_angle_forwards(i)
777                            .unwrap_or_else(|| Angle::new(0.0));
778                        let marker_bbox = emit_marker(
779                            segment,
780                            MarkerEndpoint::Start,
781                            MarkerType::Start,
782                            angle,
783                            emit_fn,
784                        )?;
785                        bbox.insert(&marker_bbox);
786
787                        subpath_state = SubpathState::InSubpath;
788                    }
789
790                    SubpathState::InSubpath => {
791                        assert!(i > 0);
792
793                        let incoming = segments.find_incoming_angle_backwards(i - 1);
794                        let outgoing = segments.find_outgoing_angle_forwards(i);
795
796                        let angle = match (incoming, outgoing) {
797                            (Some(incoming), Some(outgoing)) => incoming.bisect(outgoing),
798                            (Some(incoming), _) => incoming,
799                            (_, Some(outgoing)) => outgoing,
800                            _ => Angle::new(0.0),
801                        };
802
803                        let marker_bbox = emit_marker(
804                            segment,
805                            MarkerEndpoint::Start,
806                            MarkerType::Middle,
807                            angle,
808                            emit_fn,
809                        )?;
810                        bbox.insert(&marker_bbox);
811                    }
812                }
813            }
814        }
815    }
816
817    // Finally, render the last point
818    if !segments.is_empty() {
819        let segment = &segments[segments.len() - 1];
820        if let Segment::LineOrCurve { .. } = *segment {
821            let incoming = segments
822                .find_incoming_angle_backwards(segments.len() - 1)
823                .unwrap_or_else(|| Angle::new(0.0));
824
825            let angle = {
826                if let PathCommand::ClosePath = path.iter().nth(segments.len()).unwrap() {
827                    let outgoing = segments
828                        .find_outgoing_angle_forwards(0)
829                        .unwrap_or_else(|| Angle::new(0.0));
830                    incoming.bisect(outgoing)
831                } else {
832                    incoming
833                }
834            };
835
836            let marker_bbox = emit_marker(
837                segment,
838                MarkerEndpoint::End,
839                MarkerType::End,
840                angle,
841                emit_fn,
842            )?;
843            bbox.insert(&marker_bbox);
844        }
845    }
846
847    Ok(bbox)
848}
849
850#[cfg(test)]
851mod parser_tests {
852    use super::*;
853
854    #[test]
855    fn parsing_invalid_marker_units_yields_error() {
856        assert!(MarkerUnits::parse_str("").is_err());
857        assert!(MarkerUnits::parse_str("foo").is_err());
858    }
859
860    #[test]
861    fn parses_marker_units() {
862        assert_eq!(
863            MarkerUnits::parse_str("userSpaceOnUse").unwrap(),
864            MarkerUnits::UserSpaceOnUse
865        );
866        assert_eq!(
867            MarkerUnits::parse_str("strokeWidth").unwrap(),
868            MarkerUnits::StrokeWidth
869        );
870    }
871
872    #[test]
873    fn parsing_invalid_marker_orient_yields_error() {
874        assert!(MarkerOrient::parse_str("").is_err());
875        assert!(MarkerOrient::parse_str("blah").is_err());
876        assert!(MarkerOrient::parse_str("45blah").is_err());
877    }
878
879    #[test]
880    fn parses_marker_orient() {
881        assert_eq!(MarkerOrient::parse_str("auto").unwrap(), MarkerOrient::Auto);
882        assert_eq!(
883            MarkerOrient::parse_str("auto-start-reverse").unwrap(),
884            MarkerOrient::AutoStartReverse
885        );
886
887        assert_eq!(
888            MarkerOrient::parse_str("0").unwrap(),
889            MarkerOrient::Angle(Angle::new(0.0))
890        );
891        assert_eq!(
892            MarkerOrient::parse_str("180").unwrap(),
893            MarkerOrient::Angle(Angle::from_degrees(180.0))
894        );
895        assert_eq!(
896            MarkerOrient::parse_str("180deg").unwrap(),
897            MarkerOrient::Angle(Angle::from_degrees(180.0))
898        );
899        assert_eq!(
900            MarkerOrient::parse_str("-400grad").unwrap(),
901            MarkerOrient::Angle(Angle::from_degrees(-360.0))
902        );
903        assert_eq!(
904            MarkerOrient::parse_str("1rad").unwrap(),
905            MarkerOrient::Angle(Angle::new(1.0))
906        );
907    }
908}
909
910#[cfg(test)]
911mod directionality_tests {
912    use super::*;
913    use crate::path_builder::PathBuilder;
914
915    // Single open path; the easy case
916    fn setup_open_path() -> Segments {
917        let mut builder = PathBuilder::default();
918
919        builder.move_to(10.0, 10.0);
920        builder.line_to(20.0, 10.0);
921        builder.line_to(20.0, 20.0);
922
923        Segments::from(&builder.into_path())
924    }
925
926    #[test]
927    fn path_to_segments_handles_open_path() {
928        let expected_segments: Segments = Segments(vec![
929            Segment::line(10.0, 10.0, 20.0, 10.0),
930            Segment::line(20.0, 10.0, 20.0, 20.0),
931        ]);
932
933        assert_eq!(setup_open_path(), expected_segments);
934    }
935
936    fn setup_multiple_open_subpaths() -> Segments {
937        let mut builder = PathBuilder::default();
938
939        builder.move_to(10.0, 10.0);
940        builder.line_to(20.0, 10.0);
941        builder.line_to(20.0, 20.0);
942
943        builder.move_to(30.0, 30.0);
944        builder.line_to(40.0, 30.0);
945        builder.curve_to(50.0, 35.0, 60.0, 60.0, 70.0, 70.0);
946        builder.line_to(80.0, 90.0);
947
948        Segments::from(&builder.into_path())
949    }
950
951    #[test]
952    fn path_to_segments_handles_multiple_open_subpaths() {
953        let expected_segments: Segments = Segments(vec![
954            Segment::line(10.0, 10.0, 20.0, 10.0),
955            Segment::line(20.0, 10.0, 20.0, 20.0),
956            Segment::line(30.0, 30.0, 40.0, 30.0),
957            Segment::curve(40.0, 30.0, 50.0, 35.0, 60.0, 60.0, 70.0, 70.0),
958            Segment::line(70.0, 70.0, 80.0, 90.0),
959        ]);
960
961        assert_eq!(setup_multiple_open_subpaths(), expected_segments);
962    }
963
964    // Closed subpath; must have a line segment back to the first point
965    fn setup_closed_subpath() -> Segments {
966        let mut builder = PathBuilder::default();
967
968        builder.move_to(10.0, 10.0);
969        builder.line_to(20.0, 10.0);
970        builder.line_to(20.0, 20.0);
971        builder.close_path();
972
973        Segments::from(&builder.into_path())
974    }
975
976    #[test]
977    fn path_to_segments_handles_closed_subpath() {
978        let expected_segments: Segments = Segments(vec![
979            Segment::line(10.0, 10.0, 20.0, 10.0),
980            Segment::line(20.0, 10.0, 20.0, 20.0),
981            Segment::line(20.0, 20.0, 10.0, 10.0),
982        ]);
983
984        assert_eq!(setup_closed_subpath(), expected_segments);
985    }
986
987    // Multiple closed subpaths; each must have a line segment back to their
988    // initial points, with no degenerate segments between subpaths.
989    fn setup_multiple_closed_subpaths() -> Segments {
990        let mut builder = PathBuilder::default();
991
992        builder.move_to(10.0, 10.0);
993        builder.line_to(20.0, 10.0);
994        builder.line_to(20.0, 20.0);
995        builder.close_path();
996
997        builder.move_to(30.0, 30.0);
998        builder.line_to(40.0, 30.0);
999        builder.curve_to(50.0, 35.0, 60.0, 60.0, 70.0, 70.0);
1000        builder.line_to(80.0, 90.0);
1001        builder.close_path();
1002
1003        Segments::from(&builder.into_path())
1004    }
1005
1006    #[test]
1007    fn path_to_segments_handles_multiple_closed_subpaths() {
1008        let expected_segments: Segments = Segments(vec![
1009            Segment::line(10.0, 10.0, 20.0, 10.0),
1010            Segment::line(20.0, 10.0, 20.0, 20.0),
1011            Segment::line(20.0, 20.0, 10.0, 10.0),
1012            Segment::line(30.0, 30.0, 40.0, 30.0),
1013            Segment::curve(40.0, 30.0, 50.0, 35.0, 60.0, 60.0, 70.0, 70.0),
1014            Segment::line(70.0, 70.0, 80.0, 90.0),
1015            Segment::line(80.0, 90.0, 30.0, 30.0),
1016        ]);
1017
1018        assert_eq!(setup_multiple_closed_subpaths(), expected_segments);
1019    }
1020
1021    // A lineto follows the first closed subpath, with no moveto to start the second subpath.
1022    // The lineto must start at the first point of the first subpath.
1023    fn setup_no_moveto_after_closepath() -> Segments {
1024        let mut builder = PathBuilder::default();
1025
1026        builder.move_to(10.0, 10.0);
1027        builder.line_to(20.0, 10.0);
1028        builder.line_to(20.0, 20.0);
1029        builder.close_path();
1030
1031        builder.line_to(40.0, 30.0);
1032
1033        Segments::from(&builder.into_path())
1034    }
1035
1036    #[test]
1037    fn path_to_segments_handles_no_moveto_after_closepath() {
1038        let expected_segments: Segments = Segments(vec![
1039            Segment::line(10.0, 10.0, 20.0, 10.0),
1040            Segment::line(20.0, 10.0, 20.0, 20.0),
1041            Segment::line(20.0, 20.0, 10.0, 10.0),
1042            Segment::line(10.0, 10.0, 40.0, 30.0),
1043        ]);
1044
1045        assert_eq!(setup_no_moveto_after_closepath(), expected_segments);
1046    }
1047
1048    // Sequence of moveto; should generate degenerate points.
1049    // This test is not enabled right now!  We create the
1050    // path fixtures with Cairo, and Cairo compresses
1051    // sequences of moveto into a single one.  So, we can't
1052    // really test this, as we don't get the fixture we want.
1053    //
1054    // Eventually we'll probably have to switch librsvg to
1055    // its own internal path representation which should
1056    // allow for unelided path commands, and which should
1057    // only build a cairo_path_t for the final rendering step.
1058    //
1059    // fn setup_sequence_of_moveto () -> Segments {
1060    // let mut builder = PathBuilder::default ();
1061    //
1062    // builder.move_to (10.0, 10.0);
1063    // builder.move_to (20.0, 20.0);
1064    // builder.move_to (30.0, 30.0);
1065    // builder.move_to (40.0, 40.0);
1066    //
1067    // Segments::from(&builder.into_path())
1068    // }
1069    //
1070    // #[test]
1071    // fn path_to_segments_handles_sequence_of_moveto () {
1072    // let expected_segments: Segments = Segments(vec! [
1073    // Segment::degenerate(10.0, 10.0),
1074    // Segment::degenerate(20.0, 20.0),
1075    // Segment::degenerate(30.0, 30.0),
1076    // Segment::degenerate(40.0, 40.0),
1077    // ]);
1078    //
1079    // assert_eq!(setup_sequence_of_moveto(), expected_segments);
1080    // }
1081
1082    #[test]
1083    fn degenerate_segment_has_no_directionality() {
1084        let s = Segment::degenerate(1.0, 2.0);
1085        assert!(s.get_directionalities().is_none());
1086    }
1087
1088    #[test]
1089    fn line_segment_has_directionality() {
1090        let s = Segment::line(1.0, 2.0, 3.0, 4.0);
1091        let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
1092        assert_eq!((2.0, 2.0), (v1x, v1y));
1093        assert_eq!((2.0, 2.0), (v2x, v2y));
1094    }
1095
1096    #[test]
1097    fn line_segment_with_coincident_ends_has_no_directionality() {
1098        let s = Segment::line(1.0, 2.0, 1.0, 2.0);
1099        assert!(s.get_directionalities().is_none());
1100    }
1101
1102    #[test]
1103    fn curve_has_directionality() {
1104        let s = Segment::curve(1.0, 2.0, 3.0, 5.0, 8.0, 13.0, 20.0, 33.0);
1105        let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
1106        assert_eq!((2.0, 3.0), (v1x, v1y));
1107        assert_eq!((12.0, 20.0), (v2x, v2y));
1108    }
1109
1110    #[test]
1111    fn curves_with_loops_and_coincident_ends_have_directionality() {
1112        let s = Segment::curve(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 1.0, 2.0);
1113        let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
1114        assert_eq!((2.0, 2.0), (v1x, v1y));
1115        assert_eq!((-4.0, -4.0), (v2x, v2y));
1116
1117        let s = Segment::curve(1.0, 2.0, 1.0, 2.0, 3.0, 4.0, 1.0, 2.0);
1118        let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
1119        assert_eq!((2.0, 2.0), (v1x, v1y));
1120        assert_eq!((-2.0, -2.0), (v2x, v2y));
1121
1122        let s = Segment::curve(1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 1.0, 2.0);
1123        let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
1124        assert_eq!((2.0, 2.0), (v1x, v1y));
1125        assert_eq!((-2.0, -2.0), (v2x, v2y));
1126    }
1127
1128    #[test]
1129    fn curve_with_coincident_control_points_has_no_directionality() {
1130        let s = Segment::curve(1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 1.0, 2.0);
1131        assert!(s.get_directionalities().is_none());
1132    }
1133
1134    #[test]
1135    fn curve_with_123_coincident_has_directionality() {
1136        let s = Segment::curve(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 40.0);
1137        let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
1138        assert_eq!((20.0, 40.0), (v1x, v1y));
1139        assert_eq!((20.0, 40.0), (v2x, v2y));
1140    }
1141
1142    #[test]
1143    fn curve_with_234_coincident_has_directionality() {
1144        let s = Segment::curve(20.0, 40.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1145        let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
1146        assert_eq!((-20.0, -40.0), (v1x, v1y));
1147        assert_eq!((-20.0, -40.0), (v2x, v2y));
1148    }
1149
1150    #[test]
1151    fn curve_with_12_34_coincident_has_directionality() {
1152        let s = Segment::curve(20.0, 40.0, 20.0, 40.0, 60.0, 70.0, 60.0, 70.0);
1153        let (v1x, v1y, v2x, v2y) = s.get_directionalities().unwrap();
1154        assert_eq!((40.0, 30.0), (v1x, v1y));
1155        assert_eq!((40.0, 30.0), (v2x, v2y));
1156    }
1157}
1158
1159#[cfg(test)]
1160mod marker_tests {
1161    use super::*;
1162    use crate::path_builder::PathBuilder;
1163
1164    #[test]
1165    fn emits_for_open_subpath() {
1166        let mut builder = PathBuilder::default();
1167        builder.move_to(0.0, 0.0);
1168        builder.line_to(1.0, 0.0);
1169        builder.line_to(1.0, 1.0);
1170        builder.line_to(0.0, 1.0);
1171
1172        let mut v = Vec::new();
1173
1174        assert!(
1175            emit_markers_for_path(
1176                &builder.into_path(),
1177                Box::new(BoundingBox::new()),
1178                &mut |marker_type: MarkerType,
1179                      x: f64,
1180                      y: f64,
1181                      computed_angle: Angle|
1182                 -> DrawResult {
1183                    v.push((marker_type, x, y, computed_angle));
1184                    Ok(Box::new(BoundingBox::new()))
1185                }
1186            )
1187            .is_ok()
1188        );
1189
1190        assert_eq!(
1191            v,
1192            vec![
1193                (MarkerType::Start, 0.0, 0.0, Angle::new(0.0)),
1194                (MarkerType::Middle, 1.0, 0.0, Angle::from_vector(1.0, 1.0)),
1195                (MarkerType::Middle, 1.0, 1.0, Angle::from_vector(-1.0, 1.0)),
1196                (MarkerType::End, 0.0, 1.0, Angle::from_vector(-1.0, 0.0)),
1197            ]
1198        );
1199    }
1200
1201    #[test]
1202    fn emits_for_closed_subpath() {
1203        let mut builder = PathBuilder::default();
1204        builder.move_to(0.0, 0.0);
1205        builder.line_to(1.0, 0.0);
1206        builder.line_to(1.0, 1.0);
1207        builder.line_to(0.0, 1.0);
1208        builder.close_path();
1209
1210        let mut v = Vec::new();
1211
1212        assert!(
1213            emit_markers_for_path(
1214                &builder.into_path(),
1215                Box::new(BoundingBox::new()),
1216                &mut |marker_type: MarkerType,
1217                      x: f64,
1218                      y: f64,
1219                      computed_angle: Angle|
1220                 -> DrawResult {
1221                    v.push((marker_type, x, y, computed_angle));
1222                    Ok(Box::new(BoundingBox::new()))
1223                }
1224            )
1225            .is_ok()
1226        );
1227
1228        assert_eq!(
1229            v,
1230            vec![
1231                (MarkerType::Start, 0.0, 0.0, Angle::new(0.0)),
1232                (MarkerType::Middle, 1.0, 0.0, Angle::from_vector(1.0, 1.0)),
1233                (MarkerType::Middle, 1.0, 1.0, Angle::from_vector(-1.0, 1.0)),
1234                (MarkerType::Middle, 0.0, 1.0, Angle::from_vector(-1.0, -1.0)),
1235                (MarkerType::End, 0.0, 0.0, Angle::from_vector(1.0, -1.0)),
1236            ]
1237        );
1238    }
1239}