Skip to main content

gfx/
stroke.rs

1// stroke -- stroke outlines of a path                                     [src/gfx/stroke.s]
2//
3//   path.stroke(ras, dc, sf, width, style)   width 24.8 fixed pixels; style = join | cap << 4
4//                                           (JOIN_MITER (limit 4, else bevel) / ROUND / BEVEL,
5//                                           CAP_BUTT / ROUND / SQUARE)
6//   path.stroke_lines(ras, width, style)     only the outline line list (for other targets)
7//
8// Every flattened subpath becomes closed contours (outer side forward, inner side
9// backward, with joins and caps) that are filled with the non-zero rule.  Inner joins
10// pivot through the vertex; the non-zero fill absorbs the overlap.  Offsets use
11// n = (dy, -dx) * half_width, so with y pointing down a positive cross product din x dout
12// means the +n side is the outer side of the turn.  The float operations follow the
13// assembly one by one (0 - x instead of -x, comparisons with its NaN behaviour).
14
15use alloc::vec::Vec;
16
17use super::path::{zf, Path};
18use super::raster::Raster;
19use super::{Dc, Surface, CAP_BUTT, CAP_ROUND, CAP_SQUARE, JOIN_MITER, JOIN_ROUND};
20use rt::fx::{maxss, minss, sqrt};
21
22const EPS: f32 = 0.0001;
23
24struct St<'a> {
25    hw: f32,
26    p: (f32, f32),
27    di: (f32, f32),
28    dout: (f32, f32),
29    cs: f32,
30    sn: f32,
31    join: u32,
32    cap: u32,
33    pts: &'a [(f32, f32)],
34    base: usize,
35    n: usize,
36    v: (f32, f32),
37    t: (f32, f32),
38    spts: Vec<(f32, f32)>,
39}
40
41impl St<'_> {
42    fn pt(&self, i: usize) -> (f32, f32) {
43        self.pts[self.base + i]
44    }
45
46    // unit vector from point i to point j ((1, 0) when degenerate)
47    fn dir(&self, i: usize, j: usize) -> (f32, f32) {
48        let a = self.pt(i);
49        let b = self.pt(j);
50        let dx = b.0 - a.0;
51        let dy = b.1 - a.1;
52        let len = sqrt(dx * dx + dy * dy);
53        if zf(len, 0.0) {
54            return (1.0, 0.0);
55        }
56        (dx / len, dy / len)
57    }
58
59    // sp_add: append a contour point (exact duplicates skipped)
60    fn add(&mut self, x: f32, y: f32) {
61        if let Some(&l) = self.spts.last() {
62            if zf(x, l.0) && zf(y, l.1) {
63                return;
64            }
65        }
66        self.spts.push((x, y));
67    }
68
69    // P + sign * n(d) * hw, n = (dy, -dx)
70    fn emit_off(&mut self, sign: f32, d: (f32, f32)) {
71        let x = d.1 * self.hw * sign + self.p.0;
72        let y = (0.0 - d.0) * self.hw * sign + self.p.1;
73        self.add(x, y);
74    }
75
76    // join at P between di (in) and dout (out) on the +n side
77    fn join(&mut self) {
78        let cross = self.di.0 * self.dout.1 - self.di.1 * self.dout.0;
79        let dot = self.di.0 * self.dout.0 + self.di.1 * self.dout.1;
80        if !(cross.abs() >= EPS) && dot >= 0.0 {
81            let d = self.dout;
82            self.emit_off(1.0, d); // nearly straight: one point
83            return;
84        }
85        if !(cross >= 0.0) {
86            // inner side: pivot through the vertex
87            let di = self.di;
88            self.emit_off(1.0, di);
89            let p = self.p;
90            self.add(p.0, p.1);
91            let d = self.dout;
92            self.emit_off(1.0, d);
93            return;
94        }
95        let di = self.di;
96        self.emit_off(1.0, di);
97        if self.join == JOIN_ROUND {
98            self.v = (self.di.1, 0.0 - self.di.0);
99            self.t = (self.dout.1, 0.0 - self.dout.0);
100            self.arc();
101        } else if self.join == JOIN_MITER {
102            let m = dot + 1.0;
103            if m >= 0.125 {
104                // P + (nin + nout) * hw / (1 + dot)
105                let nx = self.di.1 + self.dout.1;
106                let ny = 0.0 - (self.di.0 + self.dout.0);
107                let k = self.hw / m;
108                let p = self.p;
109                self.add(nx * k + p.0, ny * k + p.1);
110            }
111        }
112        let d = self.dout;
113        self.emit_off(1.0, d);
114    }
115
116    // P + v * hw while rotating v towards t (t itself is not emitted)
117    fn arc(&mut self) {
118        for _ in 0..64 {
119            let nx = self.v.0 * self.cs - self.v.1 * self.sn;
120            let ny = self.v.0 * self.sn + self.v.1 * self.cs;
121            if !(nx * self.t.1 - ny * self.t.0 >= EPS) {
122                return;
123            }
124            self.v = (nx, ny);
125            let p = self.p;
126            self.add(nx * self.hw + p.0, ny * self.hw + p.1);
127        }
128    }
129
130    // cap at P for travel direction d: from +n via d to -n (the +n point is already there)
131    fn cap(&mut self, d: (f32, f32)) {
132        let (dx, dy) = d;
133        if self.cap == CAP_SQUARE {
134            let p = self.p;
135            self.add((dy + dx) * self.hw + p.0, (dy - dx) * self.hw + p.1);
136            self.add((dx - dy) * self.hw + p.0, (dy + dx) * self.hw + p.1);
137        } else if self.cap == CAP_ROUND {
138            self.v = (dy, 0.0 - dx);
139            self.t = (dx, dy);
140            self.arc();
141            let p = self.p;
142            self.add(dx * self.hw + p.0, dy * self.hw + p.1);
143            self.v = (dx, dy);
144            self.t = (0.0 - dy, dx);
145            self.arc();
146        }
147        self.emit_off(-1.0, d);
148    }
149
150    // sp_flush: the contour as closed lines
151    fn flush(&mut self, ras: &mut Raster) {
152        let n = self.spts.len();
153        if n >= 2 {
154            for i in 0..n {
155                let a = self.spts[i];
156                let b = self.spts[if i + 1 < n { i + 1 } else { 0 }];
157                ras.emit_line(a.0, a.1, b.0, b.1);
158            }
159        }
160        self.spts.clear();
161    }
162
163    // outline of an open polyline
164    fn open(&mut self, ras: &mut Raster) {
165        self.spts.clear();
166        let last = self.n - 1;
167        let d0 = self.dir(0, 1);
168        self.p = self.pt(0);
169        self.emit_off(1.0, d0);
170        for i in 1..last {
171            self.di = self.dir(i - 1, i);
172            self.dout = self.dir(i, i + 1);
173            self.p = self.pt(i);
174            self.join();
175        }
176        let dl = self.dir(last - 1, last);
177        self.p = self.pt(last);
178        self.emit_off(1.0, dl);
179        self.cap(dl);
180        let mut i = last as isize - 1;
181        while i >= 1 {
182            let k = i as usize;
183            self.di = self.dir(k + 1, k);
184            self.dout = self.dir(k, k - 1);
185            self.p = self.pt(k);
186            self.join();
187            i -= 1;
188        }
189        self.p = self.pt(0);
190        let back = (0.0 - d0.0, 0.0 - d0.1);
191        self.emit_off(1.0, back);
192        self.cap(back);
193        self.flush(ras);
194    }
195
196    // two contours of a closed polyline
197    fn closed(&mut self, ras: &mut Raster) {
198        let n = self.n;
199        if n < 2 {
200            return;
201        }
202        self.spts.clear();
203        for i in 0..n {
204            self.di = self.dir((i + n - 1) % n, i);
205            self.dout = self.dir(i, (i + 1) % n);
206            self.p = self.pt(i);
207            self.join();
208        }
209        self.flush(ras);
210        self.spts.clear();
211        for i in (0..n).rev() {
212            self.di = self.dir((i + 1) % n, i);
213            self.dout = self.dir(i, (i + n - 1) % n);
214            self.p = self.pt(i);
215            self.join();
216        }
217        self.flush(ras);
218    }
219
220    // a single point: round cap = disc, square cap = square, butt = nothing
221    fn dot(&mut self, ras: &mut Raster) {
222        self.p = self.pt(0);
223        if self.cap == CAP_BUTT {
224            return;
225        }
226        self.spts.clear();
227        self.emit_off(1.0, (1.0, 0.0));
228        self.cap((1.0, 0.0));
229        self.cap((-1.0, 0.0));
230        self.flush(ras);
231    }
232}
233
234impl Path {
235    /// the outline lines of path_stroke (ras.lines_begin + the contours); the caller fills
236    /// them with the non-zero rule
237    pub fn stroke_lines(&self, ras: &mut Raster, width: i32, style: u32) {
238        let hw = maxss(width as f32 * 0.001953125, 0.25); // half width, at least 0.25 px
239        // arc step s = min(0.5, 2 * sqrt(0.4 / hw)); cos / sin by Taylor series
240        let mut s = sqrt(0.4 / hw);
241        s = s + s;
242        let s = minss(s, 0.5);
243        let s2 = s * s;
244        let cs = (s2 * 0.041666668 - 0.5) * s2 + 1.0;
245        let sn = ((s2 * 0.008333334 - 0.16666667) * s2 + 1.0) * s;
246        let mut st = St {
247            hw,
248            p: (0.0, 0.0),
249            di: (0.0, 0.0),
250            dout: (0.0, 0.0),
251            cs,
252            sn,
253            join: style & 15,
254            cap: (style >> 4) & 15,
255            pts: &self.pts,
256            base: 0,
257            n: 0,
258            v: (0.0, 0.0),
259            t: (0.0, 0.0),
260            spts: Vec::new(),
261        };
262        ras.lines_begin();
263        for sub in &self.subs {
264            let mut n = sub.count;
265            if n == 0 {
266                continue;
267            }
268            st.base = sub.start;
269            // a closed subpath whose last point repeats the first: drop it
270            if n >= 2 && sub.closed {
271                let a = self.pts[sub.start];
272                let b = self.pts[sub.start + n - 1];
273                if zf(a.0, b.0) && zf(a.1, b.1) {
274                    n -= 1;
275                }
276            }
277            st.n = n;
278            if n == 1 {
279                st.dot(ras);
280            } else if sub.closed {
281                st.closed(ras);
282            } else {
283                st.open(ras);
284            }
285        }
286    }
287
288    /// path_stroke: the outline filled (non-zero) with the current colour
289    pub fn stroke(&self, ras: &mut Raster, dc: &Dc, sf: &mut Surface, width: i32, style: u32) {
290        self.stroke_lines(ras, width, style);
291        ras.fill(dc, sf, 0);
292    }
293}