use crate::types::{Point, Rect};
use super::{CssAffineMatrix, CssVector};
impl CssAffineMatrix {
pub const fn translation(offset: CssVector) -> Self {
Self::new(CssVector::new(1.0, 0.0), CssVector::new(0.0, 1.0), offset)
}
pub const fn is_scale_translate(self) -> bool {
self.x_axis.y == 0.0 && self.y_axis.x == 0.0
}
pub fn around(self, pivot: Point) -> Self {
let [a, b, c, d, e, f] = self.components();
let px = f64::from(pivot.x);
let py = f64::from(pivot.y);
Self::from_components(
a,
b,
c,
d,
px + e - a * px - c * py,
py + f - b * px - d * py,
)
}
pub fn transform_point(self, point: Point) -> Point {
let [a, b, c, d, e, f] = self.components();
let x = f64::from(point.x);
let y = f64::from(point.y);
Point::new((a * x + c * y + e) as f32, (b * x + d * y + f) as f32)
}
pub fn enclosing_rect(self, rect: Rect) -> Rect {
let corners = [
rect.origin,
Point::new(rect.right(), rect.origin.y),
Point::new(rect.origin.x, rect.bottom()),
Point::new(rect.right(), rect.bottom()),
]
.map(|point| self.transform_point(point));
let left = corners
.iter()
.map(|point| point.x)
.fold(f32::INFINITY, f32::min);
let top = corners
.iter()
.map(|point| point.y)
.fold(f32::INFINITY, f32::min);
let right = corners
.iter()
.map(|point| point.x)
.fold(f32::NEG_INFINITY, f32::max);
let bottom = corners
.iter()
.map(|point| point.y)
.fold(f32::NEG_INFINITY, f32::max);
Rect::from_xywh(left, top, right - left, bottom - top)
}
}
impl std::ops::Mul for CssAffineMatrix {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let [a1, b1, c1, d1, e1, f1] = self.components();
let [a2, b2, c2, d2, e2, f2] = rhs.components();
Self::from_components(
a1 * a2 + c1 * b2,
b1 * a2 + d1 * b2,
a1 * c2 + c1 * d2,
b1 * c2 + d1 * d2,
a1 * e2 + c1 * f2 + e1,
b1 * e2 + d1 * f2 + f1,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn affine_composition_applies_child_before_parent() {
let child = CssAffineMatrix::translation(CssVector::new(2.0, 3.0));
let parent = CssAffineMatrix::from_components(2.0, 0.0, 0.0, 4.0, 0.0, 0.0);
assert_eq!(
(parent * child).transform_point(Point::new(5.0, 7.0)),
Point::new(14.0, 40.0)
);
}
#[test]
fn scale_translate_classification_rejects_rotation_and_skew() {
assert!(CssAffineMatrix::IDENTITY.is_scale_translate());
assert!(CssAffineMatrix::translation(CssVector::new(2.0, 3.0)).is_scale_translate());
assert!(
!CssAffineMatrix::from_components(1.0, 0.1, 0.0, 1.0, 0.0, 0.0).is_scale_translate()
);
}
}