@@ -13,11 +13,27 @@ use alloc::vec::Vec;
1313use bytemuck:: { Pod , Zeroable } ;
1414use fearless_simd:: * ;
1515
16+ /// The element of a path made of lines.
17+ ///
18+ /// Each subpath must start with a `MoveTo`. Closing of subpaths is not supported, and subpaths are
19+ /// not closed implicitly when a new subpath (with `MoveTo`) is started. It is expected that closed
20+ /// subpaths are watertight in the sense that the last `LineTo` matches exactly with the first
21+ /// `MoveTo`.
22+ ///
23+ /// This intentionally allows for non-watertight subpaths, as, e.g., lines that are fully outside
24+ /// of the viewport do not need to be drawn.
25+ ///
26+ /// See [`PathEl`] for a more general-purpose path element type.
27+ pub ( crate ) enum LinePathEl {
28+ MoveTo ( Point ) ,
29+ LineTo ( Point ) ,
30+ }
31+
1632// Unlike kurbo, which takes a closure with a callback for outputting the lines, we use a trait
1733// instead. The reason is that this way the callback can be inlined, which is not possible with
1834// a closure and turned out to have a noticeable overhead.
1935pub ( crate ) trait Callback {
20- fn callback ( & mut self , el : PathEl ) ;
36+ fn callback ( & mut self , el : LinePathEl ) ;
2137}
2238
2339/// See the docs for the kurbo implementation of flattening:
@@ -35,97 +51,111 @@ pub(crate) fn flatten<S: Simd>(
3551 flatten_ctx. flattened_cubics . clear ( ) ;
3652
3753 let sqrt_tol = tolerance. sqrt ( ) ;
38- let mut last_pt = None ;
54+ let mut closed = true ;
55+ let mut start_pt = Point :: ZERO ;
56+ let mut last_pt = Point :: ZERO ;
3957
4058 for el in path {
4159 match el {
4260 PathEl :: MoveTo ( p) => {
43- last_pt = Some ( p) ;
44- callback. callback ( PathEl :: MoveTo ( p) ) ;
61+ if !closed && last_pt != start_pt {
62+ callback. callback ( LinePathEl :: LineTo ( start_pt) ) ;
63+ }
64+ closed = false ;
65+ last_pt = p;
66+ start_pt = p;
67+ callback. callback ( LinePathEl :: MoveTo ( p) ) ;
4568 }
4669 PathEl :: LineTo ( p) => {
47- last_pt = Some ( p) ;
48- callback. callback ( PathEl :: LineTo ( p) ) ;
70+ debug_assert ! ( !closed, "Expected a `MoveTo` before a `LineTo`" ) ;
71+ last_pt = p;
72+ callback. callback ( LinePathEl :: LineTo ( p) ) ;
4973 }
5074 PathEl :: QuadTo ( p1, p2) => {
51- if let Some ( p0) = last_pt {
52- // An upper bound on the shortest distance of any point on the quadratic Bezier
53- // curve to the line segment [p0, p2] is 1/2 of the maximum of the
54- // endpoint-to-control-point distances.
55- //
56- // The derivation is similar to that for the cubic Bezier (see below). In
57- // short:
58- //
59- // q(t) = B0(t) p0 + B1(t) p1 + B2(t) p2
60- // dist(q(t), [p0, p1]) <= B1(t) dist(p1, [p0, p1])
61- // = 2 (1-t)t dist(p1, [p0, p1]).
62- //
63- // The maximum occurs at t=1/2, hence
64- // max(dist(q(t), [p0, p1] <= 1/2 dist(p1, [p0, p1])).
65- //
66- // A cheap upper bound for dist(p1, [p0, p1]) is max(dist(p1, p0), dist(p1, p2)).
67- //
68- // The following takes the square to elide the square root of the Euclidean
69- // distance.
70- if f64:: max ( ( p1 - p0) . hypot2 ( ) , ( p1 - p2) . hypot2 ( ) ) <= 4. * TOL_2 {
71- callback. callback ( PathEl :: LineTo ( p2) ) ;
72- } else {
73- let q = QuadBez :: new ( p0, p1, p2) ;
74- let params = q. estimate_subdiv ( sqrt_tol) ;
75- let n = ( ( 0.5 * params. val / sqrt_tol) . ceil ( ) as usize ) . max ( 1 ) ;
76- let step = 1.0 / ( n as f64 ) ;
77- for i in 1 ..n {
78- let u = ( i as f64 ) * step;
79- let t = q. determine_subdiv_t ( & params, u) ;
80- let p = q. eval ( t) ;
81- callback. callback ( PathEl :: LineTo ( p) ) ;
82- }
83- callback. callback ( PathEl :: LineTo ( p2) ) ;
75+ debug_assert ! ( !closed, "Expected a `MoveTo` before a `QuadTo`" ) ;
76+ let p0 = last_pt;
77+ // An upper bound on the shortest distance of any point on the quadratic Bezier
78+ // curve to the line segment [p0, p2] is 1/2 of the maximum of the
79+ // endpoint-to-control-point distances.
80+ //
81+ // The derivation is similar to that for the cubic Bezier (see below). In
82+ // short:
83+ //
84+ // q}(t) = B0(t) p0 + B1(t) p1 + B2(t) p2
85+ // dist(q(t), [p0, p1]) <= B1(t) dist(p1, [p0, p1])
86+ // = 2 (1-t)t dist(p1, [p0, p1]).
87+ //
88+ // The maximum occurs at t=1/2, hence
89+ // max(dist(q(t), [p0, p1] <= 1/2 dist(p1, [p0, p1])).
90+ //
91+ // A cheap upper bound for dist(p1, [p0, p1]) is max(dist(p1, p0), dist(p1, p2)).
92+ //
93+ // The following takes the square to elide the square root of the Euclidean
94+ // distance.
95+ if f64:: max ( ( p1 - p0) . hypot2 ( ) , ( p1 - p2) . hypot2 ( ) ) <= 4. * TOL_2 {
96+ callback. callback ( LinePathEl :: LineTo ( p2) ) ;
97+ } else {
98+ let q = QuadBez :: new ( p0, p1, p2) ;
99+ let params = q. estimate_subdiv ( sqrt_tol) ;
100+ let n = ( ( 0.5 * params. val / sqrt_tol) . ceil ( ) as usize ) . max ( 1 ) ;
101+ let step = 1.0 / ( n as f64 ) ;
102+ for i in 1 ..n {
103+ let u = ( i as f64 ) * step;
104+ let t = q. determine_subdiv_t ( & params, u) ;
105+ let p = q. eval ( t) ;
106+ callback. callback ( LinePathEl :: LineTo ( p) ) ;
84107 }
108+ callback. callback ( LinePathEl :: LineTo ( p2) ) ;
85109 }
86- last_pt = Some ( p2 ) ;
110+ last_pt = p2 ;
87111 }
88112 PathEl :: CurveTo ( p1, p2, p3) => {
89- if let Some ( p0 ) = last_pt {
90- // An upper bound on the shortest distance of any point on the cubic Bezier
91- // curve to the line segment [p0, p3] is 3/4 of the maximum of the
92- // endpoint-to-control-point distances.
93- //
94- // With Bernstein weights Bi(t), we have
95- // c(t) = B0(t) p0 + B1 (t) p1 + B2(t) p2 + B3(t) p3
96- // with t from 0 to 1 (inclusive).
97- //
98- // Through convexivity of the Euclidean distance function and the line segment,
99- // we have
100- // dist(c(t), [p0, p3]) <= B1(t) dist(p1, [p0, p3]) + B2(t) dist(p2, [p0, p3])
101- // <= B1(t) ||p1-p0|| + B2(t) ||p2-p3||
102- // <= ( B1(t) + B2(t)) max( ||p1-p0||, ||p2-p3|||)
103- // = 3 ((1-t)t^2 + (1-t)^2t ) max(||p1-p0||, ||p2-p3||).
104- //
105- // The inner polynomial has its maximum of 1/4 at t=1/2, hence
106- // max(dist(c(t), [p0, p3])) <= 3 /4 max(||p1-p0||, ||p2-p3||).
107- //
108- // The following takes the square to elide the square root of the Euclidean
109- // distance.
110- if f64 :: max ( ( p0 - p1 ) . hypot2 ( ) , ( p3 - p2 ) . hypot2 ( ) ) <= 16. / 9. * TOL_2 {
111- callback . callback ( PathEl :: LineTo ( p3 ) ) ;
112- } else {
113- let c = CubicBez :: new ( p0 , p1 , p2 , p3 ) ;
114- let max = flatten_cubic_simd ( simd , c , flatten_ctx , tolerance as f32 ) ;
115-
116- for p in & flatten_ctx . flattened_cubics [ 1 ..max ] {
117- callback . callback ( PathEl :: LineTo ( Point :: new ( p . x as f64 , p . y as f64 ) ) ) ;
118- }
113+ debug_assert ! ( !closed , "Expected a `MoveTo` before a `CurveTo`" ) ;
114+ let p0 = last_pt ;
115+ // An upper bound on the shortest distance of any point on the cubic Bezier
116+ // curve to the line segment [p0, p3] is 3/4 of the maximum of the
117+ // endpoint-to-control-point distances.
118+ //
119+ // With Bernstein weights Bi (t), we have
120+ // c(t) = B0(t) p0 + B1(t) p1 + B2(t) p2 + B3(t) p3
121+ // with t from 0 to 1 (inclusive).
122+ //
123+ // Through convexivity of the Euclidean distance function and the line segment,
124+ // we have
125+ // dist(c(t), [p0, p3]) <= B1(t) dist(p1, [p0, p3]) + B2(t) dist(p2, [p0, p3])
126+ // <= B1(t) ||p1-p0|| + B2(t) ||p2-p3||
127+ // <= (B1(t) + B2(t) ) max(||p1-p0||, ||p2-p3|||)
128+ // = 3 ((1-t)t^2 + (1-t)^2t) max(||p1-p0||, ||p2-p3||).
129+ //
130+ // The inner polynomial has its maximum of 1 /4 at t=1/2, hence
131+ // max(dist(c(t), [p0, p3])) <= 3/4 max(||p1-p0||, ||p2-p3||).
132+ //
133+ // The following takes the square to elide the square root of the Euclidean
134+ // distance.
135+ if f64 :: max ( ( p0 - p1 ) . hypot2 ( ) , ( p3 - p2 ) . hypot2 ( ) ) <= 16. / 9. * TOL_2 {
136+ callback . callback ( LinePathEl :: LineTo ( p3 ) ) ;
137+ } else {
138+ let c = CubicBez :: new ( p0 , p1 , p2 , p3 ) ;
139+ let max = flatten_cubic_simd ( simd , c , flatten_ctx , tolerance as f32 ) ;
140+
141+ for p in & flatten_ctx . flattened_cubics [ 1 ..max ] {
142+ callback . callback ( LinePathEl :: LineTo ( Point :: new ( p . x as f64 , p . y as f64 ) ) ) ;
119143 }
120144 }
121- last_pt = Some ( p3 ) ;
145+ last_pt = p3 ;
122146 }
123147 PathEl :: ClosePath => {
124- last_pt = None ;
125- callback. callback ( PathEl :: ClosePath ) ;
148+ closed = true ;
149+ if last_pt != start_pt {
150+ callback. callback ( LinePathEl :: LineTo ( start_pt) ) ;
151+ }
126152 }
127153 }
128154 }
155+
156+ if !closed && last_pt != start_pt {
157+ callback. callback ( LinePathEl :: LineTo ( start_pt) ) ;
158+ }
129159}
130160
131161// The below methods are copied from kurbo and needed to implement flattening of normal quad curves.
0 commit comments