Skip to main content

agg_rust/
renderer_outline_aa.rs

1//! Anti-aliased outline renderer.
2//!
3//! Port of `agg_renderer_outline_aa.h` + `agg_line_profile_aa.cpp`.
4//! Renders anti-aliased lines with sub-pixel precision using distance
5//! interpolation and a configurable width profile.
6//!
7//! Copyright 2025.
8
9use crate::basics::{iround, RectI};
10use crate::dda_line::Dda2LineInterpolator;
11use crate::ellipse_bresenham::EllipseBresenhamInterpolator;
12use crate::line_aa_basics::*;
13use crate::math::fast_sqrt;
14use crate::pixfmt_rgba::PixelFormat;
15use crate::renderer_base::RendererBase;
16
17// ============================================================================
18// Line Profile
19// ============================================================================
20
21// These must match C++ line_profile_aa::subpixel_scale_e
22const PROFILE_SUBPIXEL_SHIFT: i32 = LINE_SUBPIXEL_SHIFT; // 8
23const PROFILE_SUBPIXEL_SCALE: i32 = 1 << PROFILE_SUBPIXEL_SHIFT; // 256
24const PROFILE_AA_SHIFT: i32 = 8;
25const PROFILE_AA_SCALE: i32 = 1 << PROFILE_AA_SHIFT; // 256
26const PROFILE_AA_MASK: i32 = PROFILE_AA_SCALE - 1; // 255
27
28/// Anti-aliased line width profile.
29///
30/// Port of C++ `line_profile_aa`. Builds a lookup table mapping perpendicular
31/// distance from line center → coverage value, with configurable width and
32/// gamma correction.
33pub struct LineProfileAa {
34    profile: Vec<u8>,
35    gamma: [u8; 256],
36    subpixel_width: i32,
37    min_width: f64,
38    smoother_width: f64,
39}
40
41impl LineProfileAa {
42    pub fn new() -> Self {
43        let mut s = Self {
44            profile: Vec::new(),
45            gamma: [0u8; 256],
46            subpixel_width: 0,
47            min_width: 1.0,
48            smoother_width: 1.0,
49        };
50        // Identity gamma
51        for i in 0..256 {
52            s.gamma[i] = i as u8;
53        }
54        s
55    }
56
57    /// Create with a specific width.
58    pub fn with_width(w: f64) -> Self {
59        let mut s = Self::new();
60        s.set_width(w);
61        s
62    }
63
64    pub fn min_width(&self) -> f64 {
65        self.min_width
66    }
67    pub fn smoother_width(&self) -> f64 {
68        self.smoother_width
69    }
70    pub fn subpixel_width(&self) -> i32 {
71        self.subpixel_width
72    }
73
74    pub fn set_min_width(&mut self, w: f64) {
75        self.min_width = w;
76    }
77    pub fn set_smoother_width(&mut self, w: f64) {
78        self.smoother_width = w;
79    }
80
81    /// Set the line width (in pixels).
82    /// Port of C++ `line_profile_aa::width`.
83    pub fn set_width(&mut self, mut w: f64) {
84        if w < 0.0 { w = 0.0; }
85
86        if w < self.smoother_width {
87            w += w;
88        } else {
89            w += self.smoother_width;
90        }
91
92        w *= 0.5;
93        w -= self.smoother_width;
94        let mut s = self.smoother_width;
95        if w < 0.0 {
96            s += w;
97            w = 0.0;
98        }
99        self.build_profile(w, s);
100    }
101
102    /// Apply gamma function.
103    pub fn set_gamma<F: Fn(f64) -> f64>(&mut self, gamma_fn: F) {
104        for i in 0..256 {
105            self.gamma[i] = iround(gamma_fn(i as f64 / 255.0) * 255.0) as u8;
106        }
107    }
108
109    /// Lookup coverage for a given perpendicular distance.
110    #[inline]
111    pub fn value(&self, dist: i32) -> u8 {
112        let idx = (dist + PROFILE_SUBPIXEL_SCALE * 2) as usize;
113        if idx < self.profile.len() {
114            self.profile[idx]
115        } else {
116            0
117        }
118    }
119
120    // Mirrors C++ `line_profile_aa::profile_size()`. Kept for API parity and
121    // used by tests; not called by the non-test build.
122    #[allow(dead_code)]
123    fn profile_size(&self) -> usize {
124        self.profile.len()
125    }
126
127    /// Port of C++ `line_profile_aa::set` + `line_profile_aa::profile`.
128    fn build_profile(&mut self, center_width: f64, smoother_width: f64) {
129        let mut base_val = 1.0f64;
130        let mut cw = center_width;
131        let mut sw = smoother_width;
132
133        if cw == 0.0 {
134            cw = 1.0 / PROFILE_SUBPIXEL_SCALE as f64;
135        }
136        if sw == 0.0 {
137            sw = 1.0 / PROFILE_SUBPIXEL_SCALE as f64;
138        }
139
140        let width = cw + sw;
141        if width < self.min_width {
142            let k = width / self.min_width;
143            base_val *= k;
144            cw /= k;
145            sw /= k;
146        }
147
148        // C++ profile(): m_subpixel_width = uround(w * subpixel_scale)
149        self.subpixel_width = iround((cw + sw) * PROFILE_SUBPIXEL_SCALE as f64);
150        let size = self.subpixel_width as usize + PROFILE_SUBPIXEL_SCALE as usize * 6;
151        self.profile.resize(size, 0);
152
153        let subpixel_center_width = (cw * PROFILE_SUBPIXEL_SCALE as f64) as usize;
154        let subpixel_smoother_width = (sw * PROFILE_SUBPIXEL_SCALE as f64) as usize;
155
156        let ch_center = PROFILE_SUBPIXEL_SCALE as usize * 2;
157
158        // Fill center region with full-alpha value
159        let val = self.gamma[(base_val * PROFILE_AA_MASK as f64) as usize];
160        for i in 0..subpixel_center_width {
161            self.profile[ch_center + i] = val;
162        }
163
164        // Fill smoother region with falloff
165        let ch_smoother = ch_center + subpixel_center_width;
166        for i in 0..subpixel_smoother_width {
167            let k = base_val - base_val * (i as f64 / subpixel_smoother_width as f64);
168            self.profile[ch_smoother + i] =
169                self.gamma[(k * PROFILE_AA_MASK as f64) as usize];
170        }
171
172        // Fill remaining with gamma[0]
173        let n_smoother = size
174            - subpixel_smoother_width
175            - subpixel_center_width
176            - PROFILE_SUBPIXEL_SCALE as usize * 2;
177        let gamma_zero = self.gamma[0];
178        for i in 0..n_smoother {
179            self.profile[ch_smoother + subpixel_smoother_width + i] = gamma_zero;
180        }
181
182        // Mirror to the left (C++: *--ch = *ch_center++)
183        let mut src = ch_center;
184        let mut dst = ch_center;
185        for _ in 0..(PROFILE_SUBPIXEL_SCALE as usize * 2) {
186            if dst == 0 || src >= self.profile.len() {
187                break;
188            }
189            dst -= 1;
190            let v = self.profile[src];
191            self.profile[dst] = v;
192            src += 1;
193        }
194    }
195}
196
197impl Default for LineProfileAa {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203// ============================================================================
204// Distance Interpolators
205// ============================================================================
206
207/// Distance interpolator 0 — for semidot/pie (distance from point).
208///
209/// Port of C++ `distance_interpolator0`.
210/// Uses `line_mr()` (medium resolution) for dx/dy.
211pub struct DistanceInterpolator0 {
212    // Retained for structural fidelity with C++ `distance_interpolator0::m_dx`
213    // (shifted at construction); not read after construction in this port.
214    #[allow(dead_code)]
215    dx: i32,
216    dy: i32,
217    dist: i32,
218}
219
220impl DistanceInterpolator0 {
221    pub fn new(x1: i32, y1: i32, x2: i32, y2: i32, x: i32, y: i32) -> Self {
222        let mut dx = line_mr(x2) - line_mr(x1);
223        let mut dy = line_mr(y2) - line_mr(y1);
224        let dist = (line_mr(x + LINE_SUBPIXEL_SCALE / 2) - line_mr(x2)) * dy
225            - (line_mr(y + LINE_SUBPIXEL_SCALE / 2) - line_mr(y2)) * dx;
226        dx <<= LINE_MR_SUBPIXEL_SHIFT;
227        dy <<= LINE_MR_SUBPIXEL_SHIFT;
228        Self { dx, dy, dist }
229    }
230
231    #[inline]
232    pub fn inc_x(&mut self) {
233        self.dist += self.dy;
234    }
235
236    #[inline]
237    pub fn dist(&self) -> i32 {
238        self.dist
239    }
240}
241
242/// Distance interpolator 00 — for pie (two rays).
243///
244/// Port of C++ `distance_interpolator00`.
245/// Uses `line_mr()` (medium resolution) for dx/dy.
246pub struct DistanceInterpolator00 {
247    // Retained for structural fidelity with C++ `distance_interpolator00::m_dx1`;
248    // not read after construction in this port.
249    #[allow(dead_code)]
250    dx1: i32,
251    dy1: i32,
252    // Retained for structural fidelity with C++ `distance_interpolator00::m_dx2`;
253    // not read after construction in this port.
254    #[allow(dead_code)]
255    dx2: i32,
256    dy2: i32,
257    dist1: i32,
258    dist2: i32,
259}
260
261impl DistanceInterpolator00 {
262    pub fn new(
263        xc: i32, yc: i32,
264        x1: i32, y1: i32,
265        x2: i32, y2: i32,
266        x: i32, y: i32,
267    ) -> Self {
268        let mut dx1 = line_mr(x1) - line_mr(xc);
269        let mut dy1 = line_mr(y1) - line_mr(yc);
270        let mut dx2 = line_mr(x2) - line_mr(xc);
271        let mut dy2 = line_mr(y2) - line_mr(yc);
272        let dist1 = (line_mr(x + LINE_SUBPIXEL_SCALE / 2) - line_mr(x1)) * dy1
273            - (line_mr(y + LINE_SUBPIXEL_SCALE / 2) - line_mr(y1)) * dx1;
274        let dist2 = (line_mr(x + LINE_SUBPIXEL_SCALE / 2) - line_mr(x2)) * dy2
275            - (line_mr(y + LINE_SUBPIXEL_SCALE / 2) - line_mr(y2)) * dx2;
276        dx1 <<= LINE_MR_SUBPIXEL_SHIFT;
277        dy1 <<= LINE_MR_SUBPIXEL_SHIFT;
278        dx2 <<= LINE_MR_SUBPIXEL_SHIFT;
279        dy2 <<= LINE_MR_SUBPIXEL_SHIFT;
280        Self { dx1, dy1, dx2, dy2, dist1, dist2 }
281    }
282
283    #[inline]
284    pub fn inc_x(&mut self) {
285        self.dist1 += self.dy1;
286        self.dist2 += self.dy2;
287    }
288
289    #[inline]
290    pub fn dist1(&self) -> i32 {
291        self.dist1
292    }
293    #[inline]
294    pub fn dist2(&self) -> i32 {
295        self.dist2
296    }
297}
298
299/// Distance interpolator 1 — basic perpendicular distance tracker.
300///
301/// Port of C++ `distance_interpolator1`.
302pub struct DistanceInterpolator1 {
303    dx: i32,
304    dy: i32,
305    dist: i32,
306}
307
308impl DistanceInterpolator1 {
309    pub fn new(x1: i32, y1: i32, x2: i32, y2: i32, x: i32, y: i32) -> Self {
310        let mut dx = x2 - x1;
311        let mut dy = y2 - y1;
312        let dist = iround(
313            (x + LINE_SUBPIXEL_SCALE / 2 - x2) as f64 * dy as f64
314                - (y + LINE_SUBPIXEL_SCALE / 2 - y2) as f64 * dx as f64,
315        );
316        dx <<= LINE_SUBPIXEL_SHIFT;
317        dy <<= LINE_SUBPIXEL_SHIFT;
318        Self { dx, dy, dist }
319    }
320
321    #[inline]
322    pub fn inc_x(&mut self, dy: i32) {
323        self.dist += self.dy;
324        if dy > 0 {
325            self.dist -= self.dx;
326        }
327        if dy < 0 {
328            self.dist += self.dx;
329        }
330    }
331
332    #[inline]
333    pub fn dec_x(&mut self, dy: i32) {
334        self.dist -= self.dy;
335        if dy > 0 {
336            self.dist -= self.dx;
337        }
338        if dy < 0 {
339            self.dist += self.dx;
340        }
341    }
342
343    #[inline]
344    pub fn inc_y(&mut self, dx: i32) {
345        self.dist -= self.dx;
346        if dx > 0 {
347            self.dist += self.dy;
348        }
349        if dx < 0 {
350            self.dist -= self.dy;
351        }
352    }
353
354    #[inline]
355    pub fn dec_y(&mut self, dx: i32) {
356        self.dist += self.dx;
357        if dx > 0 {
358            self.dist += self.dy;
359        }
360        if dx < 0 {
361            self.dist -= self.dy;
362        }
363    }
364
365    #[inline]
366    pub fn dist(&self) -> i32 {
367        self.dist
368    }
369    #[inline]
370    pub fn dx(&self) -> i32 {
371        self.dx
372    }
373    #[inline]
374    pub fn dy(&self) -> i32 {
375        self.dy
376    }
377}
378
379/// Distance interpolator 2 — tracks main distance + start or end join distance.
380///
381/// Port of C++ `distance_interpolator2`.
382pub struct DistanceInterpolator2 {
383    dx: i32,
384    dy: i32,
385    dx_start: i32,
386    dy_start: i32,
387    dist: i32,
388    dist_start: i32,
389}
390
391impl DistanceInterpolator2 {
392    /// Start join variant.
393    pub fn new_start(
394        x1: i32, y1: i32, x2: i32, y2: i32, sx: i32, sy: i32, x: i32, y: i32,
395    ) -> Self {
396        let mut dx = x2 - x1;
397        let mut dy = y2 - y1;
398        let mut dx_start = line_mr(sx) - line_mr(x1);
399        let mut dy_start = line_mr(sy) - line_mr(y1);
400        let dist = iround(
401            (x + LINE_SUBPIXEL_SCALE / 2 - x2) as f64 * dy as f64
402                - (y + LINE_SUBPIXEL_SCALE / 2 - y2) as f64 * dx as f64,
403        );
404        let dist_start = (line_mr(x + LINE_SUBPIXEL_SCALE / 2) - line_mr(sx)) * dy_start
405            - (line_mr(y + LINE_SUBPIXEL_SCALE / 2) - line_mr(sy)) * dx_start;
406        dx <<= LINE_SUBPIXEL_SHIFT;
407        dy <<= LINE_SUBPIXEL_SHIFT;
408        dx_start <<= LINE_MR_SUBPIXEL_SHIFT;
409        dy_start <<= LINE_MR_SUBPIXEL_SHIFT;
410        Self { dx, dy, dx_start, dy_start, dist, dist_start }
411    }
412
413    /// End join variant.
414    pub fn new_end(
415        x1: i32, y1: i32, x2: i32, y2: i32, ex: i32, ey: i32, x: i32, y: i32,
416    ) -> Self {
417        let mut dx = x2 - x1;
418        let mut dy = y2 - y1;
419        let mut dx_start = line_mr(ex) - line_mr(x2);
420        let mut dy_start = line_mr(ey) - line_mr(y2);
421        let dist = iround(
422            (x + LINE_SUBPIXEL_SCALE / 2 - x2) as f64 * dy as f64
423                - (y + LINE_SUBPIXEL_SCALE / 2 - y2) as f64 * dx as f64,
424        );
425        let dist_start = (line_mr(x + LINE_SUBPIXEL_SCALE / 2) - line_mr(ex)) * dy_start
426            - (line_mr(y + LINE_SUBPIXEL_SCALE / 2) - line_mr(ey)) * dx_start;
427        dx <<= LINE_SUBPIXEL_SHIFT;
428        dy <<= LINE_SUBPIXEL_SHIFT;
429        dx_start <<= LINE_MR_SUBPIXEL_SHIFT;
430        dy_start <<= LINE_MR_SUBPIXEL_SHIFT;
431        Self { dx, dy, dx_start, dy_start, dist, dist_start }
432    }
433
434    #[inline]
435    pub fn inc_x(&mut self, dy: i32) {
436        self.dist += self.dy;
437        self.dist_start += self.dy_start;
438        if dy > 0 {
439            self.dist -= self.dx;
440            self.dist_start -= self.dx_start;
441        }
442        if dy < 0 {
443            self.dist += self.dx;
444            self.dist_start += self.dx_start;
445        }
446    }
447
448    #[inline]
449    pub fn dec_x(&mut self, dy: i32) {
450        self.dist -= self.dy;
451        self.dist_start -= self.dy_start;
452        if dy > 0 {
453            self.dist -= self.dx;
454            self.dist_start -= self.dx_start;
455        }
456        if dy < 0 {
457            self.dist += self.dx;
458            self.dist_start += self.dx_start;
459        }
460    }
461
462    #[inline]
463    pub fn inc_y(&mut self, dx: i32) {
464        self.dist -= self.dx;
465        self.dist_start -= self.dx_start;
466        if dx > 0 {
467            self.dist += self.dy;
468            self.dist_start += self.dy_start;
469        }
470        if dx < 0 {
471            self.dist -= self.dy;
472            self.dist_start -= self.dy_start;
473        }
474    }
475
476    #[inline]
477    pub fn dec_y(&mut self, dx: i32) {
478        self.dist += self.dx;
479        self.dist_start += self.dx_start;
480        if dx > 0 {
481            self.dist += self.dy;
482            self.dist_start += self.dy_start;
483        }
484        if dx < 0 {
485            self.dist -= self.dy;
486            self.dist_start -= self.dy_start;
487        }
488    }
489
490    #[inline]
491    pub fn dist(&self) -> i32 {
492        self.dist
493    }
494    #[inline]
495    pub fn dist_start(&self) -> i32 {
496        self.dist_start
497    }
498    #[inline]
499    pub fn dist_end(&self) -> i32 {
500        self.dist_start
501    }
502    #[inline]
503    pub fn dx_start(&self) -> i32 {
504        self.dx_start
505    }
506    #[inline]
507    pub fn dy_start(&self) -> i32 {
508        self.dy_start
509    }
510    #[inline]
511    pub fn dx_end(&self) -> i32 {
512        self.dx_start
513    }
514    #[inline]
515    pub fn dy_end(&self) -> i32 {
516        self.dy_start
517    }
518}
519
520/// Distance interpolator 3 — tracks main + start + end join distances.
521///
522/// Port of C++ `distance_interpolator3`.
523pub struct DistanceInterpolator3 {
524    dx: i32,
525    dy: i32,
526    dx_start: i32,
527    dy_start: i32,
528    dx_end: i32,
529    dy_end: i32,
530    dist: i32,
531    dist_start: i32,
532    dist_end: i32,
533}
534
535impl DistanceInterpolator3 {
536    pub fn new(
537        x1: i32, y1: i32, x2: i32, y2: i32,
538        sx: i32, sy: i32, ex: i32, ey: i32,
539        x: i32, y: i32,
540    ) -> Self {
541        let mut dx = x2 - x1;
542        let mut dy = y2 - y1;
543        let mut dx_start = line_mr(sx) - line_mr(x1);
544        let mut dy_start = line_mr(sy) - line_mr(y1);
545        let mut dx_end = line_mr(ex) - line_mr(x2);
546        let mut dy_end = line_mr(ey) - line_mr(y2);
547
548        let dist = iround(
549            (x + LINE_SUBPIXEL_SCALE / 2 - x2) as f64 * dy as f64
550                - (y + LINE_SUBPIXEL_SCALE / 2 - y2) as f64 * dx as f64,
551        );
552        let dist_start = (line_mr(x + LINE_SUBPIXEL_SCALE / 2) - line_mr(sx)) * dy_start
553            - (line_mr(y + LINE_SUBPIXEL_SCALE / 2) - line_mr(sy)) * dx_start;
554        let dist_end = (line_mr(x + LINE_SUBPIXEL_SCALE / 2) - line_mr(ex)) * dy_end
555            - (line_mr(y + LINE_SUBPIXEL_SCALE / 2) - line_mr(ey)) * dx_end;
556
557        dx <<= LINE_SUBPIXEL_SHIFT;
558        dy <<= LINE_SUBPIXEL_SHIFT;
559        dx_start <<= LINE_MR_SUBPIXEL_SHIFT;
560        dy_start <<= LINE_MR_SUBPIXEL_SHIFT;
561        dx_end <<= LINE_MR_SUBPIXEL_SHIFT;
562        dy_end <<= LINE_MR_SUBPIXEL_SHIFT;
563
564        Self {
565            dx, dy, dx_start, dy_start, dx_end, dy_end, dist, dist_start, dist_end,
566        }
567    }
568
569    #[inline]
570    pub fn inc_x(&mut self, dy: i32) {
571        self.dist += self.dy;
572        self.dist_start += self.dy_start;
573        self.dist_end += self.dy_end;
574        if dy > 0 {
575            self.dist -= self.dx;
576            self.dist_start -= self.dx_start;
577            self.dist_end -= self.dx_end;
578        }
579        if dy < 0 {
580            self.dist += self.dx;
581            self.dist_start += self.dx_start;
582            self.dist_end += self.dx_end;
583        }
584    }
585
586    #[inline]
587    pub fn dec_x(&mut self, dy: i32) {
588        self.dist -= self.dy;
589        self.dist_start -= self.dy_start;
590        self.dist_end -= self.dy_end;
591        if dy > 0 {
592            self.dist -= self.dx;
593            self.dist_start -= self.dx_start;
594            self.dist_end -= self.dx_end;
595        }
596        if dy < 0 {
597            self.dist += self.dx;
598            self.dist_start += self.dx_start;
599            self.dist_end += self.dx_end;
600        }
601    }
602
603    #[inline]
604    pub fn inc_y(&mut self, dx: i32) {
605        self.dist -= self.dx;
606        self.dist_start -= self.dx_start;
607        self.dist_end -= self.dx_end;
608        if dx > 0 {
609            self.dist += self.dy;
610            self.dist_start += self.dy_start;
611            self.dist_end += self.dy_end;
612        }
613        if dx < 0 {
614            self.dist -= self.dy;
615            self.dist_start -= self.dy_start;
616            self.dist_end -= self.dy_end;
617        }
618    }
619
620    #[inline]
621    pub fn dec_y(&mut self, dx: i32) {
622        self.dist += self.dx;
623        self.dist_start += self.dx_start;
624        self.dist_end += self.dx_end;
625        if dx > 0 {
626            self.dist += self.dy;
627            self.dist_start += self.dy_start;
628            self.dist_end += self.dy_end;
629        }
630        if dx < 0 {
631            self.dist -= self.dy;
632            self.dist_start -= self.dy_start;
633            self.dist_end -= self.dy_end;
634        }
635    }
636
637    #[inline]
638    pub fn dist(&self) -> i32 {
639        self.dist
640    }
641    #[inline]
642    pub fn dist_start(&self) -> i32 {
643        self.dist_start
644    }
645    #[inline]
646    pub fn dist_end(&self) -> i32 {
647        self.dist_end
648    }
649    #[inline]
650    pub fn dx_start(&self) -> i32 {
651        self.dx_start
652    }
653    #[inline]
654    pub fn dy_start(&self) -> i32 {
655        self.dy_start
656    }
657    #[inline]
658    pub fn dx_end(&self) -> i32 {
659        self.dx_end
660    }
661    #[inline]
662    pub fn dy_end(&self) -> i32 {
663        self.dy_end
664    }
665}
666
667// ============================================================================
668// Outline AA Renderer Trait
669// ============================================================================
670
671pub const MAX_HALF_WIDTH: usize = 64;
672
673/// Trait for renderers used with `RasterizerOutlineAa`.
674///
675/// Both `RendererOutlineAa` (solid color) and `RendererOutlineImage`
676/// (image pattern) implement this trait, allowing the rasterizer to
677/// work with either renderer type.
678///
679/// Port of the C++ template interface used by `rasterizer_outline_aa`.
680pub trait OutlineAaRenderer {
681    /// Returns true if this renderer only supports accurate (miter) joins.
682    /// Image pattern renderers return true; solid AA renderers return false.
683    fn accurate_join_only(&self) -> bool;
684
685    /// Render a simple line segment (no join information).
686    fn line0(&mut self, lp: &LineParameters);
687
688    /// Render a line segment with start join bisectrix.
689    fn line1(&mut self, lp: &LineParameters, sx: i32, sy: i32);
690
691    /// Render a line segment with end join bisectrix.
692    fn line2(&mut self, lp: &LineParameters, ex: i32, ey: i32);
693
694    /// Render a line segment with both start and end join bisectrices.
695    fn line3(&mut self, lp: &LineParameters, sx: i32, sy: i32, ex: i32, ey: i32);
696
697    /// Render a semi-circular dot (for round caps).
698    fn semidot(&mut self, cmp: fn(i32) -> bool, xc1: i32, yc1: i32, xc2: i32, yc2: i32);
699
700    /// Render a pie slice (for round joins).
701    fn pie(&mut self, xc: i32, yc: i32, x1: i32, y1: i32, x2: i32, y2: i32);
702}
703
704// ============================================================================
705// Renderer Outline AA
706// ============================================================================
707
708/// Anti-aliased outline renderer.
709///
710/// Port of C++ `renderer_outline_aa<BaseRenderer>`.
711/// Renders anti-aliased lines using a distance interpolation technique
712/// with configurable width profile.
713pub struct RendererOutlineAa<'a, PF: PixelFormat> {
714    ren: &'a mut RendererBase<PF>,
715    profile: &'a LineProfileAa,
716    color: PF::ColorType,
717    clip_box: RectI,
718    clipping: bool,
719}
720
721impl<'a, PF: PixelFormat> RendererOutlineAa<'a, PF>
722where
723    PF::ColorType: Default + Clone,
724{
725    pub fn new(ren: &'a mut RendererBase<PF>, profile: &'a LineProfileAa) -> Self {
726        Self {
727            ren,
728            profile,
729            color: PF::ColorType::default(),
730            clip_box: RectI::new(0, 0, 0, 0),
731            clipping: false,
732        }
733    }
734
735    pub fn ren(&self) -> &RendererBase<PF> {
736        self.ren
737    }
738
739    pub fn set_color(&mut self, c: PF::ColorType) {
740        self.color = c;
741    }
742
743    pub fn color(&self) -> &PF::ColorType {
744        &self.color
745    }
746
747    pub fn subpixel_width(&self) -> i32 {
748        self.profile.subpixel_width()
749    }
750
751    pub fn set_clip_box(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
752        self.clip_box = RectI::new(
753            line_coord_sat(x1),
754            line_coord_sat(y1),
755            line_coord_sat(x2),
756            line_coord_sat(y2),
757        );
758        self.clipping = true;
759    }
760
761    pub fn reset_clipping(&mut self) {
762        self.clipping = false;
763    }
764
765    #[inline]
766    fn cover(&self, d: i32) -> u8 {
767        self.profile.value(d)
768    }
769
770    /// Render a simple line (no joins).
771    pub fn line0(&mut self, lp: &LineParameters) {
772        if self.clipping {
773            let (mut x1, mut y1, mut x2, mut y2) = (lp.x1, lp.y1, lp.x2, lp.y2);
774            let flags = clip_line_segment(&mut x1, &mut y1, &mut x2, &mut y2, &self.clip_box);
775            if flags >= 4 {
776                return;
777            }
778            if flags != 0 {
779                let lp2 = LineParameters::new(
780                    x1, y1, x2, y2,
781                    uround(calc_distance_i(x1, y1, x2, y2)),
782                );
783                self.line0_no_clip(&lp2);
784                return;
785            }
786        }
787        self.line0_no_clip(lp);
788    }
789
790    fn line0_no_clip(&mut self, lp: &LineParameters) {
791        if lp.len > LINE_MAX_LENGTH {
792            let (lp1, lp2) = lp.divide();
793            self.line0_no_clip(&lp1);
794            self.line0_no_clip(&lp2);
795            return;
796        }
797
798        let li = LineInterpolatorAa0::new(lp, self.profile.subpixel_width());
799        if li.count > 0 {
800            if lp.vertical {
801                self.draw_line0_ver(li, lp);
802            } else {
803                self.draw_line0_hor(li, lp);
804            }
805        }
806    }
807
808    fn draw_line0_hor(&mut self, mut li: LineInterpolatorAa0, lp: &LineParameters) {
809        while let Some(span) = li.step_hor(self.profile, lp) {
810            let x = li.x();
811            let y = li.y() - span.offset as i32 + 1;
812            self.ren.blend_solid_vspan(
813                x, y, span.len as i32, &self.color,
814                &li.covers[span.p0..span.p0 + span.len],
815            );
816        }
817    }
818
819    fn draw_line0_ver(&mut self, mut li: LineInterpolatorAa0, lp: &LineParameters) {
820        while let Some(span) = li.step_ver(self.profile, lp) {
821            let x = li.x() - span.offset as i32 + 1;
822            let y = li.y();
823            self.ren.blend_solid_hspan(
824                x, y, span.len as i32, &self.color,
825                &li.covers[span.p0..span.p0 + span.len],
826            );
827        }
828    }
829
830    /// Render line with start join.
831    pub fn line1(&mut self, lp: &LineParameters, sx: i32, sy: i32) {
832        if self.clipping {
833            let (mut x1, mut y1, mut x2, mut y2) = (lp.x1, lp.y1, lp.x2, lp.y2);
834            let flags = clip_line_segment(&mut x1, &mut y1, &mut x2, &mut y2, &self.clip_box);
835            if flags >= 4 {
836                return;
837            }
838            if flags != 0 {
839                let lp2 = LineParameters::new(
840                    x1, y1, x2, y2,
841                    uround(calc_distance_i(x1, y1, x2, y2)),
842                );
843                if flags & 1 != 0 {
844                    // Start was clipped — use line0 instead
845                    self.line0_no_clip(&lp2);
846                } else {
847                    self.line1_no_clip(&lp2, sx, sy);
848                }
849                return;
850            }
851        }
852        self.line1_no_clip(lp, sx, sy);
853    }
854
855    fn line1_no_clip(&mut self, lp: &LineParameters, mut sx: i32, mut sy: i32) {
856        if lp.len > LINE_MAX_LENGTH {
857            let (lp1, lp2) = lp.divide();
858            self.line1_no_clip(
859                &lp1,
860                (lp.x1 + sx) >> 1,
861                (lp.y1 + sy) >> 1,
862            );
863            self.line1_no_clip(
864                &lp2,
865                lp1.x2 + (lp1.y2 - lp1.y1),
866                lp1.y2 - (lp1.x2 - lp1.x1),
867            );
868            return;
869        }
870
871        fix_degenerate_bisectrix_start(lp, &mut sx, &mut sy);
872        let li = LineInterpolatorAa1::new(lp, sx, sy, self.profile.subpixel_width());
873        if lp.vertical {
874            self.draw_line1_ver(li, lp);
875        } else {
876            self.draw_line1_hor(li, lp);
877        }
878    }
879
880    fn draw_line1_hor(&mut self, mut li: LineInterpolatorAa1, lp: &LineParameters) {
881        while let Some(span) = li.step_hor(self.profile, lp) {
882            self.ren.blend_solid_vspan(
883                li.x(), li.y() - span.offset as i32 + 1, span.len as i32, &self.color,
884                &li.covers[span.p0..span.p0 + span.len],
885            );
886        }
887    }
888
889    fn draw_line1_ver(&mut self, mut li: LineInterpolatorAa1, lp: &LineParameters) {
890        while let Some(span) = li.step_ver(self.profile, lp) {
891            self.ren.blend_solid_hspan(
892                li.x() - span.offset as i32 + 1, li.y(), span.len as i32, &self.color,
893                &li.covers[span.p0..span.p0 + span.len],
894            );
895        }
896    }
897
898    /// Render line with end join.
899    pub fn line2(&mut self, lp: &LineParameters, ex: i32, ey: i32) {
900        if self.clipping {
901            let (mut x1, mut y1, mut x2, mut y2) = (lp.x1, lp.y1, lp.x2, lp.y2);
902            let flags = clip_line_segment(&mut x1, &mut y1, &mut x2, &mut y2, &self.clip_box);
903            if flags >= 4 {
904                return;
905            }
906            if flags != 0 {
907                let lp2 = LineParameters::new(
908                    x1, y1, x2, y2,
909                    uround(calc_distance_i(x1, y1, x2, y2)),
910                );
911                if flags & 2 != 0 {
912                    self.line0_no_clip(&lp2);
913                } else {
914                    self.line2_no_clip(&lp2, ex, ey);
915                }
916                return;
917            }
918        }
919        self.line2_no_clip(lp, ex, ey);
920    }
921
922    fn line2_no_clip(&mut self, lp: &LineParameters, mut ex: i32, mut ey: i32) {
923        if lp.len > LINE_MAX_LENGTH {
924            let (lp1, lp2) = lp.divide();
925            self.line2_no_clip(
926                &lp1,
927                lp1.x2 + (lp1.y2 - lp1.y1),
928                lp1.y2 - (lp1.x2 - lp1.x1),
929            );
930            self.line2_no_clip(
931                &lp2,
932                (lp.x2 + ex) >> 1,
933                (lp.y2 + ey) >> 1,
934            );
935            return;
936        }
937
938        fix_degenerate_bisectrix_end(lp, &mut ex, &mut ey);
939        let li = LineInterpolatorAa2::new(lp, ex, ey, self.profile.subpixel_width());
940        if lp.vertical {
941            self.draw_line2_ver(li, lp);
942        } else {
943            self.draw_line2_hor(li, lp);
944        }
945    }
946
947    fn draw_line2_hor(&mut self, mut li: LineInterpolatorAa2, lp: &LineParameters) {
948        while let Some(span) = li.step_hor(self.profile, lp) {
949            self.ren.blend_solid_vspan(
950                li.x(), li.y() - span.offset as i32 + 1, span.len as i32, &self.color,
951                &li.covers[span.p0..span.p0 + span.len],
952            );
953        }
954    }
955
956    fn draw_line2_ver(&mut self, mut li: LineInterpolatorAa2, lp: &LineParameters) {
957        while let Some(span) = li.step_ver(self.profile, lp) {
958            self.ren.blend_solid_hspan(
959                li.x() - span.offset as i32 + 1, li.y(), span.len as i32, &self.color,
960                &li.covers[span.p0..span.p0 + span.len],
961            );
962        }
963    }
964
965    /// Render line with both joins.
966    pub fn line3(
967        &mut self,
968        lp: &LineParameters,
969        sx: i32,
970        sy: i32,
971        ex: i32,
972        ey: i32,
973    ) {
974        if self.clipping {
975            let (mut x1, mut y1, mut x2, mut y2) = (lp.x1, lp.y1, lp.x2, lp.y2);
976            let flags = clip_line_segment(&mut x1, &mut y1, &mut x2, &mut y2, &self.clip_box);
977            if flags >= 4 {
978                return;
979            }
980            if flags != 0 {
981                let lp2 = LineParameters::new(
982                    x1, y1, x2, y2,
983                    uround(calc_distance_i(x1, y1, x2, y2)),
984                );
985                match flags & 3 {
986                    3 => self.line0_no_clip(&lp2),
987                    1 => self.line2_no_clip(&lp2, ex, ey),
988                    2 => self.line1_no_clip(&lp2, sx, sy),
989                    _ => self.line3_no_clip(&lp2, sx, sy, ex, ey),
990                }
991                return;
992            }
993        }
994        self.line3_no_clip(lp, sx, sy, ex, ey);
995    }
996
997    fn line3_no_clip(
998        &mut self,
999        lp: &LineParameters,
1000        mut sx: i32,
1001        mut sy: i32,
1002        mut ex: i32,
1003        mut ey: i32,
1004    ) {
1005        if lp.len > LINE_MAX_LENGTH {
1006            let (lp1, lp2) = lp.divide();
1007            let mx = lp1.x2 + (lp1.y2 - lp1.y1);
1008            let my = lp1.y2 - (lp1.x2 - lp1.x1);
1009            self.line3_no_clip(
1010                &lp1,
1011                (lp.x1 + sx) >> 1,
1012                (lp.y1 + sy) >> 1,
1013                mx, my,
1014            );
1015            self.line3_no_clip(
1016                &lp2,
1017                mx, my,
1018                (lp.x2 + ex) >> 1,
1019                (lp.y2 + ey) >> 1,
1020            );
1021            return;
1022        }
1023
1024        fix_degenerate_bisectrix_start(lp, &mut sx, &mut sy);
1025        fix_degenerate_bisectrix_end(lp, &mut ex, &mut ey);
1026        let li = LineInterpolatorAa3::new(lp, sx, sy, ex, ey, self.profile.subpixel_width());
1027        if lp.vertical {
1028            self.draw_line3_ver(li, lp);
1029        } else {
1030            self.draw_line3_hor(li, lp);
1031        }
1032    }
1033
1034    fn draw_line3_hor(&mut self, mut li: LineInterpolatorAa3, lp: &LineParameters) {
1035        while let Some(span) = li.step_hor(self.profile, lp) {
1036            self.ren.blend_solid_vspan(
1037                li.x(), li.y() - span.offset as i32 + 1, span.len as i32, &self.color,
1038                &li.covers[span.p0..span.p0 + span.len],
1039            );
1040        }
1041    }
1042
1043    fn draw_line3_ver(&mut self, mut li: LineInterpolatorAa3, lp: &LineParameters) {
1044        while let Some(span) = li.step_ver(self.profile, lp) {
1045            self.ren.blend_solid_hspan(
1046                li.x() - span.offset as i32 + 1, li.y(), span.len as i32, &self.color,
1047                &li.covers[span.p0..span.p0 + span.len],
1048            );
1049        }
1050    }
1051
1052    /// Render a semi-circular dot (for round caps).
1053    /// Port of C++ `semidot`.
1054    pub fn semidot<F: Fn(i32) -> bool>(
1055        &mut self,
1056        cmp: F,
1057        xc1: i32,
1058        yc1: i32,
1059        xc2: i32,
1060        yc2: i32,
1061    ) {
1062        let r = ((self.profile.subpixel_width() + LINE_SUBPIXEL_MASK) >> LINE_SUBPIXEL_SHIFT) as i32;
1063        if r < 1 {
1064            return;
1065        }
1066        let mut ei = EllipseBresenhamInterpolator::new(r, r);
1067        let mut dx = 0i32;
1068        let mut dy = -r;
1069        let mut dy0 = dy;
1070        let mut dx0 = dx;
1071
1072        let x = xc1 >> LINE_SUBPIXEL_SHIFT;
1073        let y = yc1 >> LINE_SUBPIXEL_SHIFT;
1074
1075        loop {
1076            dx += ei.dx();
1077            dy += ei.dy();
1078            if dy != dy0 {
1079                self.semidot_hline(&cmp, xc1, yc1, xc2, yc2, x - dx0, y + dy0, x + dx0);
1080                self.semidot_hline(&cmp, xc1, yc1, xc2, yc2, x - dx0, y - dy0, x + dx0);
1081            }
1082            dx0 = dx;
1083            dy0 = dy;
1084            ei.next();
1085            if dy >= 0 {
1086                break;
1087            }
1088        }
1089        self.semidot_hline(&cmp, xc1, yc1, xc2, yc2, x - dx0, y + dy0, x + dx0);
1090    }
1091
1092    /// Port of C++ `semidot_hline`.
1093    /// x1, y1, x2 are in pixel coordinates; xc1/yc1/xc2/yc2 are subpixel.
1094    fn semidot_hline<F: Fn(i32) -> bool>(
1095        &mut self,
1096        cmp: &F,
1097        xc1: i32,
1098        yc1: i32,
1099        xc2: i32,
1100        yc2: i32,
1101        mut x1: i32,
1102        y1: i32,
1103        x2: i32,
1104    ) {
1105        let mut covers = [0u8; MAX_HALF_WIDTH * 2 + 4];
1106        let p0 = 0usize;
1107        let mut p1 = 0usize;
1108
1109        // C++ passes pixel coords << subpixel_shift to DI0
1110        let x = x1 << LINE_SUBPIXEL_SHIFT;
1111        let y = y1 << LINE_SUBPIXEL_SHIFT;
1112        let w = self.profile.subpixel_width();
1113
1114        let mut di = DistanceInterpolator0::new(xc1, yc1, xc2, yc2, x, y);
1115
1116        // Offset to pixel center for distance calculation
1117        let mut dx = x + LINE_SUBPIXEL_SCALE / 2 - xc1;
1118        let dy = y + LINE_SUBPIXEL_SCALE / 2 - yc1;
1119
1120        loop {
1121            let d = fast_sqrt((dx * dx + dy * dy) as u32) as i32;
1122            covers[p1] = 0;
1123            if cmp(di.dist()) && d <= w {
1124                covers[p1] = self.cover(d);
1125            }
1126            p1 += 1;
1127            dx += LINE_SUBPIXEL_SCALE;
1128            di.inc_x();
1129            x1 += 1;
1130            if x1 > x2 {
1131                break;
1132            }
1133        }
1134
1135        self.ren.blend_solid_hspan(
1136            x1 - (p1 as i32), y1, (p1 - p0) as i32, &self.color, &covers[p0..p1],
1137        );
1138    }
1139
1140    /// Render a pie slice (for round joins between two line segments).
1141    /// Port of C++ `pie`.
1142    pub fn pie(
1143        &mut self,
1144        xc: i32,
1145        yc: i32,
1146        x1: i32,
1147        y1: i32,
1148        x2: i32,
1149        y2: i32,
1150    ) {
1151        let r = ((self.profile.subpixel_width() + LINE_SUBPIXEL_MASK) >> LINE_SUBPIXEL_SHIFT) as i32;
1152        if r < 1 {
1153            return;
1154        }
1155        let mut ei = EllipseBresenhamInterpolator::new(r, r);
1156        let mut dx = 0i32;
1157        let mut dy = -r;
1158        let mut dy0 = dy;
1159        let mut dx0 = dx;
1160
1161        let x = xc >> LINE_SUBPIXEL_SHIFT;
1162        let y = yc >> LINE_SUBPIXEL_SHIFT;
1163
1164        loop {
1165            dx += ei.dx();
1166            dy += ei.dy();
1167            if dy != dy0 {
1168                self.pie_hline(xc, yc, x1, y1, x2, y2, x - dx0, y + dy0, x + dx0);
1169                self.pie_hline(xc, yc, x1, y1, x2, y2, x - dx0, y - dy0, x + dx0);
1170            }
1171            dx0 = dx;
1172            dy0 = dy;
1173            ei.next();
1174            if dy >= 0 {
1175                break;
1176            }
1177        }
1178        self.pie_hline(xc, yc, x1, y1, x2, y2, x - dx0, y + dy0, x + dx0);
1179    }
1180
1181    /// Port of C++ `pie_hline`.
1182    /// xh1, yh1, xh2 are in pixel coordinates; xc/yc/xp1/yp1/xp2/yp2 are subpixel.
1183    fn pie_hline(
1184        &mut self,
1185        xc: i32,
1186        yc: i32,
1187        xp1: i32,
1188        yp1: i32,
1189        xp2: i32,
1190        yp2: i32,
1191        mut xh1: i32,
1192        yh1: i32,
1193        xh2: i32,
1194    ) {
1195        let mut covers = [0u8; MAX_HALF_WIDTH * 2 + 4];
1196        let p0 = 0usize;
1197        let mut p1 = 0usize;
1198
1199        let x = xh1 << LINE_SUBPIXEL_SHIFT;
1200        let y = yh1 << LINE_SUBPIXEL_SHIFT;
1201        let w = self.profile.subpixel_width();
1202
1203        let mut di = DistanceInterpolator00::new(
1204            xc, yc, xp1, yp1, xp2, yp2, x, y,
1205        );
1206
1207        let mut dx = x + LINE_SUBPIXEL_SCALE / 2 - xc;
1208        let dy = y + LINE_SUBPIXEL_SCALE / 2 - yc;
1209
1210        let xh0 = xh1;
1211        loop {
1212            let d = fast_sqrt((dx * dx + dy * dy) as u32) as i32;
1213            covers[p1] = 0;
1214            if di.dist1() <= 0 && di.dist2() > 0 && d <= w {
1215                covers[p1] = self.cover(d);
1216            }
1217            p1 += 1;
1218            dx += LINE_SUBPIXEL_SCALE;
1219            di.inc_x();
1220            xh1 += 1;
1221            if xh1 > xh2 {
1222                break;
1223            }
1224        }
1225
1226        self.ren.blend_solid_hspan(
1227            xh0, yh1, (p1 - p0) as i32, &self.color, &covers[p0..p1],
1228        );
1229    }
1230}
1231
1232// Implementation of OutlineAaRenderer for RendererOutlineAa.
1233impl<'a, PF: PixelFormat> OutlineAaRenderer for RendererOutlineAa<'a, PF>
1234where
1235    PF::ColorType: Default + Clone,
1236{
1237    fn accurate_join_only(&self) -> bool {
1238        false
1239    }
1240
1241    fn line0(&mut self, lp: &LineParameters) {
1242        self.line0(lp);
1243    }
1244
1245    fn line1(&mut self, lp: &LineParameters, sx: i32, sy: i32) {
1246        self.line1(lp, sx, sy);
1247    }
1248
1249    fn line2(&mut self, lp: &LineParameters, ex: i32, ey: i32) {
1250        self.line2(lp, ex, ey);
1251    }
1252
1253    fn line3(&mut self, lp: &LineParameters, sx: i32, sy: i32, ex: i32, ey: i32) {
1254        self.line3(lp, sx, sy, ex, ey);
1255    }
1256
1257    fn semidot(&mut self, cmp: fn(i32) -> bool, xc1: i32, yc1: i32, xc2: i32, yc2: i32) {
1258        self.semidot(cmp, xc1, yc1, xc2, yc2);
1259    }
1260
1261    fn pie(&mut self, xc: i32, yc: i32, x1: i32, y1: i32, x2: i32, y2: i32) {
1262        self.pie(xc, yc, x1, y1, x2, y2);
1263    }
1264}
1265
1266// ============================================================================
1267// Helpers
1268// ============================================================================
1269
1270#[inline]
1271fn calc_distance_i(x1: i32, y1: i32, x2: i32, y2: i32) -> f64 {
1272    let dx = (x2 - x1) as f64;
1273    let dy = (y2 - y1) as f64;
1274    (dx * dx + dy * dy).sqrt()
1275}
1276
1277#[inline]
1278fn uround(v: f64) -> i32 {
1279    (v + 0.5) as i32
1280}
1281
1282// ============================================================================
1283// Line Interpolator AA base functionality
1284// ============================================================================
1285
1286const COVER_SIZE: usize = MAX_HALF_WIDTH * 2 + 4;
1287const DIST_SIZE: usize = MAX_HALF_WIDTH + 1;
1288
1289/// Span result from a line interpolator step.
1290struct LineSpan {
1291    /// Index into covers array where the span starts.
1292    p0: usize,
1293    /// Number of cover values in the span.
1294    len: usize,
1295    /// For step_hor: vertical offset (dy) for blend_solid_vspan positioning.
1296    /// For step_ver: horizontal offset (dx) for blend_solid_hspan positioning.
1297    offset: i32,
1298}
1299
1300// Common initialization for all line interpolator types
1301fn init_line_interpolator_base(lp: &LineParameters, width: i32) -> (
1302    Dda2LineInterpolator, // li
1303    i32, // x
1304    i32, // y
1305    i32, // count
1306    i32, // len
1307    i32, // max_extent
1308    [i32; DIST_SIZE], // dist table
1309) {
1310    let max_extent = (width + LINE_SUBPIXEL_MASK) >> LINE_SUBPIXEL_SHIFT;
1311
1312    let x;
1313    let y;
1314    let count;
1315    let li;
1316
1317    if lp.vertical {
1318        x = lp.x1 >> LINE_SUBPIXEL_SHIFT;
1319        y = lp.y1 >> LINE_SUBPIXEL_SHIFT;
1320        count = ((lp.y2 >> LINE_SUBPIXEL_SHIFT) - y).abs();
1321        li = Dda2LineInterpolator::new_relative(
1322            line_dbl_hr(lp.x2 - lp.x1),
1323            (lp.y2 - lp.y1).abs(),
1324        );
1325    } else {
1326        x = lp.x1 >> LINE_SUBPIXEL_SHIFT;
1327        y = lp.y1 >> LINE_SUBPIXEL_SHIFT;
1328        count = ((lp.x2 >> LINE_SUBPIXEL_SHIFT) - x).abs();
1329        li = Dda2LineInterpolator::new_relative(
1330            line_dbl_hr(lp.y2 - lp.y1),
1331            (lp.x2 - lp.x1).abs() + 1,
1332        );
1333    };
1334
1335    let len = if lp.vertical == (lp.inc > 0) { -lp.len } else { lp.len };
1336
1337    // Pre-compute distance table
1338    let mut dist = [0i32; DIST_SIZE];
1339    let mut dd = Dda2LineInterpolator::new_forward(
1340        0,
1341        if lp.vertical { lp.dy << LINE_SUBPIXEL_SHIFT } else { lp.dx << LINE_SUBPIXEL_SHIFT },
1342        lp.len,
1343    );
1344    let stop = width + LINE_SUBPIXEL_SCALE * 2;
1345    let mut i = 0;
1346    while i < MAX_HALF_WIDTH {
1347        dist[i] = dd.y();
1348        if dist[i] >= stop {
1349            break;
1350        }
1351        dd.inc();
1352        i += 1;
1353    }
1354    if i < DIST_SIZE {
1355        dist[i] = 0x7FFF_0000;
1356    }
1357
1358    (li, x, y, count, len, max_extent, dist)
1359}
1360
1361/// Line interpolator for AA line type 0 (no joins).
1362/// Port of C++ `line_interpolator_aa0`.
1363struct LineInterpolatorAa0 {
1364    di: DistanceInterpolator1,
1365    li: Dda2LineInterpolator,
1366    x: i32,
1367    y: i32,
1368    old_x: i32,
1369    old_y: i32,
1370    count: i32,
1371    width: i32,
1372    // Retained for structural fidelity with C++ `line_interpolator_aa_base::m_max_extent`;
1373    // the step-limit checks use the local value in this port, so the stored field is unread.
1374    #[allow(dead_code)]
1375    max_extent: i32,
1376    len: i32,
1377    step: i32,
1378    dist: [i32; DIST_SIZE],
1379    pub covers: [u8; COVER_SIZE],
1380}
1381
1382impl LineInterpolatorAa0 {
1383    fn new(lp: &LineParameters, subpixel_width: i32) -> Self {
1384        let (mut li, x, y, count, len, max_extent, dist) =
1385            init_line_interpolator_base(lp, subpixel_width);
1386
1387        // C++: m_di(lp.x1, lp.y1, lp.x2, lp.y2,
1388        //          lp.x1 & ~line_subpixel_mask, lp.y1 & ~line_subpixel_mask)
1389        let di = DistanceInterpolator1::new(
1390            lp.x1, lp.y1, lp.x2, lp.y2,
1391            lp.x1 & !LINE_SUBPIXEL_MASK,
1392            lp.y1 & !LINE_SUBPIXEL_MASK,
1393        );
1394
1395        li.adjust_forward();
1396
1397        Self {
1398            di,
1399            li,
1400            x,
1401            y,
1402            old_x: x,
1403            old_y: y,
1404            count,
1405            width: subpixel_width,
1406            max_extent,
1407            len,
1408            step: 0,
1409            dist,
1410            covers: [0u8; COVER_SIZE],
1411        }
1412    }
1413
1414    fn x(&self) -> i32 { self.x }
1415    fn y(&self) -> i32 { self.y }
1416
1417    fn step_hor(&mut self, profile: &LineProfileAa, lp: &LineParameters) -> Option<LineSpan> {
1418        // Check at the BEGINNING — C++ does blend first, then `return ++step < count`.
1419        // We must check before work so that the LAST step still returns Some(span).
1420        if self.step >= self.count { return None; }
1421
1422        self.li.inc();
1423        self.x += lp.inc;
1424        self.y = (lp.y1 + self.li.y()) >> LINE_SUBPIXEL_SHIFT;
1425
1426        if lp.inc > 0 {
1427            self.di.inc_x(self.y - self.old_y);
1428        } else {
1429            self.di.dec_x(self.y - self.old_y);
1430        }
1431        self.old_y = self.y;
1432
1433        let s1 = self.di.dist() / self.len;
1434
1435        let center = MAX_HALF_WIDTH + 2;
1436        let mut p0 = center;
1437        let mut p1 = center;
1438
1439        self.covers[p1] = profile.value(s1) as u8;
1440        p1 += 1;
1441
1442        let mut dy = 1usize;
1443        loop {
1444            if dy >= DIST_SIZE { break; }
1445            let dist = self.dist[dy] - s1;
1446            if dist > self.width { break; }
1447            self.covers[p1] = profile.value(dist) as u8;
1448            p1 += 1;
1449            dy += 1;
1450        }
1451
1452        let mut dy = 1usize;
1453        loop {
1454            if dy >= DIST_SIZE { break; }
1455            let dist = self.dist[dy] + s1;
1456            if dist > self.width { break; }
1457            p0 -= 1;
1458            self.covers[p0] = profile.value(dist) as u8;
1459            dy += 1;
1460        }
1461
1462        self.step += 1;
1463
1464        Some(LineSpan {
1465            p0,
1466            len: p1 - p0,
1467            offset: dy as i32,
1468        })
1469    }
1470
1471    fn step_ver(&mut self, profile: &LineProfileAa, lp: &LineParameters) -> Option<LineSpan> {
1472        if self.step >= self.count { return None; }
1473
1474        self.li.inc();
1475        self.y += lp.inc;
1476        self.x = (lp.x1 + self.li.y()) >> LINE_SUBPIXEL_SHIFT;
1477
1478        if lp.inc > 0 {
1479            self.di.inc_y(self.x - self.old_x);
1480        } else {
1481            self.di.dec_y(self.x - self.old_x);
1482        }
1483        self.old_x = self.x;
1484
1485        let s1 = self.di.dist() / self.len;
1486
1487        let center = MAX_HALF_WIDTH + 2;
1488        let mut p0 = center;
1489        let mut p1 = center;
1490
1491        self.covers[p1] = profile.value(s1) as u8;
1492        p1 += 1;
1493
1494        let mut dx = 1usize;
1495        loop {
1496            if dx >= DIST_SIZE { break; }
1497            let dist = self.dist[dx] - s1;
1498            if dist > self.width { break; }
1499            self.covers[p1] = profile.value(dist) as u8;
1500            p1 += 1;
1501            dx += 1;
1502        }
1503
1504        let mut dx = 1usize;
1505        loop {
1506            if dx >= DIST_SIZE { break; }
1507            let dist = self.dist[dx] + s1;
1508            if dist > self.width { break; }
1509            p0 -= 1;
1510            self.covers[p0] = profile.value(dist) as u8;
1511            dx += 1;
1512        }
1513
1514        self.step += 1;
1515
1516        Some(LineSpan {
1517            p0,
1518            len: p1 - p0,
1519            offset: dx as i32,
1520        })
1521    }
1522}
1523
1524/// Line interpolator for AA line type 1 (start join).
1525/// Port of C++ `line_interpolator_aa1`.
1526struct LineInterpolatorAa1 {
1527    di: DistanceInterpolator2,
1528    li: Dda2LineInterpolator,
1529    x: i32,
1530    y: i32,
1531    old_x: i32,
1532    old_y: i32,
1533    count: i32,
1534    width: i32,
1535    // Retained for structural fidelity with C++ `line_interpolator_aa_base::m_max_extent`;
1536    // the step-limit checks use the local value in this port, so the stored field is unread.
1537    #[allow(dead_code)]
1538    max_extent: i32,
1539    len: i32,
1540    step: i32,
1541    dist: [i32; DIST_SIZE],
1542    pub covers: [u8; COVER_SIZE],
1543}
1544
1545impl LineInterpolatorAa1 {
1546    fn new(lp: &LineParameters, sx: i32, sy: i32, subpixel_width: i32) -> Self {
1547        let (mut li, mut x, mut y, count, len, max_extent, dist) =
1548            init_line_interpolator_base(lp, subpixel_width);
1549
1550        let mut di = DistanceInterpolator2::new_start(
1551            lp.x1, lp.y1, lp.x2, lp.y2, sx, sy,
1552            lp.x1 & !LINE_SUBPIXEL_MASK,
1553            lp.y1 & !LINE_SUBPIXEL_MASK,
1554        );
1555
1556        let mut old_x = x;
1557        let mut old_y = y;
1558        let mut step = 0i32;
1559
1560        // Backward stepping to find where start join begins
1561        let mut npix = 1i32;
1562
1563        if lp.vertical {
1564            loop {
1565                li.dec();
1566                y -= lp.inc;
1567                x = (lp.x1 + li.y()) >> LINE_SUBPIXEL_SHIFT;
1568
1569                if lp.inc > 0 {
1570                    di.dec_y(x - old_x);
1571                } else {
1572                    di.inc_y(x - old_x);
1573                }
1574                old_x = x;
1575
1576                let mut dist1_start = di.dist_start();
1577                let mut dist2_start = dist1_start;
1578
1579                let mut dx = 0;
1580                if dist1_start < 0 { npix += 1; }
1581                loop {
1582                    dist1_start += di.dy_start();
1583                    dist2_start -= di.dy_start();
1584                    if dist1_start < 0 { npix += 1; }
1585                    if dist2_start < 0 { npix += 1; }
1586                    dx += 1;
1587                    if dist[dx as usize] > subpixel_width { break; }
1588                }
1589                step -= 1;
1590                if npix == 0 { break; }
1591                npix = 0;
1592                if step < -max_extent { break; }
1593            }
1594        } else {
1595            loop {
1596                li.dec();
1597                x -= lp.inc;
1598                y = (lp.y1 + li.y()) >> LINE_SUBPIXEL_SHIFT;
1599
1600                if lp.inc > 0 {
1601                    di.dec_x(y - old_y);
1602                } else {
1603                    di.inc_x(y - old_y);
1604                }
1605                old_y = y;
1606
1607                let mut dist1_start = di.dist_start();
1608                let mut dist2_start = dist1_start;
1609
1610                let mut dy = 0;
1611                if dist1_start < 0 { npix += 1; }
1612                loop {
1613                    dist1_start -= di.dx_start();
1614                    dist2_start += di.dx_start();
1615                    if dist1_start < 0 { npix += 1; }
1616                    if dist2_start < 0 { npix += 1; }
1617                    dy += 1;
1618                    if dist[dy as usize] > subpixel_width { break; }
1619                }
1620                step -= 1;
1621                if npix == 0 { break; }
1622                npix = 0;
1623                if step < -max_extent { break; }
1624            }
1625        }
1626
1627        li.adjust_forward();
1628
1629        Self {
1630            di, li, x, y, old_x, old_y,
1631            count, width: subpixel_width, max_extent, len, step,
1632            dist, covers: [0u8; COVER_SIZE],
1633        }
1634    }
1635
1636    fn x(&self) -> i32 { self.x }
1637    fn y(&self) -> i32 { self.y }
1638
1639    fn step_hor(&mut self, profile: &LineProfileAa, lp: &LineParameters) -> Option<LineSpan> {
1640        if self.step >= self.count { return None; }
1641
1642        self.li.inc();
1643        self.x += lp.inc;
1644        self.y = (lp.y1 + self.li.y()) >> LINE_SUBPIXEL_SHIFT;
1645        if lp.inc > 0 { self.di.inc_x(self.y - self.old_y); }
1646        else { self.di.dec_x(self.y - self.old_y); }
1647        self.old_y = self.y;
1648
1649        let s1 = self.di.dist() / self.len;
1650        let mut dist_start = self.di.dist_start();
1651
1652        let center = MAX_HALF_WIDTH + 2;
1653        let mut p0 = center;
1654        let mut p1 = center;
1655
1656        self.covers[p1] = 0;
1657        if dist_start <= 0 {
1658            self.covers[p1] = profile.value(s1) as u8;
1659        }
1660        p1 += 1;
1661
1662        let mut dy = 1usize;
1663        loop {
1664            if dy >= DIST_SIZE { break; }
1665            let dist = self.dist[dy] - s1;
1666            if dist > self.width { break; }
1667            dist_start -= self.di.dx_start();
1668            self.covers[p1] = 0;
1669            if dist_start <= 0 {
1670                self.covers[p1] = profile.value(dist) as u8;
1671            }
1672            p1 += 1;
1673            dy += 1;
1674        }
1675
1676        let mut dy = 1usize;
1677        dist_start = self.di.dist_start();
1678        loop {
1679            if dy >= DIST_SIZE { break; }
1680            let dist = self.dist[dy] + s1;
1681            if dist > self.width { break; }
1682            dist_start += self.di.dx_start();
1683            p0 -= 1;
1684            self.covers[p0] = 0;
1685            if dist_start <= 0 {
1686                self.covers[p0] = profile.value(dist) as u8;
1687            }
1688            dy += 1;
1689        }
1690
1691        self.step += 1;
1692
1693        Some(LineSpan { p0, len: p1 - p0, offset: dy as i32 })
1694    }
1695
1696    fn step_ver(&mut self, profile: &LineProfileAa, lp: &LineParameters) -> Option<LineSpan> {
1697        if self.step >= self.count { return None; }
1698
1699        self.li.inc();
1700        self.y += lp.inc;
1701        self.x = (lp.x1 + self.li.y()) >> LINE_SUBPIXEL_SHIFT;
1702        if lp.inc > 0 { self.di.inc_y(self.x - self.old_x); }
1703        else { self.di.dec_y(self.x - self.old_x); }
1704        self.old_x = self.x;
1705
1706        let s1 = self.di.dist() / self.len;
1707        let mut dist_start = self.di.dist_start();
1708
1709        let center = MAX_HALF_WIDTH + 2;
1710        let mut p0 = center;
1711        let mut p1 = center;
1712
1713        self.covers[p1] = 0;
1714        if dist_start <= 0 {
1715            self.covers[p1] = profile.value(s1) as u8;
1716        }
1717        p1 += 1;
1718
1719        let mut dx = 1usize;
1720        loop {
1721            if dx >= DIST_SIZE { break; }
1722            let dist = self.dist[dx] - s1;
1723            if dist > self.width { break; }
1724            dist_start += self.di.dy_start();
1725            self.covers[p1] = 0;
1726            if dist_start <= 0 {
1727                self.covers[p1] = profile.value(dist) as u8;
1728            }
1729            p1 += 1;
1730            dx += 1;
1731        }
1732
1733        let mut dx = 1usize;
1734        dist_start = self.di.dist_start();
1735        loop {
1736            if dx >= DIST_SIZE { break; }
1737            let dist = self.dist[dx] + s1;
1738            if dist > self.width { break; }
1739            dist_start -= self.di.dy_start();
1740            p0 -= 1;
1741            self.covers[p0] = 0;
1742            if dist_start <= 0 {
1743                self.covers[p0] = profile.value(dist) as u8;
1744            }
1745            dx += 1;
1746        }
1747
1748        self.step += 1;
1749
1750        Some(LineSpan { p0, len: p1 - p0, offset: dx as i32 })
1751    }
1752}
1753
1754/// Line interpolator for AA line type 2 (end join).
1755/// Port of C++ `line_interpolator_aa2`.
1756struct LineInterpolatorAa2 {
1757    di: DistanceInterpolator2,
1758    li: Dda2LineInterpolator,
1759    x: i32,
1760    y: i32,
1761    old_x: i32,
1762    old_y: i32,
1763    count: i32,
1764    width: i32,
1765    // Retained for structural fidelity with C++ `line_interpolator_aa_base::m_max_extent`;
1766    // the step-limit checks use the local value in this port, so the stored field is unread.
1767    #[allow(dead_code)]
1768    max_extent: i32,
1769    len: i32,
1770    step: i32,
1771    dist: [i32; DIST_SIZE],
1772    pub covers: [u8; COVER_SIZE],
1773}
1774
1775impl LineInterpolatorAa2 {
1776    fn new(lp: &LineParameters, ex: i32, ey: i32, subpixel_width: i32) -> Self {
1777        let (mut li, x, y, count, len, max_extent, dist) =
1778            init_line_interpolator_base(lp, subpixel_width);
1779
1780        let di = DistanceInterpolator2::new_end(
1781            lp.x1, lp.y1, lp.x2, lp.y2, ex, ey,
1782            lp.x1 & !LINE_SUBPIXEL_MASK,
1783            lp.y1 & !LINE_SUBPIXEL_MASK,
1784        );
1785
1786        li.adjust_forward();
1787        let step = 0 - max_extent;
1788
1789        Self {
1790            di, li, x, y, old_x: x, old_y: y,
1791            count, width: subpixel_width, max_extent, len, step,
1792            dist, covers: [0u8; COVER_SIZE],
1793        }
1794    }
1795
1796    fn x(&self) -> i32 { self.x }
1797    fn y(&self) -> i32 { self.y }
1798
1799    fn step_hor(&mut self, profile: &LineProfileAa, lp: &LineParameters) -> Option<LineSpan> {
1800        if self.step >= self.count { return None; }
1801
1802        self.li.inc();
1803        self.x += lp.inc;
1804        self.y = (lp.y1 + self.li.y()) >> LINE_SUBPIXEL_SHIFT;
1805        if lp.inc > 0 { self.di.inc_x(self.y - self.old_y); }
1806        else { self.di.dec_x(self.y - self.old_y); }
1807        self.old_y = self.y;
1808
1809        let s1 = self.di.dist() / self.len;
1810        let mut dist_end = self.di.dist_end();
1811
1812        let center = MAX_HALF_WIDTH + 2;
1813        let mut p0 = center;
1814        let mut p1 = center;
1815
1816        let mut npix = 0;
1817        self.covers[p1] = 0;
1818        if dist_end > 0 {
1819            self.covers[p1] = profile.value(s1) as u8;
1820            npix += 1;
1821        }
1822        p1 += 1;
1823
1824        let mut dy = 1usize;
1825        loop {
1826            if dy >= DIST_SIZE { break; }
1827            let dist = self.dist[dy] - s1;
1828            if dist > self.width { break; }
1829            dist_end -= self.di.dx_end();
1830            self.covers[p1] = 0;
1831            if dist_end > 0 {
1832                self.covers[p1] = profile.value(dist) as u8;
1833                npix += 1;
1834            }
1835            p1 += 1;
1836            dy += 1;
1837        }
1838
1839        let mut dy = 1usize;
1840        dist_end = self.di.dist_end();
1841        loop {
1842            if dy >= DIST_SIZE { break; }
1843            let dist = self.dist[dy] + s1;
1844            if dist > self.width { break; }
1845            dist_end += self.di.dx_end();
1846            p0 -= 1;
1847            self.covers[p0] = 0;
1848            if dist_end > 0 {
1849                self.covers[p0] = profile.value(dist) as u8;
1850                npix += 1;
1851            }
1852            dy += 1;
1853        }
1854
1855        self.step += 1;
1856        if npix == 0 { return None; }
1857
1858        Some(LineSpan { p0, len: p1 - p0, offset: dy as i32 })
1859    }
1860
1861    fn step_ver(&mut self, profile: &LineProfileAa, lp: &LineParameters) -> Option<LineSpan> {
1862        if self.step >= self.count { return None; }
1863
1864        self.li.inc();
1865        self.y += lp.inc;
1866        self.x = (lp.x1 + self.li.y()) >> LINE_SUBPIXEL_SHIFT;
1867        if lp.inc > 0 { self.di.inc_y(self.x - self.old_x); }
1868        else { self.di.dec_y(self.x - self.old_x); }
1869        self.old_x = self.x;
1870
1871        let s1 = self.di.dist() / self.len;
1872        let mut dist_end = self.di.dist_end();
1873
1874        let center = MAX_HALF_WIDTH + 2;
1875        let mut p0 = center;
1876        let mut p1 = center;
1877
1878        let mut npix = 0;
1879        self.covers[p1] = 0;
1880        if dist_end > 0 {
1881            self.covers[p1] = profile.value(s1) as u8;
1882            npix += 1;
1883        }
1884        p1 += 1;
1885
1886        let mut dx = 1usize;
1887        loop {
1888            if dx >= DIST_SIZE { break; }
1889            let dist = self.dist[dx] - s1;
1890            if dist > self.width { break; }
1891            dist_end += self.di.dy_end();
1892            self.covers[p1] = 0;
1893            if dist_end > 0 {
1894                self.covers[p1] = profile.value(dist) as u8;
1895                npix += 1;
1896            }
1897            p1 += 1;
1898            dx += 1;
1899        }
1900
1901        let mut dx = 1usize;
1902        dist_end = self.di.dist_end();
1903        loop {
1904            if dx >= DIST_SIZE { break; }
1905            let dist = self.dist[dx] + s1;
1906            if dist > self.width { break; }
1907            dist_end -= self.di.dy_end();
1908            p0 -= 1;
1909            self.covers[p0] = 0;
1910            if dist_end > 0 {
1911                self.covers[p0] = profile.value(dist) as u8;
1912                npix += 1;
1913            }
1914            dx += 1;
1915        }
1916
1917        self.step += 1;
1918        if npix == 0 { return None; }
1919
1920        Some(LineSpan { p0, len: p1 - p0, offset: dx as i32 })
1921    }
1922}
1923
1924/// Line interpolator for AA line type 3 (both joins).
1925/// Port of C++ `line_interpolator_aa3`.
1926struct LineInterpolatorAa3 {
1927    di: DistanceInterpolator3,
1928    li: Dda2LineInterpolator,
1929    x: i32,
1930    y: i32,
1931    old_x: i32,
1932    old_y: i32,
1933    count: i32,
1934    width: i32,
1935    // Retained for structural fidelity with C++ `line_interpolator_aa_base::m_max_extent`;
1936    // the step-limit checks use the local value in this port, so the stored field is unread.
1937    #[allow(dead_code)]
1938    max_extent: i32,
1939    len: i32,
1940    step: i32,
1941    dist: [i32; DIST_SIZE],
1942    pub covers: [u8; COVER_SIZE],
1943}
1944
1945impl LineInterpolatorAa3 {
1946    fn new(
1947        lp: &LineParameters,
1948        sx: i32, sy: i32, ex: i32, ey: i32,
1949        subpixel_width: i32,
1950    ) -> Self {
1951        let (mut li, mut x, mut y, count, len, max_extent, dist) =
1952            init_line_interpolator_base(lp, subpixel_width);
1953
1954        let mut di = DistanceInterpolator3::new(
1955            lp.x1, lp.y1, lp.x2, lp.y2,
1956            sx, sy, ex, ey,
1957            lp.x1 & !LINE_SUBPIXEL_MASK,
1958            lp.y1 & !LINE_SUBPIXEL_MASK,
1959        );
1960
1961        let mut old_x = x;
1962        let mut old_y = y;
1963        let mut step = 0i32;
1964
1965        // Backward stepping (same as AA1 but uses DI3)
1966        let mut npix = 1i32;
1967
1968        if lp.vertical {
1969            loop {
1970                li.dec();
1971                y -= lp.inc;
1972                x = (lp.x1 + li.y()) >> LINE_SUBPIXEL_SHIFT;
1973
1974                if lp.inc > 0 {
1975                    di.dec_y(x - old_x);
1976                } else {
1977                    di.inc_y(x - old_x);
1978                }
1979                old_x = x;
1980
1981                let mut dist1_start = di.dist_start();
1982                let mut dist2_start = dist1_start;
1983
1984                let mut dx = 0;
1985                if dist1_start < 0 { npix += 1; }
1986                loop {
1987                    dist1_start += di.dy_start();
1988                    dist2_start -= di.dy_start();
1989                    if dist1_start < 0 { npix += 1; }
1990                    if dist2_start < 0 { npix += 1; }
1991                    dx += 1;
1992                    if dist[dx as usize] > subpixel_width { break; }
1993                }
1994                if npix == 0 { break; }
1995                npix = 0;
1996                step -= 1;
1997                if step < -max_extent { break; }
1998            }
1999        } else {
2000            loop {
2001                li.dec();
2002                x -= lp.inc;
2003                y = (lp.y1 + li.y()) >> LINE_SUBPIXEL_SHIFT;
2004
2005                if lp.inc > 0 {
2006                    di.dec_x(y - old_y);
2007                } else {
2008                    di.inc_x(y - old_y);
2009                }
2010                old_y = y;
2011
2012                let mut dist1_start = di.dist_start();
2013                let mut dist2_start = dist1_start;
2014
2015                let mut dy = 0;
2016                if dist1_start < 0 { npix += 1; }
2017                loop {
2018                    dist1_start -= di.dx_start();
2019                    dist2_start += di.dx_start();
2020                    if dist1_start < 0 { npix += 1; }
2021                    if dist2_start < 0 { npix += 1; }
2022                    dy += 1;
2023                    if dist[dy as usize] > subpixel_width { break; }
2024                }
2025                if npix == 0 { break; }
2026                npix = 0;
2027                step -= 1;
2028                if step < -max_extent { break; }
2029            }
2030        }
2031
2032        li.adjust_forward();
2033        step -= max_extent;
2034
2035        Self {
2036            di, li, x, y, old_x, old_y,
2037            count, width: subpixel_width, max_extent, len, step,
2038            dist, covers: [0u8; COVER_SIZE],
2039        }
2040    }
2041
2042    fn x(&self) -> i32 { self.x }
2043    fn y(&self) -> i32 { self.y }
2044
2045    fn step_hor(&mut self, profile: &LineProfileAa, lp: &LineParameters) -> Option<LineSpan> {
2046        if self.step >= self.count { return None; }
2047
2048        self.li.inc();
2049        self.x += lp.inc;
2050        self.y = (lp.y1 + self.li.y()) >> LINE_SUBPIXEL_SHIFT;
2051        if lp.inc > 0 { self.di.inc_x(self.y - self.old_y); }
2052        else { self.di.dec_x(self.y - self.old_y); }
2053        self.old_y = self.y;
2054
2055        let s1 = self.di.dist() / self.len;
2056        let mut dist_start = self.di.dist_start();
2057        let mut dist_end = self.di.dist_end();
2058
2059        let center = MAX_HALF_WIDTH + 2;
2060        let mut p0 = center;
2061        let mut p1 = center;
2062
2063        let mut npix = 0;
2064        self.covers[p1] = 0;
2065        if dist_end > 0 {
2066            if dist_start <= 0 {
2067                self.covers[p1] = profile.value(s1) as u8;
2068            }
2069            npix += 1;
2070        }
2071        p1 += 1;
2072
2073        let mut dy = 1usize;
2074        loop {
2075            if dy >= DIST_SIZE { break; }
2076            let dist = self.dist[dy] - s1;
2077            if dist > self.width { break; }
2078            dist_start -= self.di.dx_start();
2079            dist_end -= self.di.dx_end();
2080            self.covers[p1] = 0;
2081            if dist_end > 0 && dist_start <= 0 {
2082                self.covers[p1] = profile.value(dist) as u8;
2083                npix += 1;
2084            }
2085            p1 += 1;
2086            dy += 1;
2087        }
2088
2089        let mut dy = 1usize;
2090        dist_start = self.di.dist_start();
2091        dist_end = self.di.dist_end();
2092        loop {
2093            if dy >= DIST_SIZE { break; }
2094            let dist = self.dist[dy] + s1;
2095            if dist > self.width { break; }
2096            dist_start += self.di.dx_start();
2097            dist_end += self.di.dx_end();
2098            p0 -= 1;
2099            self.covers[p0] = 0;
2100            if dist_end > 0 && dist_start <= 0 {
2101                self.covers[p0] = profile.value(dist) as u8;
2102                npix += 1;
2103            }
2104            dy += 1;
2105        }
2106
2107        self.step += 1;
2108        if npix == 0 { return None; }
2109
2110        Some(LineSpan { p0, len: p1 - p0, offset: dy as i32 })
2111    }
2112
2113    fn step_ver(&mut self, profile: &LineProfileAa, lp: &LineParameters) -> Option<LineSpan> {
2114        if self.step >= self.count { return None; }
2115
2116        self.li.inc();
2117        self.y += lp.inc;
2118        self.x = (lp.x1 + self.li.y()) >> LINE_SUBPIXEL_SHIFT;
2119        if lp.inc > 0 { self.di.inc_y(self.x - self.old_x); }
2120        else { self.di.dec_y(self.x - self.old_x); }
2121        self.old_x = self.x;
2122
2123        let s1 = self.di.dist() / self.len;
2124        let mut dist_start = self.di.dist_start();
2125        let mut dist_end = self.di.dist_end();
2126
2127        let center = MAX_HALF_WIDTH + 2;
2128        let mut p0 = center;
2129        let mut p1 = center;
2130
2131        let mut npix = 0;
2132        self.covers[p1] = 0;
2133        if dist_end > 0 {
2134            if dist_start <= 0 {
2135                self.covers[p1] = profile.value(s1) as u8;
2136            }
2137            npix += 1;
2138        }
2139        p1 += 1;
2140
2141        let mut dx = 1usize;
2142        loop {
2143            if dx >= DIST_SIZE { break; }
2144            let dist = self.dist[dx] - s1;
2145            if dist > self.width { break; }
2146            dist_start += self.di.dy_start();
2147            dist_end += self.di.dy_end();
2148            self.covers[p1] = 0;
2149            if dist_end > 0 && dist_start <= 0 {
2150                self.covers[p1] = profile.value(dist) as u8;
2151                npix += 1;
2152            }
2153            p1 += 1;
2154            dx += 1;
2155        }
2156
2157        let mut dx = 1usize;
2158        dist_start = self.di.dist_start();
2159        dist_end = self.di.dist_end();
2160        loop {
2161            if dx >= DIST_SIZE { break; }
2162            let dist = self.dist[dx] + s1;
2163            if dist > self.width { break; }
2164            dist_start -= self.di.dy_start();
2165            dist_end -= self.di.dy_end();
2166            p0 -= 1;
2167            self.covers[p0] = 0;
2168            if dist_end > 0 && dist_start <= 0 {
2169                self.covers[p0] = profile.value(dist) as u8;
2170                npix += 1;
2171            }
2172            dx += 1;
2173        }
2174
2175        self.step += 1;
2176        if npix == 0 { return None; }
2177
2178        Some(LineSpan { p0, len: p1 - p0, offset: dx as i32 })
2179    }
2180}
2181
2182// ============================================================================
2183// Tests
2184// ============================================================================
2185
2186#[cfg(test)]
2187mod tests {
2188    use super::*;
2189    use crate::color::Rgba8;
2190    use crate::pixfmt_rgba::PixfmtRgba32;
2191    use crate::rendering_buffer::RowAccessor;
2192
2193    fn make_buffer(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
2194        let stride = (w * 4) as i32;
2195        let buf = vec![0u8; (h * w * 4) as usize];
2196        let mut ra = RowAccessor::new();
2197        unsafe {
2198            ra.attach(buf.as_ptr() as *mut u8, w, h, stride);
2199        }
2200        (buf, ra)
2201    }
2202
2203    #[test]
2204    fn test_line_profile_creation() {
2205        let p = LineProfileAa::with_width(2.0);
2206        assert!(p.subpixel_width() > 0);
2207        assert!(p.profile_size() > 0);
2208    }
2209
2210    #[test]
2211    fn test_line_profile_value_center() {
2212        let p = LineProfileAa::with_width(3.0);
2213        // Center should have high coverage
2214        let center = p.value(0);
2215        assert!(center > 200, "center coverage={center} should be > 200");
2216    }
2217
2218    #[test]
2219    fn test_line_profile_value_edge() {
2220        let p = LineProfileAa::with_width(3.0);
2221        // Far from center should have zero coverage.
2222        // Width=3.0 → half-width in subpixel is ~512, so dist=800 should be zero.
2223        let far = p.value(800);
2224        assert_eq!(far, 0);
2225    }
2226
2227    #[test]
2228    fn test_distance_interpolator1() {
2229        let di = DistanceInterpolator1::new(0, 0, 256, 0, 128, 128);
2230        // Distance from (128,128) to line (0,0)-(256,0) should be negative
2231        // (below the line) since line goes right and point is below
2232        assert_ne!(di.dist(), 0);
2233    }
2234
2235    #[test]
2236    fn test_render_line0() {
2237        let (_buf, mut ra) = make_buffer(100, 100);
2238        let pixf = PixfmtRgba32::new(&mut ra);
2239        let mut ren = RendererBase::new(pixf);
2240        let prof = LineProfileAa::with_width(2.0);
2241        let mut ren_aa = RendererOutlineAa::new(&mut ren, &prof);
2242        ren_aa.set_color(Rgba8::new(255, 0, 0, 255));
2243
2244        // Draw a horizontal line
2245        let lp = LineParameters::new(
2246            10 * 256, 50 * 256,
2247            90 * 256, 50 * 256,
2248            80 * 256,
2249        );
2250        ren_aa.line0(&lp);
2251
2252        // Check that some pixels were drawn somewhere near the line
2253        let mut found = false;
2254        for y in 48..=52 {
2255            for x in 0..100 {
2256                let p = ren_aa.ren().pixel(x, y);
2257                if p.r > 0 {
2258                    found = true;
2259                    break;
2260                }
2261            }
2262            if found { break; }
2263        }
2264        assert!(found, "Expected red pixels near row 50");
2265    }
2266
2267    #[test]
2268    fn test_render_line_diagonal() {
2269        let (_buf, mut ra) = make_buffer(100, 100);
2270        let pixf = PixfmtRgba32::new(&mut ra);
2271        let mut ren = RendererBase::new(pixf);
2272        let prof = LineProfileAa::with_width(1.5);
2273        let mut ren_aa = RendererOutlineAa::new(&mut ren, &prof);
2274        ren_aa.set_color(Rgba8::new(0, 255, 0, 255));
2275
2276        let lp = LineParameters::new(
2277            10 * 256, 10 * 256,
2278            90 * 256, 90 * 256,
2279            uround(calc_distance_i(10 * 256, 10 * 256, 90 * 256, 90 * 256)),
2280        );
2281        ren_aa.line0(&lp);
2282
2283        let p = ren_aa.ren().pixel(50, 50);
2284        assert!(p.g > 0, "Expected green pixel at (50,50), got g={}", p.g);
2285    }
2286}