1use serde::de::Error as _;
21use serde::{Deserialize, Deserializer, Serialize, Serializer};
22
23pub const MAX_SAFE_INT: i64 = 9_007_199_254_740_991;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct QuantizeError;
29
30impl core::fmt::Display for QuantizeError {
31 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32 f.write_str("non-finite or out-of-range coordinate")
33 }
34}
35impl std::error::Error for QuantizeError {}
36
37pub fn quantize(pts: f64, quantum_per_point: u32) -> Result<i64, QuantizeError> {
43 if !pts.is_finite() {
44 return Err(QuantizeError);
45 }
46 let scaled = pts * f64::from(quantum_per_point);
47 if !scaled.is_finite() {
48 return Err(QuantizeError);
49 }
50 let rounded = if scaled >= 0.0 {
51 (scaled + 0.5).floor()
52 } else {
53 (scaled - 0.5).ceil()
54 };
55 if rounded.abs() > MAX_SAFE_INT as f64 {
56 return Err(QuantizeError);
57 }
58 Ok(rounded as i64)
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub struct QPoint {
64 pub x: i64,
66 pub y: i64,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub struct QRect {
74 pub x0: i64,
76 pub y0: i64,
78 pub x1: i64,
80 pub y1: i64,
82}
83
84impl QRect {
85 pub fn new(x0: i64, y0: i64, x1: i64, y1: i64) -> Result<Self, QuantizeError> {
87 if x0 > x1 || y0 > y1 {
88 return Err(QuantizeError);
89 }
90 Ok(QRect { x0, y0, x1, y1 })
91 }
92
93 pub fn to_array(self) -> [i64; 4] {
95 [self.x0, self.y0, self.x1, self.y1]
96 }
97
98 pub fn contains_with_tolerance(self, other: QRect, tolerance_q: i64) -> bool {
100 other.x0 >= self.x0 - tolerance_q
101 && other.y0 >= self.y0 - tolerance_q
102 && other.x1 <= self.x1 + tolerance_q
103 && other.y1 <= self.y1 + tolerance_q
104 }
105}
106
107impl Serialize for QRect {
108 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
109 self.to_array().serialize(s)
110 }
111}
112
113impl<'de> Deserialize<'de> for QRect {
114 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
115 let [x0, y0, x1, y1] = <[i64; 4]>::deserialize(d)?;
116 QRect::new(x0, y0, x1, y1).map_err(|_| D::Error::custom("malformed bbox: x0>x1 or y0>y1"))
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use proptest::prelude::*;
124
125 #[test]
126 fn rounding_is_half_away_from_zero() {
127 assert_eq!(quantize(0.005, 100).unwrap(), 1);
129 assert_eq!(quantize(-0.005, 100).unwrap(), -1);
130 assert_eq!(quantize(0.004, 100).unwrap(), 0);
131 assert_eq!(quantize(-0.004, 100).unwrap(), 0);
132 assert_eq!(quantize(612.0, 100).unwrap(), 61200);
133 assert_eq!(quantize(0.0, 100).unwrap(), 0);
134 assert_eq!(quantize(-0.0, 100).unwrap(), 0);
136 }
137
138 #[test]
139 fn rejects_non_finite_and_overflow() {
140 assert!(quantize(f64::NAN, 100).is_err());
141 assert!(quantize(f64::INFINITY, 100).is_err());
142 assert!(quantize(1e17, 100).is_err());
143 }
144
145 #[test]
146 fn qrect_validates_and_serializes_as_array() {
147 assert!(QRect::new(5, 5, 4, 9).is_err());
148 let r = QRect::new(1, 2, 3, 4).unwrap();
149 assert_eq!(serde_json::to_string(&r).unwrap(), "[1,2,3,4]");
150 let back: QRect = serde_json::from_str("[1,2,3,4]").unwrap();
151 assert_eq!(back, r);
152 assert!(serde_json::from_str::<QRect>("[5,5,4,9]").is_err());
153 }
154
155 proptest! {
156 #[test]
157 fn quantization_of_integers_is_exact(n in -1_000_000i64..1_000_000i64) {
158 prop_assert_eq!(quantize(n as f64, 100).unwrap(), n * 100);
160 }
161
162 #[test]
163 fn quantize_is_monotonic(a in -10_000.0f64..10_000.0, b in -10_000.0f64..10_000.0) {
164 let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
165 prop_assert!(quantize(lo, 100).unwrap() <= quantize(hi, 100).unwrap());
166 }
167 }
168}