Skip to main content

typf_render_opixa/
edge.rs

1//! The edge detectives: line segments that build glyph foundations
2//!
3//! Every glyph starts as a collection of edges—straight lines that trace
4//! the character's outline. These edges become the roadmap for our rasterizer,
5//! telling it exactly which pixels should be filled. Think of them as the
6//! scaffolding that holds up beautiful text.
7
8use crate::fixed::F26Dot6;
9
10/// One edge, infinite possibilities
11///
12/// An edge is more than just a line—it's a promise. It promises that for every
13/// scanline it crosses, it knows exactly where to be. We store both its current
14/// position and its slope, making rasterization a simple march down the screen.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct Edge {
17    /// Where we are right now (X position at current scanline)
18    pub x: F26Dot6,
19
20    /// How we move each step down (slope in X per scanline)
21    pub x_increment: F26Dot6,
22
23    /// Which way we're going (upward fills vs downward carves)
24    pub direction: i8,
25
26    /// The last scanline where we matter (inclusive)
27    pub y_max: i32,
28
29    /// The first scanline where we appear (inclusive)
30    pub y_min: i32,
31}
32
33impl Edge {
34    /// Birth of an edge: two points become a rasterization warrior
35    ///
36    /// We transform any two points into a scan-ready edge. Horizontal edges get
37    /// discarded (they don't cross scanlines), and everything else gets sorted
38    /// so we always scan from top to bottom. The result is an edge that knows
39    /// its purpose from the moment it's created.
40    ///
41    /// # The Point Partnership
42    ///
43    /// * `x1, y1` - Where the edge begins its journey
44    /// * `x2, y2` - Where the edge completes its mission
45    ///
46    /// # Returns
47    ///
48    /// A battle-ready edge, or `None` if the points refuse to cooperate
49    pub fn new(x1: F26Dot6, y1: F26Dot6, x2: F26Dot6, y2: F26Dot6) -> Option<Self> {
50        let dy = y2 - y1;
51
52        if dy == F26Dot6::ZERO {
53            return None;
54        }
55
56        // Sort by Y so we always scan down
57        let (x_start, y_start, x_end, y_end, direction) = if dy > F26Dot6::ZERO {
58            (x1, y1, x2, y2, 1i8)
59        } else {
60            (x2, y2, x1, y1, -1i8)
61        };
62
63        let dy_abs = y_end - y_start;
64        let dx = x_end - x_start;
65
66        // Calculate slope = dx / dy
67        let x_increment = dx.div(dy_abs);
68
69        // Calculate scanline range
70        // We use ceil() for start to ensure we are inside the edge
71        // We use ceil() - 1 for end to exclude the end point (half-open interval)
72        let y_min = y_start.ceil().to_int();
73        let y_max = y_end.ceil().to_int() - 1;
74
75        if y_min > y_max {
76            return None;
77        }
78
79        // Calculate X at the first scanline
80        // x = x_start + (y_min - y_start) * slope
81        let delta_y = F26Dot6::from_int(y_min) - y_start;
82        let x = x_start + delta_y.mul(x_increment);
83
84        Some(Edge {
85            x,
86            x_increment,
87            direction,
88            y_max,
89            y_min,
90        })
91    }
92
93    /// One step closer to completion
94    ///
95    /// Each scanline brings us one step down the edge. We simply add our slope
96    /// to our current position—no complex math, just elegant progression.
97    #[inline]
98    pub fn step(&mut self) {
99        self.x = self.x + self.x_increment;
100    }
101
102    /// Are we still relevant at this height?
103    ///
104    /// An edge knows when it's time to retire. Once we've passed our maximum
105    /// Y coordinate, we're done contributing to the glyph.
106    #[inline]
107    pub fn is_active(&self, y: i32) -> bool {
108        y <= self.y_max
109    }
110}
111
112/// The edge orchestra: many lines working in harmony
113///
114/// Managing one edge is simple, but glyphs have hundreds. This collection
115/// keeps them sorted by X position, making scanline traversal efficient.
116/// Whether we're building the global edge table or managing active edges,
117/// this structure ensures every edge finds its place.
118#[derive(Debug, Clone, Default)]
119pub struct EdgeList {
120    edges: Vec<Edge>,
121}
122
123impl EdgeList {
124    /// Start fresh: a clean slate for new edges
125    pub fn new() -> Self {
126        Self { edges: Vec::new() }
127    }
128
129    /// Room for everyone: pre-allocate space for efficiency
130    pub fn with_capacity(capacity: usize) -> Self {
131        Self {
132            edges: Vec::with_capacity(capacity),
133        }
134    }
135
136    /// Find the perfect spot: insert while maintaining order
137    ///
138    /// We use binary search to locate exactly where each edge belongs.
139    /// No messy linear searches—just surgical precision.
140    pub fn insert_sorted(&mut self, edge: Edge) {
141        let pos = self
142            .edges
143            .binary_search_by(|e| e.x.cmp(&edge.x))
144            .unwrap_or_else(|e| e);
145        self.edges.insert(pos, edge);
146    }
147
148    /// Restore order: sort everyone by their X position
149    pub fn sort_by_x(&mut self) {
150        self.edges.sort_by(|a, b| a.x.cmp(&b.x));
151    }
152
153    /// Spring cleaning: remove edges that have finished their journey
154    pub fn remove_inactive(&mut self, y: i32) {
155        self.edges.retain(|edge| edge.is_active(y));
156    }
157
158    /// March together: advance every edge one scanline down
159    pub fn step_all(&mut self) {
160        for edge in &mut self.edges {
161            edge.step();
162        }
163    }
164
165    /// Clear all edges.
166    pub fn clear(&mut self) {
167        self.edges.clear();
168    }
169
170    /// Get number of edges.
171    #[inline]
172    pub fn len(&self) -> usize {
173        self.edges.len()
174    }
175
176    /// Check if empty.
177    #[inline]
178    pub fn is_empty(&self) -> bool {
179        self.edges.is_empty()
180    }
181
182    /// Get edges as slice.
183    #[inline]
184    pub fn as_slice(&self) -> &[Edge] {
185        &self.edges
186    }
187
188    /// Get edges as mutable slice.
189    #[inline]
190    pub fn as_mut_slice(&mut self) -> &mut [Edge] {
191        &mut self.edges
192    }
193
194    /// Add an edge without sorting (append).
195    pub fn push(&mut self, edge: Edge) {
196        self.edges.push(edge);
197    }
198
199    /// Extend with edges from another list.
200    pub fn extend(&mut self, other: &EdgeList) {
201        self.edges.extend_from_slice(&other.edges);
202    }
203
204    /// Get iterator over edges.
205    pub fn iter(&self) -> std::slice::Iter<'_, Edge> {
206        self.edges.iter()
207    }
208
209    /// Get mutable iterator over edges.
210    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Edge> {
211        self.edges.iter_mut()
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_edge_new_vertical() {
221        let e = Edge::new(
222            F26Dot6::from_int(10),
223            F26Dot6::from_int(5),
224            F26Dot6::from_int(10),
225            F26Dot6::from_int(15),
226        )
227        .unwrap();
228
229        assert_eq!(e.x, F26Dot6::from_int(10));
230        assert_eq!(e.x_increment, F26Dot6::ZERO);
231        assert_eq!(e.direction, 1);
232        assert_eq!(e.y_min, 5);
233        assert_eq!(e.y_max, 14); // 15 is excluded
234    }
235
236    #[test]
237    fn test_edge_new_horizontal_returns_none() {
238        let e = Edge::new(
239            F26Dot6::from_int(10),
240            F26Dot6::from_int(5),
241            F26Dot6::from_int(20),
242            F26Dot6::from_int(5),
243        );
244
245        assert!(e.is_none());
246    }
247
248    #[test]
249    fn test_edge_new_diagonal() {
250        let e = Edge::new(
251            F26Dot6::from_int(0),
252            F26Dot6::from_int(0),
253            F26Dot6::from_int(10),
254            F26Dot6::from_int(10),
255        )
256        .unwrap();
257
258        assert_eq!(e.x, F26Dot6::from_int(0));
259        assert_eq!(e.direction, 1);
260        assert_eq!(e.y_min, 0);
261        assert_eq!(e.y_max, 9); // 10 is excluded
262                                // Slope = 10/10 = 1.0 in 26.6 = 64
263        assert_eq!(e.x_increment.raw(), 64);
264    }
265
266    #[test]
267    fn test_edge_new_swaps_if_downward() {
268        let e = Edge::new(
269            F26Dot6::from_int(20),
270            F26Dot6::from_int(15),
271            F26Dot6::from_int(10),
272            F26Dot6::from_int(5),
273        )
274        .unwrap();
275
276        // Should be swapped so y1 < y2
277        assert_eq!(e.x, F26Dot6::from_int(10));
278        assert_eq!(e.y_min, 5);
279        assert_eq!(e.y_max, 14); // 15 is excluded
280        assert_eq!(e.direction, -1);
281    }
282
283    #[test]
284    fn test_edge_step() {
285        let mut e = Edge::new(
286            F26Dot6::from_int(0),
287            F26Dot6::from_int(0),
288            F26Dot6::from_int(10),
289            F26Dot6::from_int(10),
290        )
291        .unwrap();
292
293        assert_eq!(e.x.to_int(), 0);
294        e.step();
295        assert_eq!(e.x.to_int(), 1);
296        e.step();
297        assert_eq!(e.x.to_int(), 2);
298    }
299
300    #[test]
301    fn test_edge_is_active() {
302        let e = Edge::new(
303            F26Dot6::from_int(0),
304            F26Dot6::from_int(5),
305            F26Dot6::from_int(10),
306            F26Dot6::from_int(15),
307        )
308        .unwrap();
309
310        assert!(e.is_active(5));
311        assert!(e.is_active(10));
312        assert!(e.is_active(14));
313        assert!(!e.is_active(15));
314    }
315
316    #[test]
317    fn test_edgelist_new() {
318        let list = EdgeList::new();
319        assert!(list.is_empty());
320        assert_eq!(list.len(), 0);
321    }
322
323    #[test]
324    fn test_edgelist_push() {
325        let mut list = EdgeList::new();
326        let e = Edge::new(
327            F26Dot6::from_int(10),
328            F26Dot6::from_int(0),
329            F26Dot6::from_int(10),
330            F26Dot6::from_int(10),
331        )
332        .unwrap();
333
334        list.push(e);
335        assert_eq!(list.len(), 1);
336        assert!(!list.is_empty());
337    }
338
339    #[test]
340    fn test_edgelist_insert_sorted() {
341        let mut list = EdgeList::new();
342
343        list.insert_sorted(
344            Edge::new(
345                F26Dot6::from_int(20),
346                F26Dot6::ZERO,
347                F26Dot6::from_int(20),
348                F26Dot6::from_int(10),
349            )
350            .unwrap(),
351        );
352
353        list.insert_sorted(
354            Edge::new(
355                F26Dot6::from_int(10),
356                F26Dot6::ZERO,
357                F26Dot6::from_int(10),
358                F26Dot6::from_int(10),
359            )
360            .unwrap(),
361        );
362
363        list.insert_sorted(
364            Edge::new(
365                F26Dot6::from_int(15),
366                F26Dot6::ZERO,
367                F26Dot6::from_int(15),
368                F26Dot6::from_int(10),
369            )
370            .unwrap(),
371        );
372
373        assert_eq!(list.len(), 3);
374        assert_eq!(list.as_slice()[0].x.to_int(), 10);
375        assert_eq!(list.as_slice()[1].x.to_int(), 15);
376        assert_eq!(list.as_slice()[2].x.to_int(), 20);
377    }
378
379    #[test]
380    fn test_edgelist_sort_by_x() {
381        let mut list = EdgeList::new();
382
383        list.push(
384            Edge::new(
385                F26Dot6::from_int(20),
386                F26Dot6::ZERO,
387                F26Dot6::from_int(20),
388                F26Dot6::from_int(10),
389            )
390            .unwrap(),
391        );
392
393        list.push(
394            Edge::new(
395                F26Dot6::from_int(10),
396                F26Dot6::ZERO,
397                F26Dot6::from_int(10),
398                F26Dot6::from_int(10),
399            )
400            .unwrap(),
401        );
402
403        list.sort_by_x();
404
405        assert_eq!(list.as_slice()[0].x.to_int(), 10);
406        assert_eq!(list.as_slice()[1].x.to_int(), 20);
407    }
408
409    #[test]
410    fn test_edgelist_remove_inactive() {
411        let mut list = EdgeList::new();
412
413        list.push(
414            Edge::new(
415                F26Dot6::from_int(10),
416                F26Dot6::ZERO,
417                F26Dot6::from_int(10),
418                F26Dot6::from_int(5),
419            )
420            .unwrap(),
421        );
422
423        list.push(
424            Edge::new(
425                F26Dot6::from_int(20),
426                F26Dot6::ZERO,
427                F26Dot6::from_int(20),
428                F26Dot6::from_int(10),
429            )
430            .unwrap(),
431        );
432
433        assert_eq!(list.len(), 2);
434
435        list.remove_inactive(6);
436        assert_eq!(list.len(), 1);
437        assert_eq!(list.as_slice()[0].x.to_int(), 20);
438    }
439
440    #[test]
441    fn test_edgelist_step_all() {
442        let mut list = EdgeList::new();
443
444        list.push(
445            Edge::new(
446                F26Dot6::from_int(0),
447                F26Dot6::from_int(0),
448                F26Dot6::from_int(10),
449                F26Dot6::from_int(10),
450            )
451            .unwrap(),
452        );
453
454        list.push(
455            Edge::new(
456                F26Dot6::from_int(0),
457                F26Dot6::from_int(0),
458                F26Dot6::from_int(20),
459                F26Dot6::from_int(10),
460            )
461            .unwrap(),
462        );
463
464        assert_eq!(list.as_slice()[0].x.to_int(), 0);
465        assert_eq!(list.as_slice()[1].x.to_int(), 0);
466
467        list.step_all();
468
469        assert_eq!(list.as_slice()[0].x.to_int(), 1);
470        assert_eq!(list.as_slice()[1].x.to_int(), 2);
471    }
472
473    #[test]
474    fn test_edgelist_clear() {
475        let mut list = EdgeList::new();
476        list.push(
477            Edge::new(
478                F26Dot6::from_int(10),
479                F26Dot6::ZERO,
480                F26Dot6::from_int(10),
481                F26Dot6::from_int(10),
482            )
483            .unwrap(),
484        );
485
486        assert!(!list.is_empty());
487        list.clear();
488        assert!(list.is_empty());
489    }
490
491    #[test]
492    fn test_edgelist_extend() {
493        let mut list1 = EdgeList::new();
494        let mut list2 = EdgeList::new();
495
496        list1.push(
497            Edge::new(
498                F26Dot6::from_int(10),
499                F26Dot6::ZERO,
500                F26Dot6::from_int(10),
501                F26Dot6::from_int(10),
502            )
503            .unwrap(),
504        );
505
506        list2.push(
507            Edge::new(
508                F26Dot6::from_int(20),
509                F26Dot6::ZERO,
510                F26Dot6::from_int(20),
511                F26Dot6::from_int(10),
512            )
513            .unwrap(),
514        );
515
516        list1.extend(&list2);
517        assert_eq!(list1.len(), 2);
518    }
519
520    #[test]
521    fn test_edge_fractional_slope() {
522        // Edge with slope 0.5 (moves 5 pixels over 10 scanlines)
523        let e = Edge::new(
524            F26Dot6::from_int(0),
525            F26Dot6::from_int(0),
526            F26Dot6::from_int(5),
527            F26Dot6::from_int(10),
528        )
529        .unwrap();
530
531        // Slope = 5/10 = 0.5, in 26.6 format = 32
532        assert_eq!(e.x_increment.raw(), 32);
533
534        let mut x = e.x;
535        // After 2 steps, should be at x=1
536        x = x + e.x_increment;
537        x = x + e.x_increment;
538        assert_eq!(x.to_int(), 1);
539    }
540
541    #[test]
542    fn test_edge_negative_slope() {
543        // Edge going left-down (negative slope)
544        let e = Edge::new(
545            F26Dot6::from_int(10),
546            F26Dot6::from_int(0),
547            F26Dot6::from_int(0),
548            F26Dot6::from_int(10),
549        )
550        .unwrap();
551
552        // Slope = -10/10 = -1.0, in 26.6 format = -64
553        assert_eq!(e.x_increment.raw(), -64);
554
555        let mut test_e = e;
556        assert_eq!(test_e.x.to_int(), 10);
557        test_e.step();
558        assert_eq!(test_e.x.to_int(), 9);
559    }
560
561    #[test]
562    fn test_edgelist_with_capacity() {
563        let list = EdgeList::with_capacity(100);
564        assert_eq!(list.len(), 0);
565        assert!(list.edges.capacity() >= 100);
566    }
567
568    #[test]
569    fn test_edge_winding_direction() {
570        // Upward edge (y1 < y2)
571        let e_up = Edge::new(
572            F26Dot6::from_int(0),
573            F26Dot6::from_int(0),
574            F26Dot6::from_int(10),
575            F26Dot6::from_int(10),
576        )
577        .unwrap();
578        assert_eq!(e_up.direction, 1);
579
580        // Downward edge (y1 > y2)
581        let e_down = Edge::new(
582            F26Dot6::from_int(10),
583            F26Dot6::from_int(10),
584            F26Dot6::from_int(0),
585            F26Dot6::from_int(0),
586        )
587        .unwrap();
588        assert_eq!(e_down.direction, -1);
589    }
590
591    #[test]
592    fn test_edgelist_iter() {
593        let mut list = EdgeList::new();
594        list.push(
595            Edge::new(
596                F26Dot6::from_int(10),
597                F26Dot6::ZERO,
598                F26Dot6::from_int(10),
599                F26Dot6::from_int(10),
600            )
601            .unwrap(),
602        );
603
604        let count = list.iter().count();
605        assert_eq!(count, 1);
606    }
607
608    #[test]
609    fn test_edgelist_iter_mut() {
610        let mut list = EdgeList::new();
611        list.push(
612            Edge::new(
613                F26Dot6::from_int(10),
614                F26Dot6::ZERO,
615                F26Dot6::from_int(10),
616                F26Dot6::from_int(10),
617            )
618            .unwrap(),
619        );
620
621        for edge in list.iter_mut() {
622            edge.step();
623        }
624
625        // No actual change since x_increment is 0 for vertical edge
626        assert_eq!(list.as_slice()[0].x.to_int(), 10);
627    }
628}