1use serde::{Deserialize, Serialize};
14
15use crate::transform_math::{fixed_sin_cos, matrix_term};
16use crate::{ErrorCode, FileMakerError, Result, Unit};
17
18#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
20pub struct Point {
21 pub x: Unit,
23 pub y: Unit,
25}
26
27#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
29pub struct Size {
30 pub width: Unit,
32 pub height: Unit,
34}
35
36impl Size {
37 pub fn new(width: Unit, height: Unit) -> Result<Self> {
39 if width < Unit::ZERO || height < Unit::ZERO {
40 return Err(invalid_geometry("size dimensions cannot be negative"));
41 }
42 Ok(Self { width, height })
43 }
44}
45
46#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
48pub struct Rect {
49 pub origin: Point,
51 pub size: Size,
53}
54
55impl Rect {
56 pub fn new(x: Unit, y: Unit, width: Unit, height: Unit) -> Result<Self> {
58 Ok(Self {
59 origin: Point { x, y },
60 size: Size::new(width, height)?,
61 })
62 }
63
64 pub fn right(self) -> Result<Unit> {
66 self.origin.x.checked_add(self.size.width)
67 }
68
69 pub fn bottom(self) -> Result<Unit> {
71 self.origin.y.checked_add(self.size.height)
72 }
73
74 pub fn intersects(self, other: Self) -> Result<bool> {
76 Ok(self.origin.x < other.right()?
77 && self.right()? > other.origin.x
78 && self.origin.y < other.bottom()?
79 && self.bottom()? > other.origin.y)
80 }
81
82 pub fn intersection(self, other: Self) -> Result<Option<Self>> {
84 if !self.intersects(other)? {
85 return Ok(None);
86 }
87 let x = self.origin.x.max(other.origin.x);
88 let y = self.origin.y.max(other.origin.y);
89 let right = self.right()?.min(other.right()?);
90 let bottom = self.bottom()?.min(other.bottom()?);
91 Ok(Some(Self::new(
92 x,
93 y,
94 right.checked_sub(x)?,
95 bottom.checked_sub(y)?,
96 )?))
97 }
98
99 pub fn union(self, other: Self) -> Result<Self> {
101 let x = self.origin.x.min(other.origin.x);
102 let y = self.origin.y.min(other.origin.y);
103 let right = self.right()?.max(other.right()?);
104 let bottom = self.bottom()?.max(other.bottom()?);
105 Self::new(x, y, right.checked_sub(x)?, bottom.checked_sub(y)?)
106 }
107
108 pub fn contains(self, point: Point) -> Result<bool> {
110 Ok(point.x >= self.origin.x
111 && point.x < self.right()?
112 && point.y >= self.origin.y
113 && point.y < self.bottom()?)
114 }
115}
116
117#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
119pub struct Insets {
120 pub top: Unit,
122 pub right: Unit,
124 pub bottom: Unit,
126 pub left: Unit,
128}
129
130#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
132pub struct Transform {
133 pub a: i64,
135 pub b: i64,
137 pub c: i64,
139 pub d: i64,
141 pub tx: Unit,
143 pub ty: Unit,
145}
146
147#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
149#[serde(tag = "command", rename_all = "snake_case")]
150pub enum PathCommand {
151 Move { to: Point },
153 Line { to: Point },
155 Curve {
157 control_1: Point,
158 control_2: Point,
159 to: Point,
160 },
161 Close,
163}
164
165impl Default for Transform {
166 fn default() -> Self {
167 Self::IDENTITY
168 }
169}
170
171impl Transform {
172 pub const IDENTITY: Self = Self {
174 a: 1_000_000,
175 b: 0,
176 c: 0,
177 d: 1_000_000,
178 tx: Unit::ZERO,
179 ty: Unit::ZERO,
180 };
181
182 #[must_use]
184 pub const fn translation(tx: Unit, ty: Unit) -> Self {
185 Self {
186 tx,
187 ty,
188 ..Self::IDENTITY
189 }
190 }
191
192 pub fn scale(x: i64, y: i64) -> Result<Self> {
194 if x == 0 || y == 0 {
195 return Err(invalid_geometry("transform scale cannot collapse an axis"));
196 }
197 Ok(Self {
198 a: x,
199 b: 0,
200 c: 0,
201 d: y,
202 tx: Unit::ZERO,
203 ty: Unit::ZERO,
204 })
205 }
206
207 pub fn rotation_degrees(degrees: i32) -> Result<Self> {
209 let (sin, cos) = fixed_sin_cos(degrees)?;
210 Ok(Self {
211 a: cos,
212 b: sin,
213 c: sin
214 .checked_neg()
215 .ok_or_else(|| invalid_geometry("rotation overflow"))?,
216 d: cos,
217 tx: Unit::ZERO,
218 ty: Unit::ZERO,
219 })
220 }
221
222 pub fn then(self, next: Self) -> Result<Self> {
224 Ok(Self {
225 a: matrix_term(next.a, self.a, next.c, self.b)?,
226 b: matrix_term(next.b, self.a, next.d, self.b)?,
227 c: matrix_term(next.a, self.c, next.c, self.d)?,
228 d: matrix_term(next.b, self.c, next.d, self.d)?,
229 tx: combine(self.tx, next.a, self.ty, next.c)?.checked_add(next.tx)?,
230 ty: combine(self.tx, next.b, self.ty, next.d)?.checked_add(next.ty)?,
231 })
232 }
233
234 pub fn around(self, origin: Point) -> Result<Self> {
236 Self::translation(
237 Unit::from_raw(
238 origin
239 .x
240 .raw()
241 .checked_neg()
242 .ok_or_else(|| invalid_geometry("transform origin negation overflow"))?,
243 ),
244 Unit::from_raw(
245 origin
246 .y
247 .raw()
248 .checked_neg()
249 .ok_or_else(|| invalid_geometry("transform origin negation overflow"))?,
250 ),
251 )
252 .then(self)?
253 .then(Self::translation(origin.x, origin.y))
254 }
255
256 #[must_use]
258 pub const fn is_identity(self) -> bool {
259 self.a == Self::IDENTITY.a
260 && self.b == 0
261 && self.c == 0
262 && self.d == Self::IDENTITY.d
263 && self.tx.raw() == 0
264 && self.ty.raw() == 0
265 }
266
267 pub fn inverse_vector(self, vector: Point) -> Result<Point> {
269 let determinant =
270 i128::from(self.a) * i128::from(self.d) - i128::from(self.b) * i128::from(self.c);
271 if determinant == 0 {
272 return Err(invalid_geometry("transform matrix is not invertible"));
273 }
274 let x = (i128::from(self.d) * i128::from(vector.x.raw())
275 - i128::from(self.c) * i128::from(vector.y.raw()))
276 .checked_mul(1_000_000)
277 .ok_or_else(|| invalid_geometry("inverse transform overflow"))?;
278 let y = (i128::from(self.a) * i128::from(vector.y.raw())
279 - i128::from(self.b) * i128::from(vector.x.raw()))
280 .checked_mul(1_000_000)
281 .ok_or_else(|| invalid_geometry("inverse transform overflow"))?;
282 Ok(Point {
283 x: Unit::from_raw(divide_round_i128(x, determinant)?),
284 y: Unit::from_raw(divide_round_i128(y, determinant)?),
285 })
286 }
287
288 pub fn apply(self, point: Point) -> Result<Point> {
290 let x = combine(point.x, self.a, point.y, self.c)?.checked_add(self.tx)?;
291 let y = combine(point.x, self.b, point.y, self.d)?.checked_add(self.ty)?;
292 Ok(Point { x, y })
293 }
294
295 pub fn bounds(self, rect: Rect) -> Result<Rect> {
297 let right = rect.right()?;
298 let bottom = rect.bottom()?;
299 let points = [
300 self.apply(rect.origin)?,
301 self.apply(Point {
302 x: right,
303 y: rect.origin.y,
304 })?,
305 self.apply(Point {
306 x: rect.origin.x,
307 y: bottom,
308 })?,
309 self.apply(Point {
310 x: right,
311 y: bottom,
312 })?,
313 ];
314 let min_x = points
315 .iter()
316 .map(|point| point.x)
317 .min()
318 .unwrap_or(Unit::ZERO);
319 let max_x = points
320 .iter()
321 .map(|point| point.x)
322 .max()
323 .unwrap_or(Unit::ZERO);
324 let min_y = points
325 .iter()
326 .map(|point| point.y)
327 .min()
328 .unwrap_or(Unit::ZERO);
329 let max_y = points
330 .iter()
331 .map(|point| point.y)
332 .max()
333 .unwrap_or(Unit::ZERO);
334 Rect::new(
335 min_x,
336 min_y,
337 max_x.checked_sub(min_x)?,
338 max_y.checked_sub(min_y)?,
339 )
340 }
341}
342
343#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
345#[serde(tag = "kind", rename_all = "snake_case")]
346pub enum Shape {
347 Rect {
349 bounds: Rect,
351 },
352 Ellipse {
354 bounds: Rect,
356 },
357 Polygon {
359 points: Vec<Point>,
361 },
362 Path {
364 bounds: Rect,
366 commands: Vec<PathCommand>,
368 },
369}
370
371impl Shape {
372 pub fn bounds(&self) -> Result<Rect> {
374 match self {
375 Self::Rect { bounds } | Self::Ellipse { bounds } | Self::Path { bounds, .. } => {
376 Ok(*bounds)
377 }
378 Self::Polygon { points } => polygon_bounds(points),
379 }
380 }
381}
382
383#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
385pub struct BoundsSet {
386 pub intrinsic: Rect,
388 pub layout: Rect,
390 pub collision: Rect,
392 pub visual: Rect,
394 pub clip: Option<Rect>,
396}
397
398fn combine(first: Unit, first_scale: i64, second: Unit, second_scale: i64) -> Result<Unit> {
399 let left = i128::from(first.raw()) * i128::from(first_scale);
400 let right = i128::from(second.raw()) * i128::from(second_scale);
401 let raw = (left + right) / 1_000_000;
402 i64::try_from(raw)
403 .map(Unit::from_raw)
404 .map_err(|_| invalid_geometry("transform overflow"))
405}
406
407fn divide_round_i128(numerator: i128, denominator: i128) -> Result<i64> {
408 let (numerator, denominator) = if denominator < 0 {
409 (
410 numerator
411 .checked_neg()
412 .ok_or_else(|| invalid_geometry("inverse transform overflow"))?,
413 denominator
414 .checked_neg()
415 .ok_or_else(|| invalid_geometry("inverse transform overflow"))?,
416 )
417 } else {
418 (numerator, denominator)
419 };
420 let adjustment = denominator / 2;
421 let adjusted = if numerator >= 0 {
422 numerator.checked_add(adjustment)
423 } else {
424 numerator.checked_sub(adjustment)
425 }
426 .ok_or_else(|| invalid_geometry("inverse transform rounding overflow"))?;
427 i64::try_from(adjusted / denominator)
428 .map_err(|_| invalid_geometry("inverse transform is outside the supported range"))
429}
430
431fn polygon_bounds(points: &[Point]) -> Result<Rect> {
432 let first = points
433 .first()
434 .ok_or_else(|| invalid_geometry("polygon requires at least one point"))?;
435 let (mut min_x, mut max_x, mut min_y, mut max_y) = (first.x, first.x, first.y, first.y);
436 for point in &points[1..] {
437 min_x = min_x.min(point.x);
438 max_x = max_x.max(point.x);
439 min_y = min_y.min(point.y);
440 max_y = max_y.max(point.y);
441 }
442 Rect::new(
443 min_x,
444 min_y,
445 max_x.checked_sub(min_x)?,
446 max_y.checked_sub(min_y)?,
447 )
448}
449
450fn invalid_geometry(message: impl Into<String>) -> FileMakerError {
451 FileMakerError::new(ErrorCode::GeometryInvalid, message)
452}