reflexo/vector/ir/
geom.rs

1use core::fmt;
2use std::{
3    cmp::Ordering,
4    hash::{Hash, Hasher},
5    sync::Arc,
6};
7
8#[cfg(feature = "rkyv")]
9use rkyv::{Archive, Deserialize as rDeser, Serialize as rSer};
10
11use super::PathItem;
12
13/// Scalar value of Vector representation.
14/// Note: Unlike Typst's Scalar, all lengths with Scalar type are in pt.
15#[derive(Default, Clone, Copy)]
16#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))]
17#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
18pub struct Scalar(pub f32);
19
20impl Scalar {
21    fn is_zero(&self) -> bool {
22        self.0 == 0.0
23    }
24}
25
26impl From<f32> for Scalar {
27    fn from(float: f32) -> Self {
28        Self(float)
29    }
30}
31
32impl From<Scalar> for f32 {
33    fn from(scalar: Scalar) -> Self {
34        scalar.0
35    }
36}
37
38impl fmt::Debug for Scalar {
39    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40        self.0.fmt(f)
41    }
42}
43
44impl std::ops::Add for Scalar {
45    type Output = Self;
46
47    fn add(self, rhs: Self) -> Self::Output {
48        Self(self.0 + rhs.0)
49    }
50}
51
52impl std::ops::Sub for Scalar {
53    type Output = Self;
54
55    fn sub(self, rhs: Self) -> Self::Output {
56        Self(self.0 - rhs.0)
57    }
58}
59
60impl std::ops::Mul for Scalar {
61    type Output = Self;
62
63    fn mul(self, rhs: Self) -> Self::Output {
64        Self(self.0 * rhs.0)
65    }
66}
67
68impl std::ops::Div for Scalar {
69    type Output = Self;
70
71    fn div(self, rhs: Self) -> Self::Output {
72        Self(self.0 / rhs.0)
73    }
74}
75
76impl Eq for Scalar {}
77
78impl PartialEq for Scalar {
79    fn eq(&self, other: &Self) -> bool {
80        assert!(!self.0.is_nan() && !other.0.is_nan(), "float is NaN");
81        self.0 == other.0
82    }
83}
84
85impl PartialEq<f32> for Scalar {
86    fn eq(&self, other: &f32) -> bool {
87        self == &Self(*other)
88    }
89}
90
91impl Ord for Scalar {
92    fn cmp(&self, other: &Self) -> Ordering {
93        self.0.partial_cmp(&other.0).expect("float is NaN")
94    }
95}
96
97impl PartialOrd for Scalar {
98    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
99        Some(self.cmp(other))
100    }
101
102    fn lt(&self, other: &Self) -> bool {
103        self.0 < other.0
104    }
105
106    fn le(&self, other: &Self) -> bool {
107        self.0 <= other.0
108    }
109
110    fn gt(&self, other: &Self) -> bool {
111        self.0 > other.0
112    }
113
114    fn ge(&self, other: &Self) -> bool {
115        self.0 >= other.0
116    }
117}
118
119impl std::ops::Neg for Scalar {
120    type Output = Self;
121
122    fn neg(self) -> Self::Output {
123        Self(-self.0)
124    }
125}
126
127impl Hash for Scalar {
128    fn hash<H: Hasher>(&self, state: &mut H) {
129        debug_assert!(!self.0.is_nan(), "float is NaN");
130        // instead of bits we swap the bytes on platform with BigEndian
131        self.0.to_le_bytes().hash(state);
132    }
133}
134
135/// length in pt.
136pub type Abs = Scalar;
137/// Size in (width pt, height pt)
138pub type Size = Axes<Abs>;
139/// Point in (x pt, y pt)
140pub type Point = Axes<Scalar>;
141/// Ratio within range [0, 1]
142pub type Ratio = Scalar;
143/// Angle in radians
144pub type Angle = Scalar;
145
146/// A container with a horizontal and vertical component.
147#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)]
148#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))]
149#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
150pub struct Axes<T> {
151    /// The horizontal component.
152    pub x: T,
153    /// The vertical component.
154    pub y: T,
155}
156
157impl<T> Axes<T> {
158    pub const fn new(x: T, y: T) -> Self {
159        Self { x, y }
160    }
161}
162
163// impl Add for Axes
164impl<T> std::ops::Add for Axes<T>
165where
166    T: std::ops::Add<Output = T>,
167{
168    type Output = Self;
169
170    fn add(self, rhs: Self) -> Self::Output {
171        Self {
172            x: self.x + rhs.x,
173            y: self.y + rhs.y,
174        }
175    }
176}
177
178impl From<tiny_skia_path::Point> for Point {
179    fn from(typst_axes: tiny_skia_path::Point) -> Self {
180        Self {
181            x: typst_axes.x.into(),
182            y: typst_axes.y.into(),
183        }
184    }
185}
186
187impl From<Point> for tiny_skia_path::Point {
188    fn from(axes: Point) -> Self {
189        Self {
190            x: axes.x.into(),
191            y: axes.y.into(),
192        }
193    }
194}
195
196/// A scale-skew-translate transformation.
197#[repr(C)]
198#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
199#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))]
200#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
201pub struct Transform {
202    pub sx: Ratio,
203    pub ky: Ratio,
204    pub kx: Ratio,
205    pub sy: Ratio,
206    pub tx: Abs,
207    pub ty: Abs,
208}
209
210impl Transform {
211    pub fn from_scale(sx: Ratio, sy: Ratio) -> Self {
212        Self {
213            sx,
214            ky: 0.0.into(),
215            kx: 0.0.into(),
216            sy,
217            tx: 0.0.into(),
218            ty: 0.0.into(),
219        }
220    }
221
222    pub fn from_translate(tx: Abs, ty: Abs) -> Self {
223        Self {
224            sx: 1.0.into(),
225            ky: 0.0.into(),
226            kx: 0.0.into(),
227            sy: 1.0.into(),
228            tx,
229            ty,
230        }
231    }
232
233    pub fn from_skew(kx: Ratio, ky: Ratio) -> Self {
234        Self {
235            sx: 1.0.into(),
236            ky,
237            kx,
238            sy: 1.0.into(),
239            tx: 0.0.into(),
240            ty: 0.0.into(),
241        }
242    }
243
244    pub fn identity() -> Self {
245        Self::from_scale(1.0.into(), 1.0.into())
246    }
247
248    #[inline]
249    pub fn pre_concat(self, other: Self) -> Self {
250        let ts: tiny_skia_path::Transform = self.into();
251        let other: tiny_skia_path::Transform = other.into();
252        let ts = ts.pre_concat(other);
253        ts.into()
254    }
255
256    #[inline]
257    pub fn post_concat(self, other: Self) -> Self {
258        other.pre_concat(self)
259    }
260
261    #[inline]
262    pub fn pre_translate(self, tx: f32, ty: f32) -> Self {
263        let ts: tiny_skia_path::Transform = self.into();
264        let ts = ts.pre_translate(tx, ty);
265        ts.into()
266    }
267
268    /// Whether this is the identity transformation.
269    pub fn is_identity(self) -> bool {
270        self == Self::identity()
271    }
272
273    /// Inverts the transformation.
274    ///
275    /// Returns `None` if the determinant of the matrix is zero.
276    pub fn invert(self) -> Option<Self> {
277        // Allow the trivial case to be inlined.
278        if self.is_identity() {
279            return Some(self);
280        }
281
282        // Fast path for scale-translate-only transforms.
283        if self.kx.is_zero() && self.ky.is_zero() {
284            if self.sx.is_zero() || self.sy.is_zero() {
285                return Some(Self::from_translate(-self.tx, -self.ty));
286            }
287
288            let inv_x = 1.0 / self.sx.0;
289            let inv_y = 1.0 / self.sy.0;
290            return Some(Self {
291                sx: Scalar(inv_x),
292                ky: Scalar(0.),
293                kx: Scalar(0.),
294                sy: Scalar(inv_y),
295                tx: Scalar(-self.tx.0 * inv_x),
296                ty: Scalar(-self.ty.0 * inv_y),
297            });
298        }
299
300        let det = self.sx.0 * self.sy.0 - self.kx.0 * self.ky.0;
301        if det.abs() < 1e-12 {
302            return None;
303        }
304
305        let inv_det = 1.0 / det;
306        Some(Self {
307            sx: Scalar(self.sy.0 * inv_det),
308            ky: Scalar(-self.ky.0 * inv_det),
309            kx: Scalar(-self.kx.0 * inv_det),
310            sy: Scalar(self.sx.0 * inv_det),
311            tx: Scalar((self.kx.0 * self.ty.0 - self.sy.0 * self.tx.0) * inv_det),
312            ty: Scalar((self.ky.0 * self.tx.0 - self.sx.0 * self.ty.0) * inv_det),
313        })
314    }
315}
316
317impl From<tiny_skia_path::Transform> for Transform {
318    fn from(skia_transform: tiny_skia_path::Transform) -> Self {
319        Self {
320            sx: skia_transform.sx.into(),
321            ky: skia_transform.ky.into(),
322            kx: skia_transform.kx.into(),
323            sy: skia_transform.sy.into(),
324            tx: skia_transform.tx.into(),
325            ty: skia_transform.ty.into(),
326        }
327    }
328}
329
330impl From<Transform> for tiny_skia_path::Transform {
331    fn from(ir_transform: Transform) -> Self {
332        Self {
333            sx: ir_transform.sx.into(),
334            ky: ir_transform.ky.into(),
335            kx: ir_transform.kx.into(),
336            sy: ir_transform.sy.into(),
337            tx: ir_transform.tx.into(),
338            ty: ir_transform.ty.into(),
339        }
340    }
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Hash, Default)]
344pub struct Rect {
345    pub lo: Point,
346    pub hi: Point,
347}
348
349impl Rect {
350    pub fn empty() -> Self {
351        Self {
352            lo: Point::new(Scalar(0.), Scalar(0.)),
353            hi: Point::new(Scalar(0.), Scalar(0.)),
354        }
355    }
356
357    pub fn cano(&self) -> Self {
358        let Rect { lo, hi } = self;
359        Self {
360            lo: Point::new(lo.x.min(hi.x), lo.y.min(hi.y)),
361            hi: Point::new(lo.x.max(hi.x), lo.y.max(hi.y)),
362        }
363    }
364
365    /// Returns whether the rectangle has no area.
366    pub fn is_empty(&self) -> bool {
367        self.lo.x >= self.hi.x || self.lo.y >= self.hi.y
368    }
369
370    /// Returns whether the rectangle is not well constructed.
371    ///
372    /// Note: This is not the same as `is_empty`.
373    pub fn is_intersected(&self) -> bool {
374        self.lo.x <= self.hi.x || self.lo.y <= self.hi.y
375    }
376
377    pub fn intersect(&self, other: &Self) -> Self {
378        Self {
379            lo: self.lo.max(&other.lo),
380            hi: self.hi.min(&other.hi),
381        }
382    }
383
384    pub fn union(&self, other: &Self) -> Self {
385        if self.is_empty() {
386            return *other;
387        }
388
389        Self {
390            lo: self.lo.min(&other.lo),
391            hi: self.hi.max(&other.hi),
392        }
393    }
394
395    pub fn translate(&self, dp: Point) -> Self {
396        Self {
397            lo: self.lo + dp,
398            hi: self.hi + dp,
399        }
400    }
401
402    pub fn width(&self) -> Scalar {
403        self.hi.x - self.lo.x
404    }
405
406    pub fn height(&self) -> Scalar {
407        self.hi.y - self.lo.y
408    }
409
410    pub fn left(&self) -> Scalar {
411        self.lo.x
412    }
413
414    pub fn right(&self) -> Scalar {
415        self.hi.x
416    }
417
418    pub fn top(&self) -> Scalar {
419        self.lo.y
420    }
421
422    pub fn bottom(&self) -> Scalar {
423        self.hi.y
424    }
425}
426
427impl From<tiny_skia_path::Rect> for Rect {
428    fn from(rect: tiny_skia_path::Rect) -> Self {
429        Self {
430            lo: Point::new(Scalar(rect.left()), Scalar(rect.top())),
431            hi: Point::new(Scalar(rect.right()), Scalar(rect.bottom())),
432        }
433    }
434}
435
436impl TryFrom<Rect> for tiny_skia_path::Rect {
437    type Error = ();
438
439    fn try_from(rect: Rect) -> Result<Self, Self::Error> {
440        Self::from_ltrb(rect.lo.x.0, rect.lo.y.0, rect.hi.x.0, rect.hi.y.0).ok_or(())
441    }
442}
443
444pub trait EuclidMinMax {
445    fn min(&self, other: &Self) -> Self;
446    fn max(&self, other: &Self) -> Self;
447}
448
449impl EuclidMinMax for Scalar {
450    fn min(&self, other: &Self) -> Self {
451        Self(self.0.min(other.0))
452    }
453
454    fn max(&self, other: &Self) -> Self {
455        Self(self.0.max(other.0))
456    }
457}
458
459impl EuclidMinMax for Point {
460    fn min(&self, other: &Self) -> Self {
461        Self {
462            x: self.x.min(other.x),
463            y: self.y.min(other.y),
464        }
465    }
466
467    fn max(&self, other: &Self) -> Self {
468        Self {
469            x: self.x.max(other.x),
470            y: self.y.max(other.y),
471        }
472    }
473}
474
475/// Item representing all the transform that is applicable to a
476/// [`super::VecItem`]. See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform>
477#[derive(Debug, Clone, Hash, PartialEq, Eq)]
478#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))]
479#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
480pub enum TransformItem {
481    /// `matrix` transform.
482    Matrix(Arc<Transform>),
483    /// `translate` transform.
484    Translate(Arc<Axes<Abs>>),
485    /// `scale` transform.
486    Scale(Arc<(Ratio, Ratio)>),
487    /// `rotate` transform.
488    Rotate(Arc<Scalar>),
489    /// `skewX skewY` transform.
490    Skew(Arc<(Ratio, Ratio)>),
491
492    /// clip path.
493    Clip(Arc<PathItem>),
494}
495
496/// See [`TransformItem`].
497impl From<TransformItem> for Transform {
498    fn from(value: TransformItem) -> Self {
499        match value {
500            TransformItem::Matrix(m) => *m,
501            TransformItem::Scale(m) => Transform::from_scale(m.0, m.1),
502            TransformItem::Translate(m) => Transform::from_translate(m.x, m.y),
503            TransformItem::Rotate(_m) => todo!(),
504            TransformItem::Skew(m) => Transform::from_skew(m.0, m.1),
505            TransformItem::Clip(_m) => Transform::identity(),
506        }
507    }
508}