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