Skip to main content

agg_rust/
basics.rs

1//! Foundation types, constants, and path command utilities.
2//!
3//! Port of `agg_basics.h` — the most fundamental types in AGG that
4//! everything else depends on.
5
6use core::ops::{Add, Sub};
7
8// ============================================================================
9// Rounding and conversion functions
10// ============================================================================
11
12/// Round a double to the nearest integer (round half away from zero).
13/// Matches C++ AGG's `iround` (non-FISTP, non-QIFIST path).
14#[inline]
15pub fn iround(v: f64) -> i32 {
16    if v < 0.0 {
17        (v - 0.5) as i32
18    } else {
19        (v + 0.5) as i32
20    }
21}
22
23/// Round a double to the nearest unsigned integer (round half up).
24/// Matches C++ AGG's `uround`.
25#[inline]
26pub fn uround(v: f64) -> u32 {
27    (v + 0.5) as u32
28}
29
30/// Floor a double to the nearest integer toward negative infinity.
31/// Matches C++ AGG's `ifloor`.
32#[inline]
33pub fn ifloor(v: f64) -> i32 {
34    let i = v as i32;
35    i - (i as f64 > v) as i32
36}
37
38/// Floor a double to the nearest unsigned integer (truncation toward zero).
39/// Matches C++ AGG's `ufloor`.
40#[inline]
41pub fn ufloor(v: f64) -> u32 {
42    v as u32
43}
44
45/// Ceiling of a double as a signed integer.
46/// Matches C++ AGG's `iceil`.
47#[inline]
48pub fn iceil(v: f64) -> i32 {
49    v.ceil() as i32
50}
51
52/// Ceiling of a double as an unsigned integer.
53/// Matches C++ AGG's `uceil`.
54#[inline]
55pub fn uceil(v: f64) -> u32 {
56    v.ceil() as u32
57}
58
59// ============================================================================
60// Saturation and fixed-point multiply
61// ============================================================================
62
63/// Round `v` to int, clamping to `[-limit, limit]`.
64/// Replaces C++ template `saturation<Limit>::iround`.
65#[inline]
66pub fn saturation_iround(limit: i32, v: f64) -> i32 {
67    if v < -(limit as f64) {
68        return -limit;
69    }
70    if v > limit as f64 {
71        return limit;
72    }
73    iround(v)
74}
75
76/// Fixed-point multiply: `(a * b + half) >> shift`, with rounding.
77/// Replaces C++ template `mul_one<Shift>::mul`.
78#[inline]
79pub fn mul_one(a: u32, b: u32, shift: u32) -> u32 {
80    let q = a * b + (1 << (shift - 1));
81    (q + (q >> shift)) >> shift
82}
83
84// ============================================================================
85// Cover (anti-aliasing) constants
86// ============================================================================
87
88/// The type used for anti-aliasing coverage values.
89pub type CoverType = u8;
90
91pub const COVER_SHIFT: u32 = 8;
92pub const COVER_SIZE: u32 = 1 << COVER_SHIFT;
93pub const COVER_MASK: u32 = COVER_SIZE - 1;
94pub const COVER_NONE: CoverType = 0;
95pub const COVER_FULL: CoverType = COVER_MASK as CoverType;
96
97// ============================================================================
98// Subpixel constants
99// ============================================================================
100
101/// These constants determine the subpixel accuracy (number of fractional bits).
102/// With 8-bit fractional part and 32-bit integers, coordinate capacity is 24 bits.
103pub const POLY_SUBPIXEL_SHIFT: u32 = 8;
104pub const POLY_SUBPIXEL_SCALE: u32 = 1 << POLY_SUBPIXEL_SHIFT;
105pub const POLY_SUBPIXEL_MASK: u32 = POLY_SUBPIXEL_SCALE - 1;
106
107// ============================================================================
108// Filling rule
109// ============================================================================
110
111/// Filling rule for polygon rasterization.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum FillingRule {
114    NonZero,
115    EvenOdd,
116}
117
118// ============================================================================
119// Mathematical constants
120// ============================================================================
121
122pub const PI: f64 = std::f64::consts::PI;
123
124/// Convert degrees to radians.
125#[inline]
126pub fn deg2rad(deg: f64) -> f64 {
127    deg * PI / 180.0
128}
129
130/// Convert radians to degrees.
131#[inline]
132pub fn rad2deg(rad: f64) -> f64 {
133    rad * 180.0 / PI
134}
135
136// ============================================================================
137// Rect
138// ============================================================================
139
140/// A rectangle defined by two corner points.
141/// Port of C++ `rect_base<T>`.
142#[derive(Debug, Clone, Copy, PartialEq)]
143pub struct Rect<T: Copy> {
144    pub x1: T,
145    pub y1: T,
146    pub x2: T,
147    pub y2: T,
148}
149
150impl<T: Copy + PartialOrd> Rect<T> {
151    pub fn new(x1: T, y1: T, x2: T, y2: T) -> Self {
152        Self { x1, y1, x2, y2 }
153    }
154
155    pub fn init(&mut self, x1: T, y1: T, x2: T, y2: T) {
156        self.x1 = x1;
157        self.y1 = y1;
158        self.x2 = x2;
159        self.y2 = y2;
160    }
161
162    /// Normalize so that x1 <= x2 and y1 <= y2, swapping if needed.
163    pub fn normalize(&mut self) -> &Self {
164        if self.x1 > self.x2 {
165            core::mem::swap(&mut self.x1, &mut self.x2);
166        }
167        if self.y1 > self.y2 {
168            core::mem::swap(&mut self.y1, &mut self.y2);
169        }
170        self
171    }
172
173    /// Clip this rectangle to the intersection with `r`.
174    /// Returns `true` if the result is a valid (non-empty) rectangle.
175    pub fn clip(&mut self, r: &Self) -> bool {
176        if self.x2 > r.x2 {
177            self.x2 = r.x2;
178        }
179        if self.y2 > r.y2 {
180            self.y2 = r.y2;
181        }
182        if self.x1 < r.x1 {
183            self.x1 = r.x1;
184        }
185        if self.y1 < r.y1 {
186            self.y1 = r.y1;
187        }
188        self.x1 <= self.x2 && self.y1 <= self.y2
189    }
190
191    /// Returns `true` if the rectangle is valid (non-empty).
192    pub fn is_valid(&self) -> bool {
193        self.x1 <= self.x2 && self.y1 <= self.y2
194    }
195
196    /// Returns `true` if the point (x, y) is inside the rectangle.
197    pub fn hit_test(&self, x: T, y: T) -> bool {
198        x >= self.x1 && x <= self.x2 && y >= self.y1 && y <= self.y2
199    }
200
201    /// Returns `true` if this rectangle overlaps with `r`.
202    pub fn overlaps(&self, r: &Self) -> bool {
203        !(r.x1 > self.x2 || r.x2 < self.x1 || r.y1 > self.y2 || r.y2 < self.y1)
204    }
205}
206
207/// Compute the intersection of two rectangles.
208pub fn intersect_rectangles<T: Copy + PartialOrd>(r1: &Rect<T>, r2: &Rect<T>) -> Rect<T> {
209    let mut r = *r1;
210    // Process x2,y2 first (matches C++ order for MSVC compatibility comment)
211    if r.x2 > r2.x2 {
212        r.x2 = r2.x2;
213    }
214    if r.y2 > r2.y2 {
215        r.y2 = r2.y2;
216    }
217    if r.x1 < r2.x1 {
218        r.x1 = r2.x1;
219    }
220    if r.y1 < r2.y1 {
221        r.y1 = r2.y1;
222    }
223    r
224}
225
226/// Compute the union (bounding box) of two rectangles.
227pub fn unite_rectangles<T: Copy + PartialOrd>(r1: &Rect<T>, r2: &Rect<T>) -> Rect<T> {
228    let mut r = *r1;
229    if r.x2 < r2.x2 {
230        r.x2 = r2.x2;
231    }
232    if r.y2 < r2.y2 {
233        r.y2 = r2.y2;
234    }
235    if r.x1 > r2.x1 {
236        r.x1 = r2.x1;
237    }
238    if r.y1 > r2.y1 {
239        r.y1 = r2.y1;
240    }
241    r
242}
243
244/// Rectangle with `i32` coordinates.
245pub type RectI = Rect<i32>;
246/// Rectangle with `f32` coordinates.
247pub type RectF = Rect<f32>;
248/// Rectangle with `f64` coordinates.
249pub type RectD = Rect<f64>;
250
251// ============================================================================
252// Path commands
253// ============================================================================
254
255pub const PATH_CMD_STOP: u32 = 0;
256pub const PATH_CMD_MOVE_TO: u32 = 1;
257pub const PATH_CMD_LINE_TO: u32 = 2;
258pub const PATH_CMD_CURVE3: u32 = 3;
259pub const PATH_CMD_CURVE4: u32 = 4;
260pub const PATH_CMD_CURVE_N: u32 = 5;
261pub const PATH_CMD_CATROM: u32 = 6;
262pub const PATH_CMD_UBSPLINE: u32 = 7;
263pub const PATH_CMD_END_POLY: u32 = 0x0F;
264pub const PATH_CMD_MASK: u32 = 0x0F;
265
266// ============================================================================
267// Path flags
268// ============================================================================
269
270pub const PATH_FLAGS_NONE: u32 = 0;
271pub const PATH_FLAGS_CCW: u32 = 0x10;
272pub const PATH_FLAGS_CW: u32 = 0x20;
273pub const PATH_FLAGS_CLOSE: u32 = 0x40;
274pub const PATH_FLAGS_MASK: u32 = 0xF0;
275
276// ============================================================================
277// Path command query functions
278// ============================================================================
279
280/// Returns `true` if `c` is a vertex command (move_to through curveN).
281#[inline]
282pub fn is_vertex(c: u32) -> bool {
283    (PATH_CMD_MOVE_TO..PATH_CMD_END_POLY).contains(&c)
284}
285
286/// Returns `true` if `c` is a drawing command (line_to through curveN).
287#[inline]
288pub fn is_drawing(c: u32) -> bool {
289    (PATH_CMD_LINE_TO..PATH_CMD_END_POLY).contains(&c)
290}
291
292/// Returns `true` if `c` is the stop command.
293#[inline]
294pub fn is_stop(c: u32) -> bool {
295    c == PATH_CMD_STOP
296}
297
298/// Returns `true` if `c` is a move_to command.
299#[inline]
300pub fn is_move_to(c: u32) -> bool {
301    c == PATH_CMD_MOVE_TO
302}
303
304/// Returns `true` if `c` is a line_to command.
305#[inline]
306pub fn is_line_to(c: u32) -> bool {
307    c == PATH_CMD_LINE_TO
308}
309
310/// Returns `true` if `c` is a curve command (curve3 or curve4).
311#[inline]
312pub fn is_curve(c: u32) -> bool {
313    c == PATH_CMD_CURVE3 || c == PATH_CMD_CURVE4
314}
315
316/// Returns `true` if `c` is a quadratic curve command.
317#[inline]
318pub fn is_curve3(c: u32) -> bool {
319    c == PATH_CMD_CURVE3
320}
321
322/// Returns `true` if `c` is a cubic curve command.
323#[inline]
324pub fn is_curve4(c: u32) -> bool {
325    c == PATH_CMD_CURVE4
326}
327
328/// Returns `true` if `c` is an end_poly command (with any flags).
329#[inline]
330pub fn is_end_poly(c: u32) -> bool {
331    (c & PATH_CMD_MASK) == PATH_CMD_END_POLY
332}
333
334/// Returns `true` if `c` is a close polygon command.
335#[inline]
336pub fn is_close(c: u32) -> bool {
337    (c & !(PATH_FLAGS_CW | PATH_FLAGS_CCW)) == (PATH_CMD_END_POLY | PATH_FLAGS_CLOSE)
338}
339
340/// Returns `true` if `c` starts a new polygon (stop, move_to, or end_poly).
341#[inline]
342pub fn is_next_poly(c: u32) -> bool {
343    is_stop(c) || is_move_to(c) || is_end_poly(c)
344}
345
346/// Returns `true` if `c` has the clockwise flag set.
347#[inline]
348pub fn is_cw(c: u32) -> bool {
349    (c & PATH_FLAGS_CW) != 0
350}
351
352/// Returns `true` if `c` has the counter-clockwise flag set.
353#[inline]
354pub fn is_ccw(c: u32) -> bool {
355    (c & PATH_FLAGS_CCW) != 0
356}
357
358/// Returns `true` if `c` has any orientation flag set.
359#[inline]
360pub fn is_oriented(c: u32) -> bool {
361    (c & (PATH_FLAGS_CW | PATH_FLAGS_CCW)) != 0
362}
363
364/// Returns `true` if `c` has the close flag set.
365#[inline]
366pub fn is_closed(c: u32) -> bool {
367    (c & PATH_FLAGS_CLOSE) != 0
368}
369
370/// Extract the close flag from a command.
371#[inline]
372pub fn get_close_flag(c: u32) -> u32 {
373    c & PATH_FLAGS_CLOSE
374}
375
376/// Remove orientation flags from a command.
377#[inline]
378pub fn clear_orientation(c: u32) -> u32 {
379    c & !(PATH_FLAGS_CW | PATH_FLAGS_CCW)
380}
381
382/// Extract the orientation flags from a command.
383#[inline]
384pub fn get_orientation(c: u32) -> u32 {
385    c & (PATH_FLAGS_CW | PATH_FLAGS_CCW)
386}
387
388/// Set the orientation flags on a command.
389#[inline]
390pub fn set_orientation(c: u32, o: u32) -> u32 {
391    clear_orientation(c) | o
392}
393
394// ============================================================================
395// Point
396// ============================================================================
397
398/// A 2D point.
399/// Port of C++ `point_base<T>`.
400#[derive(Debug, Clone, Copy, PartialEq, Default)]
401pub struct PointBase<T: Copy> {
402    pub x: T,
403    pub y: T,
404}
405
406impl<T: Copy> PointBase<T> {
407    pub fn new(x: T, y: T) -> Self {
408        Self { x, y }
409    }
410}
411
412pub type PointI = PointBase<i32>;
413pub type PointF = PointBase<f32>;
414pub type PointD = PointBase<f64>;
415
416// ============================================================================
417// Vertex
418// ============================================================================
419
420/// A vertex with coordinates and a path command.
421/// Port of C++ `vertex_base<T>`.
422#[derive(Debug, Clone, Copy, PartialEq)]
423pub struct VertexBase<T: Copy> {
424    pub x: T,
425    pub y: T,
426    pub cmd: u32,
427}
428
429impl<T: Copy> VertexBase<T> {
430    pub fn new(x: T, y: T, cmd: u32) -> Self {
431        Self { x, y, cmd }
432    }
433}
434
435pub type VertexI = VertexBase<i32>;
436pub type VertexF = VertexBase<f32>;
437pub type VertexD = VertexBase<f64>;
438
439// ============================================================================
440// Row info (for rendering buffer row access)
441// ============================================================================
442
443/// Information about a row in a rendering buffer.
444/// Port of C++ `row_info<T>` — used with raw pointers for pixel buffer access.
445#[derive(Debug, Clone, Copy)]
446pub struct RowInfo<T> {
447    pub x1: i32,
448    pub x2: i32,
449    pub ptr: *mut T,
450}
451
452impl<T> RowInfo<T> {
453    pub fn new(x1: i32, x2: i32, ptr: *mut T) -> Self {
454        Self { x1, x2, ptr }
455    }
456}
457
458/// Const version of row info.
459#[derive(Debug, Clone, Copy)]
460pub struct ConstRowInfo<T> {
461    pub x1: i32,
462    pub x2: i32,
463    pub ptr: *const T,
464}
465
466impl<T> ConstRowInfo<T> {
467    pub fn new(x1: i32, x2: i32, ptr: *const T) -> Self {
468        Self { x1, x2, ptr }
469    }
470}
471
472// ============================================================================
473// Approximate equality comparison
474// ============================================================================
475
476/// Compare two floating-point values for approximate equality using
477/// relative comparison scaled by the smaller exponent.
478/// Port of C++ `is_equal_eps`.
479pub fn is_equal_eps<T>(v1: T, v2: T, epsilon: T) -> bool
480where
481    T: Copy + PartialOrd + Sub<Output = T> + Add<Output = T> + Into<f64> + From<f64>,
482{
483    let v1_f: f64 = v1.into();
484    let v2_f: f64 = v2.into();
485    let eps_f: f64 = epsilon.into();
486
487    let neg1 = v1_f < 0.0;
488    let neg2 = v2_f < 0.0;
489
490    if neg1 != neg2 {
491        return v1_f.abs() < eps_f && v2_f.abs() < eps_f;
492    }
493
494    let (_, exp1) = frexp(v1_f);
495    let (_, exp2) = frexp(v2_f);
496    let min_exp = exp1.min(exp2);
497
498    let scaled1 = ldexp(v1_f, -min_exp);
499    let scaled2 = ldexp(v2_f, -min_exp);
500
501    (scaled1 - scaled2).abs() < eps_f
502}
503
504/// C-style frexp: decompose `x` into `(mantissa, exponent)` where
505/// `x = mantissa * 2^exponent` and `0.5 <= |mantissa| < 1.0`.
506#[inline]
507fn frexp(x: f64) -> (f64, i32) {
508    if x == 0.0 {
509        return (0.0, 0);
510    }
511    let bits = x.to_bits();
512    let exp = ((bits >> 52) & 0x7FF) as i32 - 1022;
513    let mantissa = f64::from_bits((bits & 0x800F_FFFF_FFFF_FFFF) | 0x3FE0_0000_0000_0000);
514    (mantissa, exp)
515}
516
517/// C-style ldexp: compute `x * 2^exp`.
518#[inline]
519fn ldexp(x: f64, exp: i32) -> f64 {
520    x * (2.0_f64).powi(exp)
521}
522
523// ============================================================================
524// VertexSource trait
525// ============================================================================
526
527/// The fundamental vertex source interface. Every shape, path, and converter
528/// in AGG implements this trait to produce a stream of vertices.
529///
530/// Port of the C++ "vertex source concept" — the implicit interface that
531/// all AGG vertex sources implement via duck typing (template parameters).
532pub trait VertexSource {
533    /// Reset the vertex source to the beginning of the given path.
534    /// `path_id` selects which sub-path to iterate (0 for the first/only path).
535    fn rewind(&mut self, path_id: u32);
536
537    /// Return the next vertex. Writes coordinates to `x` and `y`, returns a
538    /// path command. Returns `PATH_CMD_STOP` when iteration is complete.
539    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32;
540}
541
542/// Blanket implementation so `&mut T` can be used as a VertexSource.
543/// This allows pipeline stages to borrow their source instead of owning it.
544impl<T: VertexSource> VertexSource for &mut T {
545    fn rewind(&mut self, path_id: u32) {
546        (*self).rewind(path_id);
547    }
548
549    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
550        (*self).vertex(x, y)
551    }
552}
553
554// ============================================================================
555// Tests
556// ============================================================================
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561
562    #[test]
563    fn test_iround() {
564        assert_eq!(iround(0.5), 1);
565        assert_eq!(iround(0.49), 0);
566        assert_eq!(iround(-0.5), -1);
567        assert_eq!(iround(-0.49), 0);
568        assert_eq!(iround(1.5), 2);
569        assert_eq!(iround(-1.5), -2);
570        assert_eq!(iround(0.0), 0);
571    }
572
573    #[test]
574    fn test_uround() {
575        assert_eq!(uround(0.5), 1);
576        assert_eq!(uround(0.49), 0);
577        assert_eq!(uround(1.5), 2);
578        assert_eq!(uround(0.0), 0);
579    }
580
581    #[test]
582    fn test_ifloor() {
583        assert_eq!(ifloor(1.7), 1);
584        assert_eq!(ifloor(1.0), 1);
585        assert_eq!(ifloor(-1.7), -2);
586        assert_eq!(ifloor(-1.0), -1);
587        assert_eq!(ifloor(0.0), 0);
588    }
589
590    #[test]
591    fn test_ufloor() {
592        assert_eq!(ufloor(1.7), 1);
593        assert_eq!(ufloor(1.0), 1);
594        assert_eq!(ufloor(0.0), 0);
595    }
596
597    #[test]
598    fn test_iceil() {
599        assert_eq!(iceil(1.1), 2);
600        assert_eq!(iceil(1.0), 1);
601        assert_eq!(iceil(-1.1), -1);
602        assert_eq!(iceil(0.0), 0);
603    }
604
605    #[test]
606    fn test_uceil() {
607        assert_eq!(uceil(1.1), 2);
608        assert_eq!(uceil(1.0), 1);
609        assert_eq!(uceil(0.0), 0);
610    }
611
612    #[test]
613    fn test_saturation_iround() {
614        assert_eq!(saturation_iround(128, 200.0), 128);
615        assert_eq!(saturation_iround(128, -200.0), -128);
616        assert_eq!(saturation_iround(128, 50.7), 51);
617    }
618
619    #[test]
620    fn test_mul_one_shift8() {
621        // mul_one with shift=8: (a * b + 128) >> 8, with rounding correction
622        // For a=255, b=255: should give 255
623        assert_eq!(mul_one(255, 255, 8), 255);
624        // For a=128, b=255: should give 128
625        assert_eq!(mul_one(128, 255, 8), 128);
626        // For a=0, b=255: should give 0
627        assert_eq!(mul_one(0, 255, 8), 0);
628    }
629
630    #[test]
631    fn test_cover_constants() {
632        assert_eq!(COVER_SHIFT, 8);
633        assert_eq!(COVER_SIZE, 256);
634        assert_eq!(COVER_MASK, 255);
635        assert_eq!(COVER_NONE, 0);
636        assert_eq!(COVER_FULL, 255);
637    }
638
639    #[test]
640    fn test_poly_subpixel_constants() {
641        assert_eq!(POLY_SUBPIXEL_SHIFT, 8);
642        assert_eq!(POLY_SUBPIXEL_SCALE, 256);
643        assert_eq!(POLY_SUBPIXEL_MASK, 255);
644    }
645
646    #[test]
647    fn test_deg2rad_rad2deg() {
648        let epsilon = 1e-10;
649        assert!((deg2rad(180.0) - PI).abs() < epsilon);
650        assert!((rad2deg(PI) - 180.0).abs() < epsilon);
651        assert!((deg2rad(90.0) - PI / 2.0).abs() < epsilon);
652        assert!((deg2rad(0.0)).abs() < epsilon);
653    }
654
655    #[test]
656    fn test_rect_new_and_is_valid() {
657        let r = RectI::new(10, 20, 30, 40);
658        assert!(r.is_valid());
659        assert_eq!(r.x1, 10);
660        assert_eq!(r.y1, 20);
661        assert_eq!(r.x2, 30);
662        assert_eq!(r.y2, 40);
663
664        let r_invalid = RectI::new(30, 40, 10, 20);
665        assert!(!r_invalid.is_valid());
666    }
667
668    #[test]
669    fn test_rect_normalize() {
670        let mut r = RectI::new(30, 40, 10, 20);
671        r.normalize();
672        assert_eq!(r.x1, 10);
673        assert_eq!(r.y1, 20);
674        assert_eq!(r.x2, 30);
675        assert_eq!(r.y2, 40);
676    }
677
678    #[test]
679    fn test_rect_clip() {
680        let mut r = RectI::new(10, 20, 100, 200);
681        let clip = RectI::new(50, 50, 80, 80);
682        let valid = r.clip(&clip);
683        assert!(valid);
684        assert_eq!(r.x1, 50);
685        assert_eq!(r.y1, 50);
686        assert_eq!(r.x2, 80);
687        assert_eq!(r.y2, 80);
688    }
689
690    #[test]
691    fn test_rect_hit_test() {
692        let r = RectI::new(10, 20, 30, 40);
693        assert!(r.hit_test(15, 25));
694        assert!(r.hit_test(10, 20));
695        assert!(r.hit_test(30, 40));
696        assert!(!r.hit_test(5, 25));
697        assert!(!r.hit_test(15, 45));
698    }
699
700    #[test]
701    fn test_rect_overlaps() {
702        let r1 = RectI::new(10, 20, 30, 40);
703        let r2 = RectI::new(25, 35, 50, 60);
704        assert!(r1.overlaps(&r2));
705        assert!(r2.overlaps(&r1));
706
707        let r3 = RectI::new(31, 41, 50, 60);
708        assert!(!r1.overlaps(&r3));
709    }
710
711    #[test]
712    fn test_intersect_rectangles() {
713        let r1 = RectI::new(10, 20, 100, 200);
714        let r2 = RectI::new(50, 50, 80, 80);
715        let r = intersect_rectangles(&r1, &r2);
716        assert_eq!(r.x1, 50);
717        assert_eq!(r.y1, 50);
718        assert_eq!(r.x2, 80);
719        assert_eq!(r.y2, 80);
720    }
721
722    #[test]
723    fn test_unite_rectangles() {
724        let r1 = RectI::new(10, 20, 30, 40);
725        let r2 = RectI::new(50, 60, 70, 80);
726        let r = unite_rectangles(&r1, &r2);
727        assert_eq!(r.x1, 10);
728        assert_eq!(r.y1, 20);
729        assert_eq!(r.x2, 70);
730        assert_eq!(r.y2, 80);
731    }
732
733    #[test]
734    fn test_path_command_classification() {
735        assert!(is_stop(PATH_CMD_STOP));
736        assert!(!is_stop(PATH_CMD_MOVE_TO));
737
738        assert!(is_move_to(PATH_CMD_MOVE_TO));
739        assert!(is_line_to(PATH_CMD_LINE_TO));
740
741        assert!(is_vertex(PATH_CMD_MOVE_TO));
742        assert!(is_vertex(PATH_CMD_LINE_TO));
743        assert!(is_vertex(PATH_CMD_CURVE3));
744        assert!(is_vertex(PATH_CMD_CURVE4));
745        assert!(!is_vertex(PATH_CMD_STOP));
746        assert!(!is_vertex(PATH_CMD_END_POLY));
747
748        assert!(is_drawing(PATH_CMD_LINE_TO));
749        assert!(is_drawing(PATH_CMD_CURVE3));
750        assert!(!is_drawing(PATH_CMD_MOVE_TO));
751        assert!(!is_drawing(PATH_CMD_END_POLY));
752
753        assert!(is_curve(PATH_CMD_CURVE3));
754        assert!(is_curve(PATH_CMD_CURVE4));
755        assert!(!is_curve(PATH_CMD_LINE_TO));
756
757        assert!(is_curve3(PATH_CMD_CURVE3));
758        assert!(!is_curve3(PATH_CMD_CURVE4));
759
760        assert!(is_curve4(PATH_CMD_CURVE4));
761        assert!(!is_curve4(PATH_CMD_CURVE3));
762    }
763
764    #[test]
765    fn test_path_end_poly_and_close() {
766        assert!(is_end_poly(PATH_CMD_END_POLY));
767        assert!(is_end_poly(PATH_CMD_END_POLY | PATH_FLAGS_CLOSE));
768        assert!(is_end_poly(PATH_CMD_END_POLY | PATH_FLAGS_CW));
769        assert!(!is_end_poly(PATH_CMD_LINE_TO));
770
771        assert!(is_close(PATH_CMD_END_POLY | PATH_FLAGS_CLOSE));
772        assert!(!is_close(PATH_CMD_END_POLY));
773        // Close with orientation should still be close
774        assert!(is_close(
775            PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CW
776        ));
777    }
778
779    #[test]
780    fn test_path_flags() {
781        let cmd = PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CW;
782        assert!(is_cw(cmd));
783        assert!(!is_ccw(cmd));
784        assert!(is_oriented(cmd));
785        assert!(is_closed(cmd));
786
787        assert_eq!(get_close_flag(cmd), PATH_FLAGS_CLOSE);
788        assert_eq!(get_orientation(cmd), PATH_FLAGS_CW);
789        assert_eq!(clear_orientation(cmd), PATH_CMD_END_POLY | PATH_FLAGS_CLOSE);
790        assert_eq!(
791            set_orientation(cmd, PATH_FLAGS_CCW),
792            PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CCW
793        );
794    }
795
796    #[test]
797    fn test_is_next_poly() {
798        assert!(is_next_poly(PATH_CMD_STOP));
799        assert!(is_next_poly(PATH_CMD_MOVE_TO));
800        assert!(is_next_poly(PATH_CMD_END_POLY));
801        assert!(!is_next_poly(PATH_CMD_LINE_TO));
802        assert!(!is_next_poly(PATH_CMD_CURVE3));
803    }
804
805    #[test]
806    fn test_point() {
807        let p = PointD::new(1.5, 2.5);
808        assert_eq!(p.x, 1.5);
809        assert_eq!(p.y, 2.5);
810    }
811
812    #[test]
813    fn test_vertex() {
814        let v = VertexD::new(1.0, 2.0, PATH_CMD_LINE_TO);
815        assert_eq!(v.x, 1.0);
816        assert_eq!(v.y, 2.0);
817        assert_eq!(v.cmd, PATH_CMD_LINE_TO);
818    }
819
820    #[test]
821    fn test_is_equal_eps() {
822        assert!(is_equal_eps(1.0, 1.0, 1e-10));
823        assert!(is_equal_eps(1.0, 1.0 + 1e-11, 1e-10));
824        assert!(!is_equal_eps(1.0, 2.0, 1e-10));
825        assert!(is_equal_eps(0.0, 0.0, 1e-10));
826        // Different signs, both small
827        assert!(is_equal_eps(1e-12, -1e-12, 1e-10));
828        // Different signs, not small enough
829        assert!(!is_equal_eps(0.1, -0.1, 1e-10));
830    }
831}