diff options
Diffstat (limited to 'src/geom')
| -rw-r--r-- | src/geom/abs.rs | 266 | ||||
| -rw-r--r-- | src/geom/align.rs | 239 | ||||
| -rw-r--r-- | src/geom/angle.rs | 188 | ||||
| -rw-r--r-- | src/geom/axes.rs | 305 | ||||
| -rw-r--r-- | src/geom/color.rs | 386 | ||||
| -rw-r--r-- | src/geom/corners.rs | 219 | ||||
| -rw-r--r-- | src/geom/dir.rs | 79 | ||||
| -rw-r--r-- | src/geom/ellipse.rs | 22 | ||||
| -rw-r--r-- | src/geom/em.rs | 153 | ||||
| -rw-r--r-- | src/geom/fr.rs | 119 | ||||
| -rw-r--r-- | src/geom/length.rs | 128 | ||||
| -rw-r--r-- | src/geom/macros.rs | 47 | ||||
| -rw-r--r-- | src/geom/mod.rs | 121 | ||||
| -rw-r--r-- | src/geom/paint.rs | 30 | ||||
| -rw-r--r-- | src/geom/path.rs | 54 | ||||
| -rw-r--r-- | src/geom/point.rs | 146 | ||||
| -rw-r--r-- | src/geom/ratio.rs | 133 | ||||
| -rw-r--r-- | src/geom/rel.rs | 246 | ||||
| -rw-r--r-- | src/geom/rounded.rs | 182 | ||||
| -rw-r--r-- | src/geom/scalar.rs | 175 | ||||
| -rw-r--r-- | src/geom/shape.rs | 35 | ||||
| -rw-r--r-- | src/geom/sides.rs | 268 | ||||
| -rw-r--r-- | src/geom/size.rs | 78 | ||||
| -rw-r--r-- | src/geom/smart.rs | 146 | ||||
| -rw-r--r-- | src/geom/stroke.rs | 387 | ||||
| -rw-r--r-- | src/geom/transform.rs | 77 |
26 files changed, 0 insertions, 4229 deletions
diff --git a/src/geom/abs.rs b/src/geom/abs.rs deleted file mode 100644 index 4ca3a9a1..00000000 --- a/src/geom/abs.rs +++ /dev/null @@ -1,266 +0,0 @@ -use super::*; - -/// An absolute length. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Abs(Scalar); - -impl Abs { - /// The zero length. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// The infinite length. - pub const fn inf() -> Self { - Self(Scalar(f64::INFINITY)) - } - - /// Create an absolute length from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) - } - - /// Create an absolute length from a value in a unit. - pub fn with_unit(val: f64, unit: AbsUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) - } - - /// Create an absolute length from a number of points. - pub fn pt(pt: f64) -> Self { - Self::with_unit(pt, AbsUnit::Pt) - } - - /// Create an absolute length from a number of millimeters. - pub fn mm(mm: f64) -> Self { - Self::with_unit(mm, AbsUnit::Mm) - } - - /// Create an absolute length from a number of centimeters. - pub fn cm(cm: f64) -> Self { - Self::with_unit(cm, AbsUnit::Cm) - } - - /// Create an absolute length from a number of inches. - pub fn inches(inches: f64) -> Self { - Self::with_unit(inches, AbsUnit::In) - } - - /// Get the value of this absolute length in raw units. - pub const fn to_raw(self) -> f64 { - (self.0).0 - } - - /// Get the value of this absolute length in a unit. - pub fn to_unit(self, unit: AbsUnit) -> f64 { - self.to_raw() / unit.raw_scale() - } - - /// Convert this to a number of points. - pub fn to_pt(self) -> f64 { - self.to_unit(AbsUnit::Pt) - } - - /// Convert this to a number of millimeters. - pub fn to_mm(self) -> f64 { - self.to_unit(AbsUnit::Mm) - } - - /// Convert this to a number of centimeters. - pub fn to_cm(self) -> f64 { - self.to_unit(AbsUnit::Cm) - } - - /// Convert this to a number of inches. - pub fn to_inches(self) -> f64 { - self.to_unit(AbsUnit::In) - } - - /// The absolute value of this length. - pub fn abs(self) -> Self { - Self::raw(self.to_raw().abs()) - } - - /// The minimum of this and another absolute length. - pub fn min(self, other: Self) -> Self { - Self(self.0.min(other.0)) - } - - /// Set to the minimum of this and another absolute length. - pub fn set_min(&mut self, other: Self) { - *self = (*self).min(other); - } - - /// The maximum of this and another absolute length. - pub fn max(self, other: Self) -> Self { - Self(self.0.max(other.0)) - } - - /// Set to the maximum of this and another absolute length. - pub fn set_max(&mut self, other: Self) { - *self = (*self).max(other); - } - - /// Whether the other absolute length fits into this one (i.e. is smaller). - /// Allows for a bit of slack. - pub fn fits(self, other: Self) -> bool { - self.0 + 1e-6 >= other.0 - } - - /// Compares two absolute lengths for whether they are approximately equal. - pub fn approx_eq(self, other: Self) -> bool { - self == other || (self - other).to_raw().abs() < 1e-6 - } - - /// Perform a checked division by a number, returning zero if the result - /// is not finite. - pub fn safe_div(self, number: f64) -> Self { - let result = self.to_raw() / number; - if result.is_finite() { - Self::raw(result) - } else { - Self::zero() - } - } -} - -impl Numeric for Abs { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl Debug for Abs { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}pt", round_2(self.to_pt())) - } -} - -impl Neg for Abs { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Abs { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Abs - Abs -> Abs); - -impl Mul<f64> for Abs { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Abs> for f64 { - type Output = Abs; - - fn mul(self, other: Abs) -> Abs { - other * self - } -} - -impl Div<f64> for Abs { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Abs { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.to_raw() / other.to_raw() - } -} - -assign_impl!(Abs += Abs); -assign_impl!(Abs -= Abs); -assign_impl!(Abs *= f64); -assign_impl!(Abs /= f64); - -impl Rem for Abs { - type Output = Self; - - fn rem(self, other: Self) -> Self::Output { - Self(self.0 % other.0) - } -} - -impl Sum for Abs { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -impl<'a> Sum<&'a Self> for Abs { - fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -cast! { - Abs, - self => Value::Length(self.into()), -} - -/// Different units of absolute measurement. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum AbsUnit { - /// Points. - Pt, - /// Millimeters. - Mm, - /// Centimeters. - Cm, - /// Inches. - In, -} - -impl AbsUnit { - /// How many raw units correspond to a value of `1.0` in this unit. - fn raw_scale(self) -> f64 { - match self { - AbsUnit::Pt => 1.0, - AbsUnit::Mm => 2.83465, - AbsUnit::Cm => 28.3465, - AbsUnit::In => 72.0, - } - } -} - -impl Debug for AbsUnit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - AbsUnit::Mm => "mm", - AbsUnit::Pt => "pt", - AbsUnit::Cm => "cm", - AbsUnit::In => "in", - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_length_unit_conversion() { - assert!((Abs::mm(150.0).to_cm() - 15.0) < 1e-4); - } -} diff --git a/src/geom/align.rs b/src/geom/align.rs deleted file mode 100644 index 47acd3a6..00000000 --- a/src/geom/align.rs +++ /dev/null @@ -1,239 +0,0 @@ -use super::*; - -/// Where to align something along an axis. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Align { - /// Align at the left side. - Left, - /// Align in the horizontal middle. - Center, - /// Align at the right side. - Right, - /// Align at the top side. - Top, - /// Align in the vertical middle. - Horizon, - /// Align at the bottom side. - Bottom, -} - -impl Align { - /// Top-left alignment. - pub const LEFT_TOP: Axes<Self> = Axes { x: Align::Left, y: Align::Top }; - - /// Center-horizon alignment. - pub const CENTER_HORIZON: Axes<Self> = Axes { x: Align::Center, y: Align::Horizon }; - - /// The axis this alignment belongs to. - pub const fn axis(self) -> Axis { - match self { - Self::Left | Self::Center | Self::Right => Axis::X, - Self::Top | Self::Horizon | Self::Bottom => Axis::Y, - } - } - - /// The inverse alignment. - pub const fn inv(self) -> Self { - match self { - Self::Left => Self::Right, - Self::Center => Self::Center, - Self::Right => Self::Left, - Self::Top => Self::Bottom, - Self::Horizon => Self::Horizon, - Self::Bottom => Self::Top, - } - } - - /// Returns the position of this alignment in a container with the given - /// extent. - pub fn position(self, extent: Abs) -> Abs { - match self { - Self::Left | Self::Top => Abs::zero(), - Self::Center | Self::Horizon => extent / 2.0, - Self::Right | Self::Bottom => extent, - } - } -} - -impl From<Side> for Align { - fn from(side: Side) -> Self { - match side { - Side::Left => Self::Left, - Side::Top => Self::Top, - Side::Right => Self::Right, - Side::Bottom => Self::Bottom, - } - } -} - -impl Debug for Align { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Left => "left", - Self::Center => "center", - Self::Right => "right", - Self::Top => "top", - Self::Horizon => "horizon", - Self::Bottom => "bottom", - }) - } -} - -/// The generic alignment representation. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum GenAlign { - /// Align at the start side of the text direction. - Start, - /// Align at the end side of the text direction. - End, - /// Align at a specific alignment. - Specific(Align), -} - -impl GenAlign { - /// The axis this alignment belongs to. - pub const fn axis(self) -> Axis { - match self { - Self::Start | Self::End => Axis::X, - Self::Specific(align) => align.axis(), - } - } -} - -impl From<Align> for GenAlign { - fn from(align: Align) -> Self { - Self::Specific(align) - } -} - -impl From<HorizontalAlign> for GenAlign { - fn from(align: HorizontalAlign) -> Self { - align.0 - } -} - -impl From<VerticalAlign> for GenAlign { - fn from(align: VerticalAlign) -> Self { - align.0 - } -} - -impl Debug for GenAlign { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Start => f.pad("start"), - Self::End => f.pad("end"), - Self::Specific(align) => align.fmt(f), - } - } -} - -cast! { - type GenAlign: "alignment", -} - -cast! { - type Axes<GenAlign>: "2d alignment", -} - -cast! { - Axes<Align>, - self => self.map(GenAlign::from).into_value(), -} - -cast! { - Axes<Option<GenAlign>>, - self => match (self.x, self.y) { - (Some(x), Some(y)) => Axes::new(x, y).into_value(), - (Some(x), None) => x.into_value(), - (None, Some(y)) => y.into_value(), - (None, None) => Value::None, - }, - align: GenAlign => { - let mut aligns = Axes::default(); - aligns.set(align.axis(), Some(align)); - aligns - }, - aligns: Axes<GenAlign> => aligns.map(Some), -} - -impl From<Axes<GenAlign>> for Axes<Option<GenAlign>> { - fn from(axes: Axes<GenAlign>) -> Self { - axes.map(Some) - } -} - -impl From<Axes<Align>> for Axes<Option<GenAlign>> { - fn from(axes: Axes<Align>) -> Self { - axes.map(GenAlign::Specific).into() - } -} - -impl From<Align> for Axes<Option<GenAlign>> { - fn from(align: Align) -> Self { - let mut axes = Axes::splat(None); - axes.set(align.axis(), Some(align.into())); - axes - } -} - -impl Resolve for GenAlign { - type Output = Align; - - fn resolve(self, styles: StyleChain) -> Self::Output { - let dir = item!(dir)(styles); - match self { - Self::Start => dir.start().into(), - Self::End => dir.end().into(), - Self::Specific(align) => align, - } - } -} - -impl Fold for GenAlign { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -impl Fold for Align { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -/// Utility struct to restrict a passed alignment value to the horizontal axis -/// on cast. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct HorizontalAlign(pub GenAlign); - -cast! { - HorizontalAlign, - self => self.0.into_value(), - align: GenAlign => { - if align.axis() != Axis::X { - bail!("alignment must be horizontal"); - } - Self(align) - }, -} - -/// Utility struct to restrict a passed alignment value to the vertical axis on -/// cast. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct VerticalAlign(pub GenAlign); - -cast! { - VerticalAlign, - self => self.0.into_value(), - align: GenAlign => { - if align.axis() != Axis::Y { - bail!("alignment must be vertical"); - } - Self(align) - }, -} diff --git a/src/geom/angle.rs b/src/geom/angle.rs deleted file mode 100644 index c03810d9..00000000 --- a/src/geom/angle.rs +++ /dev/null @@ -1,188 +0,0 @@ -use super::*; - -/// An angle. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Angle(Scalar); - -impl Angle { - /// The zero angle. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// Create an angle from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) - } - - /// Create an angle from a value in a unit. - pub fn with_unit(val: f64, unit: AngleUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) - } - - /// Create an angle from a number of radians. - pub fn rad(rad: f64) -> Self { - Self::with_unit(rad, AngleUnit::Rad) - } - - /// Create an angle from a number of degrees. - pub fn deg(deg: f64) -> Self { - Self::with_unit(deg, AngleUnit::Deg) - } - - /// Get the value of this angle in raw units. - pub const fn to_raw(self) -> f64 { - (self.0).0 - } - - /// Get the value of this angle in a unit. - pub fn to_unit(self, unit: AngleUnit) -> f64 { - self.to_raw() / unit.raw_scale() - } - - /// Convert this to a number of radians. - pub fn to_rad(self) -> f64 { - self.to_unit(AngleUnit::Rad) - } - - /// Convert this to a number of degrees. - pub fn to_deg(self) -> f64 { - self.to_unit(AngleUnit::Deg) - } - - /// The absolute value of the this angle. - pub fn abs(self) -> Self { - Self::raw(self.to_raw().abs()) - } - - /// Get the sine of this angle in radians. - pub fn sin(self) -> f64 { - self.to_rad().sin() - } - - /// Get the cosine of this angle in radians. - pub fn cos(self) -> f64 { - self.to_rad().cos() - } - - /// Get the tangent of this angle in radians. - pub fn tan(self) -> f64 { - self.to_rad().tan() - } -} - -impl Numeric for Angle { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl Debug for Angle { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}deg", round_2(self.to_deg())) - } -} - -impl Neg for Angle { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Angle { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Angle - Angle -> Angle); - -impl Mul<f64> for Angle { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Angle> for f64 { - type Output = Angle; - - fn mul(self, other: Angle) -> Angle { - other * self - } -} - -impl Div<f64> for Angle { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Angle { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.to_raw() / other.to_raw() - } -} - -assign_impl!(Angle += Angle); -assign_impl!(Angle -= Angle); -assign_impl!(Angle *= f64); -assign_impl!(Angle /= f64); - -impl Sum for Angle { - fn sum<I: Iterator<Item = Angle>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -/// Different units of angular measurement. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum AngleUnit { - /// Radians. - Rad, - /// Degrees. - Deg, -} - -impl AngleUnit { - /// How many raw units correspond to a value of `1.0` in this unit. - fn raw_scale(self) -> f64 { - match self { - Self::Rad => 1.0, - Self::Deg => PI / 180.0, - } - } -} - -impl Debug for AngleUnit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Rad => "rad", - Self::Deg => "deg", - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_angle_unit_conversion() { - assert!((Angle::rad(2.0 * PI).to_deg() - 360.0) < 1e-4); - assert!((Angle::deg(45.0).to_rad() - std::f64::consts::FRAC_PI_4) < 1e-4); - } -} diff --git a/src/geom/axes.rs b/src/geom/axes.rs deleted file mode 100644 index 059d3bb2..00000000 --- a/src/geom/axes.rs +++ /dev/null @@ -1,305 +0,0 @@ -use std::any::Any; -use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; - -use super::*; - -/// A container with a horizontal and vertical component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Axes<T> { - /// The horizontal component. - pub x: T, - /// The vertical component. - pub y: T, -} - -impl<T> Axes<T> { - /// Create a new instance from the two components. - pub const fn new(x: T, y: T) -> Self { - Self { x, y } - } - - /// Create a new instance with two equal components. - pub fn splat(v: T) -> Self - where - T: Clone, - { - Self { x: v.clone(), y: v } - } - - /// Map the individual fields with `f`. - pub fn map<F, U>(self, mut f: F) -> Axes<U> - where - F: FnMut(T) -> U, - { - Axes { x: f(self.x), y: f(self.y) } - } - - /// Convert from `&Axes<T>` to `Axes<&T>`. - pub fn as_ref(&self) -> Axes<&T> { - Axes { x: &self.x, y: &self.y } - } - - /// Convert from `&Axes<T>` to `Axes<&<T as Deref>::Target>`. - pub fn as_deref(&self) -> Axes<&T::Target> - where - T: Deref, - { - Axes { x: &self.x, y: &self.y } - } - - /// Convert from `&mut Axes<T>` to `Axes<&mut T>`. - pub fn as_mut(&mut self) -> Axes<&mut T> { - Axes { x: &mut self.x, y: &mut self.y } - } - - /// Zip two instances into an instance over a tuple. - pub fn zip<U>(self, other: Axes<U>) -> Axes<(T, U)> { - Axes { x: (self.x, other.x), y: (self.y, other.y) } - } - - /// Whether a condition is true for at least one of fields. - pub fn any<F>(self, mut f: F) -> bool - where - F: FnMut(&T) -> bool, - { - f(&self.x) || f(&self.y) - } - - /// Whether a condition is true for both fields. - pub fn all<F>(self, mut f: F) -> bool - where - F: FnMut(&T) -> bool, - { - f(&self.x) && f(&self.y) - } - - /// Filter the individual fields with a mask. - pub fn filter(self, mask: Axes<bool>) -> Axes<Option<T>> { - Axes { - x: if mask.x { Some(self.x) } else { None }, - y: if mask.y { Some(self.y) } else { None }, - } - } -} - -impl<T: Default> Axes<T> { - /// Create a new instance with y set to its default value. - pub fn with_x(x: T) -> Self { - Self { x, y: T::default() } - } - - /// Create a new instance with x set to its default value. - pub fn with_y(y: T) -> Self { - Self { x: T::default(), y } - } -} - -impl<T: Ord> Axes<T> { - /// The component-wise minimum of this and another instance. - pub fn min(self, other: Self) -> Self { - Self { x: self.x.min(other.x), y: self.y.min(other.y) } - } - - /// The component-wise minimum of this and another instance. - pub fn max(self, other: Self) -> Self { - Self { x: self.x.max(other.x), y: self.y.max(other.y) } - } - - /// The minimum of width and height. - pub fn min_by_side(self) -> T { - self.x.min(self.y) - } - - /// The minimum of width and height. - pub fn max_by_side(self) -> T { - self.x.max(self.y) - } -} - -impl<T> Get<Axis> for Axes<T> { - type Component = T; - - fn get_ref(&self, axis: Axis) -> &T { - match axis { - Axis::X => &self.x, - Axis::Y => &self.y, - } - } - - fn get_mut(&mut self, axis: Axis) -> &mut T { - match axis { - Axis::X => &mut self.x, - Axis::Y => &mut self.y, - } - } -} - -impl<T> Debug for Axes<T> -where - T: Debug + 'static, -{ - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Axes { x: Some(x), y: Some(y) } = - self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<GenAlign>()) - { - write!(f, "{:?} + {:?}", x, y) - } else if (&self.x as &dyn Any).is::<Abs>() { - write!(f, "Size({:?}, {:?})", self.x, self.y) - } else { - write!(f, "Axes({:?}, {:?})", self.x, self.y) - } - } -} - -/// The two layouting axes. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum Axis { - /// The horizontal axis. - X, - /// The vertical axis. - Y, -} - -impl Axis { - /// The direction with the given positivity for this axis. - pub fn dir(self, positive: bool) -> Dir { - match (self, positive) { - (Self::X, true) => Dir::LTR, - (Self::X, false) => Dir::RTL, - (Self::Y, true) => Dir::TTB, - (Self::Y, false) => Dir::BTT, - } - } - - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::X => Self::Y, - Self::Y => Self::X, - } - } -} - -impl Debug for Axis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::X => "horizontal", - Self::Y => "vertical", - }) - } -} - -impl<T> Axes<Option<T>> { - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> { - Axes { - x: self.x.unwrap_or(other.x), - y: self.y.unwrap_or(other.y), - } - } -} - -impl<T> Axes<Smart<T>> { - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> { - Axes { - x: self.x.unwrap_or(other.x), - y: self.y.unwrap_or(other.y), - } - } -} - -impl Axes<bool> { - /// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`. - pub fn select<T>(self, t: Axes<T>, f: Axes<T>) -> Axes<T> { - Axes { - x: if self.x { t.x } else { f.x }, - y: if self.y { t.y } else { f.y }, - } - } -} - -impl Not for Axes<bool> { - type Output = Self; - - fn not(self) -> Self::Output { - Self { x: !self.x, y: !self.y } - } -} - -impl BitOr for Axes<bool> { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self::Output { - Self { x: self.x | rhs.x, y: self.y | rhs.y } - } -} - -impl BitOr<bool> for Axes<bool> { - type Output = Self; - - fn bitor(self, rhs: bool) -> Self::Output { - Self { x: self.x | rhs, y: self.y | rhs } - } -} - -impl BitAnd for Axes<bool> { - type Output = Self; - - fn bitand(self, rhs: Self) -> Self::Output { - Self { x: self.x & rhs.x, y: self.y & rhs.y } - } -} - -impl BitAnd<bool> for Axes<bool> { - type Output = Self; - - fn bitand(self, rhs: bool) -> Self::Output { - Self { x: self.x & rhs, y: self.y & rhs } - } -} - -impl BitOrAssign for Axes<bool> { - fn bitor_assign(&mut self, rhs: Self) { - self.x |= rhs.x; - self.y |= rhs.y; - } -} - -impl BitAndAssign for Axes<bool> { - fn bitand_assign(&mut self, rhs: Self) { - self.x &= rhs.x; - self.y &= rhs.y; - } -} - -cast! { - Axes<Rel<Length>>, - self => array![self.x, self.y].into_value(), - array: Array => { - let mut iter = array.into_iter(); - match (iter.next(), iter.next(), iter.next()) { - (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), - _ => bail!("point array must contain exactly two entries"), - } - }, -} - -impl<T: Resolve> Resolve for Axes<T> { - type Output = Axes<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Fold> Fold for Axes<Option<T>> { - type Output = Axes<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer).map(|(inner, outer)| match inner { - Some(value) => value.fold(outer), - None => outer, - }) - } -} diff --git a/src/geom/color.rs b/src/geom/color.rs deleted file mode 100644 index c7676c2b..00000000 --- a/src/geom/color.rs +++ /dev/null @@ -1,386 +0,0 @@ -use std::str::FromStr; - -use super::*; - -/// A color in a dynamic format. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum Color { - /// An 8-bit luma color. - Luma(LumaColor), - /// An 8-bit RGBA color. - Rgba(RgbaColor), - /// An 8-bit CMYK color. - Cmyk(CmykColor), -} - -impl Color { - pub const BLACK: Self = Self::Rgba(RgbaColor::new(0x00, 0x00, 0x00, 0xFF)); - pub const GRAY: Self = Self::Rgba(RgbaColor::new(0xAA, 0xAA, 0xAA, 0xFF)); - pub const SILVER: Self = Self::Rgba(RgbaColor::new(0xDD, 0xDD, 0xDD, 0xFF)); - pub const WHITE: Self = Self::Rgba(RgbaColor::new(0xFF, 0xFF, 0xFF, 0xFF)); - pub const NAVY: Self = Self::Rgba(RgbaColor::new(0x00, 0x1f, 0x3f, 0xFF)); - pub const BLUE: Self = Self::Rgba(RgbaColor::new(0x00, 0x74, 0xD9, 0xFF)); - pub const AQUA: Self = Self::Rgba(RgbaColor::new(0x7F, 0xDB, 0xFF, 0xFF)); - pub const TEAL: Self = Self::Rgba(RgbaColor::new(0x39, 0xCC, 0xCC, 0xFF)); - pub const EASTERN: Self = Self::Rgba(RgbaColor::new(0x23, 0x9D, 0xAD, 0xFF)); - pub const PURPLE: Self = Self::Rgba(RgbaColor::new(0xB1, 0x0D, 0xC9, 0xFF)); - pub const FUCHSIA: Self = Self::Rgba(RgbaColor::new(0xF0, 0x12, 0xBE, 0xFF)); - pub const MAROON: Self = Self::Rgba(RgbaColor::new(0x85, 0x14, 0x4b, 0xFF)); - pub const RED: Self = Self::Rgba(RgbaColor::new(0xFF, 0x41, 0x36, 0xFF)); - pub const ORANGE: Self = Self::Rgba(RgbaColor::new(0xFF, 0x85, 0x1B, 0xFF)); - pub const YELLOW: Self = Self::Rgba(RgbaColor::new(0xFF, 0xDC, 0x00, 0xFF)); - pub const OLIVE: Self = Self::Rgba(RgbaColor::new(0x3D, 0x99, 0x70, 0xFF)); - pub const GREEN: Self = Self::Rgba(RgbaColor::new(0x2E, 0xCC, 0x40, 0xFF)); - pub const LIME: Self = Self::Rgba(RgbaColor::new(0x01, 0xFF, 0x70, 0xFF)); - - /// Convert this color to RGBA. - pub fn to_rgba(self) -> RgbaColor { - match self { - Self::Luma(luma) => luma.to_rgba(), - Self::Rgba(rgba) => rgba, - Self::Cmyk(cmyk) => cmyk.to_rgba(), - } - } - - /// Lighten this color by the given factor. - pub fn lighten(self, factor: Ratio) -> Self { - match self { - Self::Luma(luma) => Self::Luma(luma.lighten(factor)), - Self::Rgba(rgba) => Self::Rgba(rgba.lighten(factor)), - Self::Cmyk(cmyk) => Self::Cmyk(cmyk.lighten(factor)), - } - } - - /// Darken this color by the given factor. - pub fn darken(self, factor: Ratio) -> Self { - match self { - Self::Luma(luma) => Self::Luma(luma.darken(factor)), - Self::Rgba(rgba) => Self::Rgba(rgba.darken(factor)), - Self::Cmyk(cmyk) => Self::Cmyk(cmyk.darken(factor)), - } - } - - /// Negate this color. - pub fn negate(self) -> Self { - match self { - Self::Luma(luma) => Self::Luma(luma.negate()), - Self::Rgba(rgba) => Self::Rgba(rgba.negate()), - Self::Cmyk(cmyk) => Self::Cmyk(cmyk.negate()), - } - } -} - -impl Debug for Color { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Luma(c) => Debug::fmt(c, f), - Self::Rgba(c) => Debug::fmt(c, f), - Self::Cmyk(c) => Debug::fmt(c, f), - } - } -} - -/// An 8-bit grayscale color. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct LumaColor(pub u8); - -impl LumaColor { - /// Construct a new luma color. - pub const fn new(luma: u8) -> Self { - Self(luma) - } - - /// Convert to an opque RGBA color. - pub const fn to_rgba(self) -> RgbaColor { - RgbaColor::new(self.0, self.0, self.0, u8::MAX) - } - - /// Convert to CMYK as a fraction of true black. - pub fn to_cmyk(self) -> CmykColor { - CmykColor::new( - round_u8(self.0 as f64 * 0.75), - round_u8(self.0 as f64 * 0.68), - round_u8(self.0 as f64 * 0.67), - round_u8(self.0 as f64 * 0.90), - ) - } - - /// Lighten this color by a factor. - pub fn lighten(self, factor: Ratio) -> Self { - let inc = round_u8((u8::MAX - self.0) as f64 * factor.get()); - Self(self.0.saturating_add(inc)) - } - - /// Darken this color by a factor. - pub fn darken(self, factor: Ratio) -> Self { - let dec = round_u8(self.0 as f64 * factor.get()); - Self(self.0.saturating_sub(dec)) - } - - /// Negate this color. - pub fn negate(self) -> Self { - Self(u8::MAX - self.0) - } -} - -impl Debug for LumaColor { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "luma({})", self.0) - } -} - -impl From<LumaColor> for Color { - fn from(luma: LumaColor) -> Self { - Self::Luma(luma) - } -} - -/// An 8-bit RGBA color. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct RgbaColor { - /// Red channel. - pub r: u8, - /// Green channel. - pub g: u8, - /// Blue channel. - pub b: u8, - /// Alpha channel. - pub a: u8, -} - -impl RgbaColor { - /// Construct a new RGBA color. - pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a } - } - - /// Lighten this color by a factor. - /// - /// The alpha channel is not affected. - pub fn lighten(self, factor: Ratio) -> Self { - let lighten = - |c: u8| c.saturating_add(round_u8((u8::MAX - c) as f64 * factor.get())); - Self { - r: lighten(self.r), - g: lighten(self.g), - b: lighten(self.b), - a: self.a, - } - } - - /// Darken this color by a factor. - /// - /// The alpha channel is not affected. - pub fn darken(self, factor: Ratio) -> Self { - let darken = |c: u8| c.saturating_sub(round_u8(c as f64 * factor.get())); - Self { - r: darken(self.r), - g: darken(self.g), - b: darken(self.b), - a: self.a, - } - } - - /// Negate this color. - /// - /// The alpha channel is not affected. - pub fn negate(self) -> Self { - Self { - r: u8::MAX - self.r, - g: u8::MAX - self.g, - b: u8::MAX - self.b, - a: self.a, - } - } -} - -impl FromStr for RgbaColor { - type Err = &'static str; - - /// Constructs a new color from hex strings like the following: - /// - `#aef` (shorthand, with leading hashtag), - /// - `7a03c2` (without alpha), - /// - `abcdefff` (with alpha). - /// - /// The hashtag is optional and both lower and upper case are fine. - fn from_str(hex_str: &str) -> Result<Self, Self::Err> { - let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str); - if hex_str.chars().any(|c| !c.is_ascii_hexdigit()) { - return Err("color string contains non-hexadecimal letters"); - } - - let len = hex_str.len(); - let long = len == 6 || len == 8; - let short = len == 3 || len == 4; - let alpha = len == 4 || len == 8; - if !long && !short { - return Err("color string has wrong length"); - } - - let mut values: [u8; 4] = [u8::MAX; 4]; - for elem in if alpha { 0..4 } else { 0..3 } { - let item_len = if long { 2 } else { 1 }; - let pos = elem * item_len; - - let item = &hex_str[pos..(pos + item_len)]; - values[elem] = u8::from_str_radix(item, 16).unwrap(); - - if short { - // Duplicate number for shorthand notation, i.e. `a` -> `aa` - values[elem] += values[elem] * 16; - } - } - - Ok(Self::new(values[0], values[1], values[2], values[3])) - } -} - -impl Debug for RgbaColor { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - write!(f, "rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a,)?; - } else { - write!(f, "rgb(\"#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?; - if self.a != 255 { - write!(f, "{:02x}", self.a)?; - } - write!(f, "\")")?; - } - Ok(()) - } -} - -impl<T: Into<RgbaColor>> From<T> for Color { - fn from(rgba: T) -> Self { - Self::Rgba(rgba.into()) - } -} - -cast! { - RgbaColor, - self => Value::Color(self.into()), -} - -/// An 8-bit CMYK color. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct CmykColor { - /// The cyan component. - pub c: u8, - /// The magenta component. - pub m: u8, - /// The yellow component. - pub y: u8, - /// The key (black) component. - pub k: u8, -} - -impl CmykColor { - /// Construct a new CMYK color. - pub const fn new(c: u8, m: u8, y: u8, k: u8) -> Self { - Self { c, m, y, k } - } - - /// Convert this color to RGBA. - pub fn to_rgba(self) -> RgbaColor { - let k = self.k as f64 / 255.0; - let f = |c| { - let c = c as f64 / 255.0; - round_u8(255.0 * (1.0 - c) * (1.0 - k)) - }; - - RgbaColor { r: f(self.c), g: f(self.m), b: f(self.y), a: 255 } - } - - /// Lighten this color by a factor. - pub fn lighten(self, factor: Ratio) -> Self { - let lighten = |c: u8| c.saturating_sub(round_u8(c as f64 * factor.get())); - Self { - c: lighten(self.c), - m: lighten(self.m), - y: lighten(self.y), - k: lighten(self.k), - } - } - - /// Darken this color by a factor. - pub fn darken(self, factor: Ratio) -> Self { - let darken = - |c: u8| c.saturating_add(round_u8((u8::MAX - c) as f64 * factor.get())); - Self { - c: darken(self.c), - m: darken(self.m), - y: darken(self.y), - k: darken(self.k), - } - } - - /// Negate this color. - /// - /// Does not affect the key component. - pub fn negate(self) -> Self { - Self { - c: u8::MAX - self.c, - m: u8::MAX - self.m, - y: u8::MAX - self.y, - k: self.k, - } - } -} - -impl Debug for CmykColor { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let g = |c| 100.0 * (c as f64 / 255.0); - write!( - f, - "cmyk({:.1}%, {:.1}%, {:.1}%, {:.1}%)", - g(self.c), - g(self.m), - g(self.y), - g(self.k), - ) - } -} - -impl From<CmykColor> for Color { - fn from(cmyk: CmykColor) -> Self { - Self::Cmyk(cmyk) - } -} - -/// Convert to the closest u8. -fn round_u8(value: f64) -> u8 { - value.round() as u8 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_color_strings() { - #[track_caller] - fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) { - assert_eq!(RgbaColor::from_str(hex), Ok(RgbaColor::new(r, g, b, a))); - } - - test("f61243ff", 0xf6, 0x12, 0x43, 0xff); - test("b3d8b3", 0xb3, 0xd8, 0xb3, 0xff); - test("fCd2a9AD", 0xfc, 0xd2, 0xa9, 0xad); - test("233", 0x22, 0x33, 0x33, 0xff); - test("111b", 0x11, 0x11, 0x11, 0xbb); - } - - #[test] - fn test_parse_invalid_colors() { - #[track_caller] - fn test(hex: &str, message: &str) { - assert_eq!(RgbaColor::from_str(hex), Err(message)); - } - - test("a5", "color string has wrong length"); - test("12345", "color string has wrong length"); - test("f075ff011", "color string has wrong length"); - test("hmmm", "color string contains non-hexadecimal letters"); - test("14B2AH", "color string contains non-hexadecimal letters"); - } -} diff --git a/src/geom/corners.rs b/src/geom/corners.rs deleted file mode 100644 index 5ee1e063..00000000 --- a/src/geom/corners.rs +++ /dev/null @@ -1,219 +0,0 @@ -use crate::eval::{CastInfo, FromValue, IntoValue, Reflect}; - -use super::*; - -/// A container with components for the four corners of a rectangle. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Corners<T> { - /// The value for the top left corner. - pub top_left: T, - /// The value for the top right corner. - pub top_right: T, - /// The value for the bottom right corner. - pub bottom_right: T, - /// The value for the bottom left corner. - pub bottom_left: T, -} - -impl<T> Corners<T> { - /// Create a new instance from the four components. - pub const fn new(top_left: T, top_right: T, bottom_right: T, bottom_left: T) -> Self { - Self { top_left, top_right, bottom_right, bottom_left } - } - - /// Create an instance with four equal components. - pub fn splat(value: T) -> Self - where - T: Clone, - { - Self { - top_left: value.clone(), - top_right: value.clone(), - bottom_right: value.clone(), - bottom_left: value, - } - } - - /// Map the individual fields with `f`. - pub fn map<F, U>(self, mut f: F) -> Corners<U> - where - F: FnMut(T) -> U, - { - Corners { - top_left: f(self.top_left), - top_right: f(self.top_right), - bottom_right: f(self.bottom_right), - bottom_left: f(self.bottom_left), - } - } - - /// Zip two instances into one. - pub fn zip<U>(self, other: Corners<U>) -> Corners<(T, U)> { - Corners { - top_left: (self.top_left, other.top_left), - top_right: (self.top_right, other.top_right), - bottom_right: (self.bottom_right, other.bottom_right), - bottom_left: (self.bottom_left, other.bottom_left), - } - } - - /// An iterator over the corners, starting with the top left corner, - /// clockwise. - pub fn iter(&self) -> impl Iterator<Item = &T> { - [&self.top_left, &self.top_right, &self.bottom_right, &self.bottom_left] - .into_iter() - } - - /// Whether all sides are equal. - pub fn is_uniform(&self) -> bool - where - T: PartialEq, - { - self.top_left == self.top_right - && self.top_right == self.bottom_right - && self.bottom_right == self.bottom_left - } -} - -impl<T> Get<Corner> for Corners<T> { - type Component = T; - - fn get_ref(&self, corner: Corner) -> &T { - match corner { - Corner::TopLeft => &self.top_left, - Corner::TopRight => &self.top_right, - Corner::BottomRight => &self.bottom_right, - Corner::BottomLeft => &self.bottom_left, - } - } - - fn get_mut(&mut self, corner: Corner) -> &mut T { - match corner { - Corner::TopLeft => &mut self.top_left, - Corner::TopRight => &mut self.top_right, - Corner::BottomRight => &mut self.bottom_right, - Corner::BottomLeft => &mut self.bottom_left, - } - } -} - -/// The four corners of a rectangle. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Corner { - /// The top left corner. - TopLeft, - /// The top right corner. - TopRight, - /// The bottom right corner. - BottomRight, - /// The bottom left corner. - BottomLeft, -} - -impl<T: Reflect> Reflect for Corners<Option<T>> { - fn describe() -> CastInfo { - T::describe() + Dict::describe() - } - - fn castable(value: &Value) -> bool { - Dict::castable(value) || T::castable(value) - } -} - -impl<T> IntoValue for Corners<T> -where - T: PartialEq + IntoValue, -{ - fn into_value(self) -> Value { - if self.is_uniform() { - return self.top_left.into_value(); - } - - let mut dict = Dict::new(); - let mut handle = |key: &str, component: T| { - let value = component.into_value(); - if value != Value::None { - dict.insert(key.into(), value); - } - }; - - handle("top-left", self.top_left); - handle("top-right", self.top_right); - handle("bottom-right", self.bottom_right); - handle("bottom-left", self.bottom_left); - - Value::Dict(dict) - } -} - -impl<T> FromValue for Corners<Option<T>> -where - T: FromValue + Clone, -{ - fn from_value(mut value: Value) -> StrResult<Self> { - let keys = [ - "top-left", - "top-right", - "bottom-right", - "bottom-left", - "left", - "top", - "right", - "bottom", - "rest", - ]; - - if let Value::Dict(dict) = &mut value { - if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) { - let mut take = |key| dict.take(key).ok().map(T::from_value).transpose(); - let rest = take("rest")?; - let left = take("left")?.or_else(|| rest.clone()); - let top = take("top")?.or_else(|| rest.clone()); - let right = take("right")?.or_else(|| rest.clone()); - let bottom = take("bottom")?.or_else(|| rest.clone()); - let corners = Corners { - top_left: take("top-left")? - .or_else(|| top.clone()) - .or_else(|| left.clone()), - top_right: take("top-right")? - .or_else(|| top.clone()) - .or_else(|| right.clone()), - bottom_right: take("bottom-right")? - .or_else(|| bottom.clone()) - .or_else(|| right.clone()), - bottom_left: take("bottom-left")? - .or_else(|| bottom.clone()) - .or_else(|| left.clone()), - }; - - dict.finish(&keys)?; - return Ok(corners); - } - } - - if T::castable(&value) { - Ok(Self::splat(Some(T::from_value(value)?))) - } else { - Err(Self::error(&value)) - } - } -} - -impl<T: Resolve> Resolve for Corners<T> { - type Output = Corners<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Fold> Fold for Corners<Option<T>> { - type Output = Corners<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer).map(|(inner, outer)| match inner { - Some(value) => value.fold(outer), - None => outer, - }) - } -} diff --git a/src/geom/dir.rs b/src/geom/dir.rs deleted file mode 100644 index 48915471..00000000 --- a/src/geom/dir.rs +++ /dev/null @@ -1,79 +0,0 @@ -use super::*; - -/// The four directions into which content can be laid out. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum Dir { - /// Left to right. - LTR, - /// Right to left. - RTL, - /// Top to bottom. - TTB, - /// Bottom to top. - BTT, -} - -impl Dir { - /// The specific axis this direction belongs to. - pub const fn axis(self) -> Axis { - match self { - Self::LTR | Self::RTL => Axis::X, - Self::TTB | Self::BTT => Axis::Y, - } - } - - /// The side this direction starts at. - pub const fn start(self) -> Side { - match self { - Self::LTR => Side::Left, - Self::RTL => Side::Right, - Self::TTB => Side::Top, - Self::BTT => Side::Bottom, - } - } - - /// The side this direction ends at. - pub const fn end(self) -> Side { - match self { - Self::LTR => Side::Right, - Self::RTL => Side::Left, - Self::TTB => Side::Bottom, - Self::BTT => Side::Top, - } - } - - /// The inverse direction. - pub const fn inv(self) -> Self { - match self { - Self::LTR => Self::RTL, - Self::RTL => Self::LTR, - Self::TTB => Self::BTT, - Self::BTT => Self::TTB, - } - } - - /// Whether this direction points into the positive coordinate direction. - /// - /// The positive directions are left-to-right and top-to-bottom. - pub const fn is_positive(self) -> bool { - match self { - Self::LTR | Self::TTB => true, - Self::RTL | Self::BTT => false, - } - } -} - -impl Debug for Dir { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::LTR => "ltr", - Self::RTL => "rtl", - Self::TTB => "ttb", - Self::BTT => "btt", - }) - } -} - -cast! { - type Dir: "direction", -} diff --git a/src/geom/ellipse.rs b/src/geom/ellipse.rs deleted file mode 100644 index ac20ffd3..00000000 --- a/src/geom/ellipse.rs +++ /dev/null @@ -1,22 +0,0 @@ -use super::*; - -/// Produce a shape that approximates an axis-aligned ellipse. -pub fn ellipse(size: Size, fill: Option<Paint>, stroke: Option<Stroke>) -> Shape { - // https://stackoverflow.com/a/2007782 - let z = Abs::zero(); - let rx = size.x / 2.0; - let ry = size.y / 2.0; - let m = 0.551784; - let mx = m * rx; - let my = m * ry; - let point = |x, y| Point::new(x + rx, y + ry); - - let mut path = Path::new(); - path.move_to(point(-rx, z)); - path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry)); - path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z)); - path.cubic_to(point(rx, my), point(mx, ry), point(z, ry)); - path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z)); - - Shape { geometry: Geometry::Path(path), stroke, fill } -} diff --git a/src/geom/em.rs b/src/geom/em.rs deleted file mode 100644 index 8dda9ff6..00000000 --- a/src/geom/em.rs +++ /dev/null @@ -1,153 +0,0 @@ -use super::*; - -/// A length that is relative to the font size. -/// -/// `1em` is the same as the font size. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Em(Scalar); - -impl Em { - /// The zero em length. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// The font size. - pub const fn one() -> Self { - Self(Scalar(1.0)) - } - - /// Create a font-relative length. - pub const fn new(em: f64) -> Self { - Self(Scalar(em)) - } - - /// Create an em length from font units at the given units per em. - pub fn from_units(units: impl Into<f64>, units_per_em: f64) -> Self { - Self(Scalar(units.into() / units_per_em)) - } - - /// Create an em length from a length at the given font size. - pub fn from_length(length: Abs, font_size: Abs) -> Self { - let result = length / font_size; - if result.is_finite() { - Self(Scalar(result)) - } else { - Self::zero() - } - } - - /// The number of em units. - pub const fn get(self) -> f64 { - (self.0).0 - } - - /// The absolute value of this em length. - pub fn abs(self) -> Self { - Self::new(self.get().abs()) - } - - /// Convert to an absolute length at the given font size. - pub fn at(self, font_size: Abs) -> Abs { - let resolved = font_size * self.get(); - if resolved.is_finite() { - resolved - } else { - Abs::zero() - } - } -} - -impl Numeric for Em { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl Debug for Em { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}em", self.get()) - } -} - -impl Neg for Em { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Em { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Em - Em -> Em); - -impl Mul<f64> for Em { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Em> for f64 { - type Output = Em; - - fn mul(self, other: Em) -> Em { - other * self - } -} - -impl Div<f64> for Em { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Em { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.get() / other.get() - } -} - -assign_impl!(Em += Em); -assign_impl!(Em -= Em); -assign_impl!(Em *= f64); -assign_impl!(Em /= f64); - -impl Sum for Em { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -cast! { - Em, - self => Value::Length(self.into()), -} - -impl Resolve for Em { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - if self.is_zero() { - Abs::zero() - } else { - self.at(item!(em)(styles)) - } - } -} diff --git a/src/geom/fr.rs b/src/geom/fr.rs deleted file mode 100644 index c602634d..00000000 --- a/src/geom/fr.rs +++ /dev/null @@ -1,119 +0,0 @@ -use super::*; - -/// A fraction of remaining space. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Fr(Scalar); - -impl Fr { - /// Takes up zero space: `0fr`. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// Takes up as much space as all other items with this fraction: `1fr`. - pub const fn one() -> Self { - Self(Scalar(1.0)) - } - - /// Create a new fraction. - pub const fn new(ratio: f64) -> Self { - Self(Scalar(ratio)) - } - - /// Get the underlying number. - pub const fn get(self) -> f64 { - (self.0).0 - } - - /// The absolute value of this fraction. - pub fn abs(self) -> Self { - Self::new(self.get().abs()) - } - - /// Determine this fraction's share in the remaining space. - pub fn share(self, total: Self, remaining: Abs) -> Abs { - let ratio = self / total; - if ratio.is_finite() && remaining.is_finite() { - (ratio * remaining).max(Abs::zero()) - } else { - Abs::zero() - } - } -} - -impl Numeric for Fr { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl Debug for Fr { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}fr", round_2(self.get())) - } -} - -impl Neg for Fr { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Fr { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Fr - Fr -> Fr); - -impl Mul<f64> for Fr { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Fr> for f64 { - type Output = Fr; - - fn mul(self, other: Fr) -> Fr { - other * self - } -} - -impl Div<f64> for Fr { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Fr { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.get() / other.get() - } -} - -assign_impl!(Fr += Fr); -assign_impl!(Fr -= Fr); -assign_impl!(Fr *= f64); -assign_impl!(Fr /= f64); - -impl Sum for Fr { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} diff --git a/src/geom/length.rs b/src/geom/length.rs deleted file mode 100644 index 7d0a9841..00000000 --- a/src/geom/length.rs +++ /dev/null @@ -1,128 +0,0 @@ -use super::*; - -/// A size or distance, possibly expressed with contextual units. -/// -/// Currently supports absolute and font-relative units, but support could quite -/// easily be extended to other units. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Length { - /// The absolute part. - pub abs: Abs, - /// The font-relative part. - pub em: Em, -} - -impl Length { - /// The zero length. - pub const fn zero() -> Self { - Self { abs: Abs::zero(), em: Em::zero() } - } - - /// Try to compute the absolute value of the length. - pub fn try_abs(self) -> Option<Self> { - (self.abs.is_zero() || self.em.is_zero()) - .then(|| Self { abs: self.abs.abs(), em: self.em.abs() }) - } - - /// Try to divide two lengths. - pub fn try_div(self, other: Self) -> Option<f64> { - if self.abs.is_zero() && other.abs.is_zero() { - Some(self.em / other.em) - } else if self.em.is_zero() && other.em.is_zero() { - Some(self.abs / other.abs) - } else { - None - } - } -} - -impl Debug for Length { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.abs.is_zero(), self.em.is_zero()) { - (false, false) => write!(f, "{:?} + {:?}", self.abs, self.em), - (true, false) => self.em.fmt(f), - (_, true) => self.abs.fmt(f), - } - } -} - -impl Numeric for Length { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.abs.is_finite() && self.em.is_finite() - } -} - -impl PartialOrd for Length { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - if self.em.is_zero() && other.em.is_zero() { - self.abs.partial_cmp(&other.abs) - } else if self.abs.is_zero() && other.abs.is_zero() { - self.em.partial_cmp(&other.em) - } else { - None - } - } -} - -impl From<Abs> for Length { - fn from(abs: Abs) -> Self { - Self { abs, em: Em::zero() } - } -} - -impl From<Em> for Length { - fn from(em: Em) -> Self { - Self { abs: Abs::zero(), em } - } -} - -impl Neg for Length { - type Output = Self; - - fn neg(self) -> Self::Output { - Self { abs: -self.abs, em: -self.em } - } -} - -impl Add for Length { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self { abs: self.abs + rhs.abs, em: self.em + rhs.em } - } -} - -sub_impl!(Length - Length -> Length); - -impl Mul<f64> for Length { - type Output = Self; - - fn mul(self, rhs: f64) -> Self::Output { - Self { abs: self.abs * rhs, em: self.em * rhs } - } -} - -impl Div<f64> for Length { - type Output = Self; - - fn div(self, rhs: f64) -> Self::Output { - Self { abs: self.abs / rhs, em: self.em / rhs } - } -} - -assign_impl!(Length += Length); -assign_impl!(Length -= Length); -assign_impl!(Length *= f64); -assign_impl!(Length /= f64); - -impl Resolve for Length { - type Output = Abs; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.abs + self.em.resolve(styles) - } -} diff --git a/src/geom/macros.rs b/src/geom/macros.rs deleted file mode 100644 index b1b50e22..00000000 --- a/src/geom/macros.rs +++ /dev/null @@ -1,47 +0,0 @@ -/// Implement the `Sub` trait based on existing `Neg` and `Add` impls. -macro_rules! sub_impl { - ($a:ident - $b:ident -> $c:ident) => { - impl std::ops::Sub<$b> for $a { - type Output = $c; - - fn sub(self, other: $b) -> $c { - self + -other - } - } - }; -} - -/// Implement an assign trait based on an existing non-assign trait. -macro_rules! assign_impl { - ($a:ident += $b:ident) => { - impl std::ops::AddAssign<$b> for $a { - fn add_assign(&mut self, other: $b) { - *self = *self + other; - } - } - }; - - ($a:ident -= $b:ident) => { - impl std::ops::SubAssign<$b> for $a { - fn sub_assign(&mut self, other: $b) { - *self = *self - other; - } - } - }; - - ($a:ident *= $b:ident) => { - impl std::ops::MulAssign<$b> for $a { - fn mul_assign(&mut self, other: $b) { - *self = *self * other; - } - } - }; - - ($a:ident /= $b:ident) => { - impl std::ops::DivAssign<$b> for $a { - fn div_assign(&mut self, other: $b) { - *self = *self / other; - } - } - }; -} diff --git a/src/geom/mod.rs b/src/geom/mod.rs deleted file mode 100644 index b7a7ff40..00000000 --- a/src/geom/mod.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! Geometrical primitives. - -#[macro_use] -mod macros; -mod abs; -mod align; -mod angle; -mod axes; -mod color; -mod corners; -mod dir; -mod ellipse; -mod em; -mod fr; -mod length; -mod paint; -mod path; -mod point; -mod ratio; -mod rel; -mod rounded; -mod scalar; -mod shape; -mod sides; -mod size; -mod smart; -mod stroke; -mod transform; - -pub use self::abs::{Abs, AbsUnit}; -pub use self::align::{Align, GenAlign, HorizontalAlign, VerticalAlign}; -pub use self::angle::{Angle, AngleUnit}; -pub use self::axes::{Axes, Axis}; -pub use self::color::{CmykColor, Color, LumaColor, RgbaColor}; -pub use self::corners::{Corner, Corners}; -pub use self::dir::Dir; -pub use self::ellipse::ellipse; -pub use self::em::Em; -pub use self::fr::Fr; -pub use self::length::Length; -pub use self::paint::Paint; -pub use self::path::{Path, PathItem}; -pub use self::point::Point; -pub use self::ratio::Ratio; -pub use self::rel::Rel; -pub use self::rounded::rounded_rect; -pub use self::scalar::Scalar; -pub use self::shape::{Geometry, Shape}; -pub use self::sides::{Side, Sides}; -pub use self::size::Size; -pub use self::smart::Smart; -pub use self::stroke::{ - DashLength, DashPattern, LineCap, LineJoin, PartialStroke, Stroke, -}; -pub use self::transform::Transform; - -use std::cmp::Ordering; -use std::f64::consts::PI; -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::iter::Sum; -use std::ops::*; - -use crate::diag::{bail, StrResult}; -use crate::eval::{array, cast, Array, Dict, Value}; -use crate::model::{Fold, Resolve, StyleChain}; - -/// Generic access to a structure's components. -pub trait Get<Index> { - /// The structure's component type. - type Component; - - /// Borrow the component for the specified index. - fn get_ref(&self, index: Index) -> &Self::Component; - - /// Borrow the component for the specified index mutably. - fn get_mut(&mut self, index: Index) -> &mut Self::Component; - - /// Convenience method for getting a copy of a component. - fn get(self, index: Index) -> Self::Component - where - Self: Sized, - Self::Component: Copy, - { - *self.get_ref(index) - } - - /// Convenience method for setting a component. - fn set(&mut self, index: Index, component: Self::Component) { - *self.get_mut(index) = component; - } -} - -/// A numeric type. -pub trait Numeric: - Sized - + Debug - + Copy - + PartialEq - + Neg<Output = Self> - + Add<Output = Self> - + Sub<Output = Self> - + Mul<f64, Output = Self> - + Div<f64, Output = Self> -{ - /// The identity element for addition. - fn zero() -> Self; - - /// Whether `self` is zero. - fn is_zero(self) -> bool { - self == Self::zero() - } - - /// Whether `self` consists only of finite parts. - fn is_finite(self) -> bool; -} - -/// Round a float to two decimal places. -pub fn round_2(value: f64) -> f64 { - (value * 100.0).round() / 100.0 -} diff --git a/src/geom/paint.rs b/src/geom/paint.rs deleted file mode 100644 index 10fa9fde..00000000 --- a/src/geom/paint.rs +++ /dev/null @@ -1,30 +0,0 @@ -use super::*; - -/// How a fill or stroke should be painted. -#[derive(Clone, Eq, PartialEq, Hash)] -pub enum Paint { - /// A solid color. - Solid(Color), -} - -impl<T: Into<Color>> From<T> for Paint { - fn from(t: T) -> Self { - Self::Solid(t.into()) - } -} - -impl Debug for Paint { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Solid(color) => color.fmt(f), - } - } -} - -cast! { - Paint, - self => match self { - Self::Solid(color) => Value::Color(color), - }, - color: Color => Self::Solid(color), -} diff --git a/src/geom/path.rs b/src/geom/path.rs deleted file mode 100644 index 1c5325a3..00000000 --- a/src/geom/path.rs +++ /dev/null @@ -1,54 +0,0 @@ -use super::*; - -/// A bezier path. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct Path(pub Vec<PathItem>); - -/// An item in a bezier path. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum PathItem { - MoveTo(Point), - LineTo(Point), - CubicTo(Point, Point, Point), - ClosePath, -} - -impl Path { - /// Create an empty path. - pub const fn new() -> Self { - Self(vec![]) - } - - /// Create a path that describes a rectangle. - pub fn rect(size: Size) -> Self { - let z = Abs::zero(); - let point = Point::new; - let mut path = Self::new(); - path.move_to(point(z, z)); - path.line_to(point(size.x, z)); - path.line_to(point(size.x, size.y)); - path.line_to(point(z, size.y)); - path.close_path(); - path - } - - /// Push a [`MoveTo`](PathItem::MoveTo) item. - pub fn move_to(&mut self, p: Point) { - self.0.push(PathItem::MoveTo(p)); - } - - /// Push a [`LineTo`](PathItem::LineTo) item. - pub fn line_to(&mut self, p: Point) { - self.0.push(PathItem::LineTo(p)); - } - - /// Push a [`CubicTo`](PathItem::CubicTo) item. - pub fn cubic_to(&mut self, p1: Point, p2: Point, p3: Point) { - self.0.push(PathItem::CubicTo(p1, p2, p3)); - } - - /// Push a [`ClosePath`](PathItem::ClosePath) item. - pub fn close_path(&mut self) { - self.0.push(PathItem::ClosePath); - } -} diff --git a/src/geom/point.rs b/src/geom/point.rs deleted file mode 100644 index e7811e1e..00000000 --- a/src/geom/point.rs +++ /dev/null @@ -1,146 +0,0 @@ -use super::*; - -/// A point in 2D. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Point { - /// The x coordinate. - pub x: Abs, - /// The y coordinate. - pub y: Abs, -} - -impl Point { - /// The origin point. - pub const fn zero() -> Self { - Self { x: Abs::zero(), y: Abs::zero() } - } - - /// Create a new point from x and y coordinates. - pub const fn new(x: Abs, y: Abs) -> Self { - Self { x, y } - } - - /// Create an instance with two equal components. - pub const fn splat(value: Abs) -> Self { - Self { x: value, y: value } - } - - /// Create a new point with y set to zero. - pub const fn with_x(x: Abs) -> Self { - Self { x, y: Abs::zero() } - } - - /// Create a new point with x set to zero. - pub const fn with_y(y: Abs) -> Self { - Self { x: Abs::zero(), y } - } - - /// The component-wise minimum of this and another point. - pub fn min(self, other: Self) -> Self { - Self { x: self.x.min(other.x), y: self.y.min(other.y) } - } - - /// The component-wise minimum of this and another point. - pub fn max(self, other: Self) -> Self { - Self { x: self.x.max(other.x), y: self.y.max(other.y) } - } - - /// The distance between this point and the origin. - pub fn hypot(self) -> Abs { - Abs::raw(self.x.to_raw().hypot(self.y.to_raw())) - } - - /// Transform the point with the given transformation. - pub fn transform(self, ts: Transform) -> Self { - Self::new( - ts.sx.of(self.x) + ts.kx.of(self.y) + ts.tx, - ts.ky.of(self.x) + ts.sy.of(self.y) + ts.ty, - ) - } - - /// Convert to a size. - pub fn to_size(self) -> Size { - Size::new(self.x, self.y) - } -} - -impl Numeric for Point { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.x.is_finite() && self.y.is_finite() - } -} - -impl Get<Axis> for Point { - type Component = Abs; - - fn get_ref(&self, axis: Axis) -> &Abs { - match axis { - Axis::X => &self.x, - Axis::Y => &self.y, - } - } - - fn get_mut(&mut self, axis: Axis) -> &mut Abs { - match axis { - Axis::X => &mut self.x, - Axis::Y => &mut self.y, - } - } -} - -impl Debug for Point { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Point({:?}, {:?})", self.x, self.y) - } -} - -impl Neg for Point { - type Output = Self; - - fn neg(self) -> Self { - Self { x: -self.x, y: -self.y } - } -} - -impl Add for Point { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { x: self.x + other.x, y: self.y + other.y } - } -} - -sub_impl!(Point - Point -> Point); - -impl Mul<f64> for Point { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self { x: self.x * other, y: self.y * other } - } -} - -impl Mul<Point> for f64 { - type Output = Point; - - fn mul(self, other: Point) -> Point { - other * self - } -} - -impl Div<f64> for Point { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self { x: self.x / other, y: self.y / other } - } -} - -assign_impl!(Point += Point); -assign_impl!(Point -= Point); -assign_impl!(Point *= f64); -assign_impl!(Point /= f64); diff --git a/src/geom/ratio.rs b/src/geom/ratio.rs deleted file mode 100644 index fe87dd6c..00000000 --- a/src/geom/ratio.rs +++ /dev/null @@ -1,133 +0,0 @@ -use super::*; - -/// A ratio of a whole. -/// -/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the -/// corresponding [literal](crate::syntax::ast::Numeric). -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Ratio(Scalar); - -impl Ratio { - /// A ratio of `0%` represented as `0.0`. - pub const fn zero() -> Self { - Self(Scalar(0.0)) - } - - /// A ratio of `100%` represented as `1.0`. - pub const fn one() -> Self { - Self(Scalar(1.0)) - } - - /// Create a new ratio from a value, where `1.0` means `100%`. - pub const fn new(ratio: f64) -> Self { - Self(Scalar(ratio)) - } - - /// Get the underlying ratio. - pub const fn get(self) -> f64 { - (self.0).0 - } - - /// Whether the ratio is zero. - pub fn is_zero(self) -> bool { - self.0 == 0.0 - } - - /// Whether the ratio is one. - pub fn is_one(self) -> bool { - self.0 == 1.0 - } - - /// The absolute value of this ratio. - pub fn abs(self) -> Self { - Self::new(self.get().abs()) - } - - /// Return the ratio of the given `whole`. - pub fn of<T: Numeric>(self, whole: T) -> T { - let resolved = whole * self.get(); - if resolved.is_finite() { - resolved - } else { - T::zero() - } - } -} - -impl Debug for Ratio { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}%", round_2(100.0 * self.get())) - } -} - -impl Neg for Ratio { - type Output = Self; - - fn neg(self) -> Self { - Self(-self.0) - } -} - -impl Add for Ratio { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -sub_impl!(Ratio - Ratio -> Ratio); - -impl Mul for Ratio { - type Output = Self; - - fn mul(self, other: Self) -> Self { - Self(self.0 * other.0) - } -} - -impl Mul<f64> for Ratio { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Ratio> for f64 { - type Output = Ratio; - - fn mul(self, other: Ratio) -> Ratio { - other * self - } -} - -impl Div<f64> for Ratio { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div<Ratio> for f64 { - type Output = Self; - - fn div(self, other: Ratio) -> Self { - self / other.get() - } -} - -impl Div for Ratio { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.get() / other.get() - } -} - -assign_impl!(Ratio += Ratio); -assign_impl!(Ratio -= Ratio); -assign_impl!(Ratio *= Ratio); -assign_impl!(Ratio *= f64); -assign_impl!(Ratio /= f64); diff --git a/src/geom/rel.rs b/src/geom/rel.rs deleted file mode 100644 index 88972222..00000000 --- a/src/geom/rel.rs +++ /dev/null @@ -1,246 +0,0 @@ -use super::*; - -/// A value that is composed of a relative and an absolute part. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Rel<T: Numeric> { - /// The relative part. - pub rel: Ratio, - /// The absolute part. - pub abs: T, -} - -impl<T: Numeric> Rel<T> { - /// The zero relative. - pub fn zero() -> Self { - Self { rel: Ratio::zero(), abs: T::zero() } - } - - /// A relative with a ratio of `100%` and no absolute part. - pub fn one() -> Self { - Self { rel: Ratio::one(), abs: T::zero() } - } - - /// Create a new relative from its parts. - pub fn new(rel: Ratio, abs: T) -> Self { - Self { rel, abs } - } - - /// Whether both parts are zero. - pub fn is_zero(self) -> bool { - self.rel.is_zero() && self.abs == T::zero() - } - - /// Whether the relative part is one and the absolute part is zero. - pub fn is_one(self) -> bool { - self.rel.is_one() && self.abs == T::zero() - } - - /// Evaluate this relative to the given `whole`. - pub fn relative_to(self, whole: T) -> T { - self.rel.of(whole) + self.abs - } - - /// Map the absolute part with `f`. - pub fn map<F, U>(self, f: F) -> Rel<U> - where - F: FnOnce(T) -> U, - U: Numeric, - { - Rel { rel: self.rel, abs: f(self.abs) } - } -} - -impl Rel<Length> { - /// Try to divide two relative lengths. - pub fn try_div(self, other: Self) -> Option<f64> { - if self.rel.is_zero() && other.rel.is_zero() { - self.abs.try_div(other.abs) - } else if self.abs.is_zero() && other.abs.is_zero() { - Some(self.rel / other.rel) - } else { - None - } - } -} - -impl<T: Numeric> Debug for Rel<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.rel.is_zero(), self.abs.is_zero()) { - (false, false) => write!(f, "{:?} + {:?}", self.rel, self.abs), - (false, true) => self.rel.fmt(f), - (true, _) => self.abs.fmt(f), - } - } -} - -impl From<Abs> for Rel<Length> { - fn from(abs: Abs) -> Self { - Rel::from(Length::from(abs)) - } -} - -impl From<Em> for Rel<Length> { - fn from(em: Em) -> Self { - Rel::from(Length::from(em)) - } -} - -impl<T: Numeric> From<T> for Rel<T> { - fn from(abs: T) -> Self { - Self { rel: Ratio::zero(), abs } - } -} - -impl<T: Numeric> From<Ratio> for Rel<T> { - fn from(rel: Ratio) -> Self { - Self { rel, abs: T::zero() } - } -} - -impl<T: Numeric + PartialOrd> PartialOrd for Rel<T> { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - if self.rel.is_zero() && other.rel.is_zero() { - self.abs.partial_cmp(&other.abs) - } else if self.abs.is_zero() && other.abs.is_zero() { - self.rel.partial_cmp(&other.rel) - } else { - None - } - } -} - -impl<T: Numeric> Neg for Rel<T> { - type Output = Self; - - fn neg(self) -> Self { - Self { rel: -self.rel, abs: -self.abs } - } -} - -impl<T: Numeric> Add for Rel<T> { - type Output = Self; - - fn add(self, other: Self) -> Self::Output { - Self { - rel: self.rel + other.rel, - abs: self.abs + other.abs, - } - } -} - -impl<T: Numeric> Sub for Rel<T> { - type Output = Self; - - fn sub(self, other: Self) -> Self::Output { - self + -other - } -} - -impl<T: Numeric> Mul<f64> for Rel<T> { - type Output = Self; - - fn mul(self, other: f64) -> Self::Output { - Self { rel: self.rel * other, abs: self.abs * other } - } -} - -impl<T: Numeric> Mul<Rel<T>> for f64 { - type Output = Rel<T>; - - fn mul(self, other: Rel<T>) -> Self::Output { - other * self - } -} - -impl<T: Numeric> Div<f64> for Rel<T> { - type Output = Self; - - fn div(self, other: f64) -> Self::Output { - Self { rel: self.rel / other, abs: self.abs / other } - } -} - -impl<T: Numeric + AddAssign> AddAssign for Rel<T> { - fn add_assign(&mut self, other: Self) { - self.rel += other.rel; - self.abs += other.abs; - } -} - -impl<T: Numeric + SubAssign> SubAssign for Rel<T> { - fn sub_assign(&mut self, other: Self) { - self.rel -= other.rel; - self.abs -= other.abs; - } -} - -impl<T: Numeric + MulAssign<f64>> MulAssign<f64> for Rel<T> { - fn mul_assign(&mut self, other: f64) { - self.rel *= other; - self.abs *= other; - } -} - -impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Rel<T> { - fn div_assign(&mut self, other: f64) { - self.rel /= other; - self.abs /= other; - } -} - -impl<T: Numeric> Add<T> for Ratio { - type Output = Rel<T>; - - fn add(self, other: T) -> Self::Output { - Rel::from(self) + Rel::from(other) - } -} - -impl<T: Numeric> Add<T> for Rel<T> { - type Output = Self; - - fn add(self, other: T) -> Self::Output { - self + Rel::from(other) - } -} - -impl<T: Numeric> Add<Ratio> for Rel<T> { - type Output = Self; - - fn add(self, other: Ratio) -> Self::Output { - self + Rel::from(other) - } -} - -impl<T> Resolve for Rel<T> -where - T: Resolve + Numeric, - <T as Resolve>::Output: Numeric, -{ - type Output = Rel<<T as Resolve>::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|abs| abs.resolve(styles)) - } -} - -impl Fold for Rel<Abs> { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -impl Fold for Rel<Length> { - type Output = Self; - - fn fold(self, _: Self::Output) -> Self::Output { - self - } -} - -cast! { - Rel<Abs>, - self => self.map(Length::from).into_value(), -} diff --git a/src/geom/rounded.rs b/src/geom/rounded.rs deleted file mode 100644 index f1a7ea08..00000000 --- a/src/geom/rounded.rs +++ /dev/null @@ -1,182 +0,0 @@ -use super::*; - -/// Produce shapes that together make up a rounded rectangle. -pub fn rounded_rect( - size: Size, - radius: Corners<Abs>, - fill: Option<Paint>, - stroke: Sides<Option<Stroke>>, -) -> Vec<Shape> { - let mut res = vec![]; - if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) { - res.push(Shape { - geometry: fill_geometry(size, radius), - fill, - stroke: if stroke.is_uniform() { stroke.top.clone() } else { None }, - }); - } - - if !stroke.is_uniform() { - for (path, stroke) in stroke_segments(size, radius, stroke) { - if stroke.is_some() { - res.push(Shape { geometry: Geometry::Path(path), fill: None, stroke }); - } - } - } - - res -} - -/// Output the shape of the rectangle as a path or primitive rectangle, -/// depending on whether it is rounded. -fn fill_geometry(size: Size, radius: Corners<Abs>) -> Geometry { - if radius.iter().copied().all(Abs::is_zero) { - Geometry::Rect(size) - } else { - let mut paths = stroke_segments(size, radius, Sides::splat(None)); - assert_eq!(paths.len(), 1); - Geometry::Path(paths.pop().unwrap().0) - } -} - -/// Output the minimum number of paths along the rectangles border. -fn stroke_segments( - size: Size, - radius: Corners<Abs>, - stroke: Sides<Option<Stroke>>, -) -> Vec<(Path, Option<Stroke>)> { - let mut res = vec![]; - - let mut connection = Connection::default(); - let mut path = Path::new(); - let mut always_continuous = true; - let max_radius = size.x.min(size.y).max(Abs::zero()) / 2.0; - - for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] { - let continuous = stroke.get_ref(side) == stroke.get_ref(side.next_cw()); - connection = connection.advance(continuous && side != Side::Left); - always_continuous &= continuous; - - draw_side( - &mut path, - side, - size, - radius.get(side.start_corner()).clamp(Abs::zero(), max_radius), - radius.get(side.end_corner()).clamp(Abs::zero(), max_radius), - connection, - ); - - if !continuous { - res.push((std::mem::take(&mut path), stroke.get_ref(side).clone())); - } - } - - if always_continuous { - path.close_path(); - } - - if !path.0.is_empty() { - res.push((path, stroke.left)); - } - - res -} - -/// Draws one side of the rounded rectangle. Will always draw the left arc. The -/// right arc will be drawn halfway if and only if there is no connection. -fn draw_side( - path: &mut Path, - side: Side, - size: Size, - start_radius: Abs, - end_radius: Abs, - connection: Connection, -) { - let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 }); - let angle_right = Angle::deg(if connection.next { 90.0 } else { 45.0 }); - let length = size.get(side.axis()); - - // The arcs for a border of the rectangle along the x-axis, starting at (0,0). - let p1 = Point::with_x(start_radius); - let mut arc1 = bezier_arc( - p1 + Point::new( - -angle_left.sin() * start_radius, - (1.0 - angle_left.cos()) * start_radius, - ), - Point::new(start_radius, start_radius), - p1, - ); - - let p2 = Point::with_x(length - end_radius); - let mut arc2 = bezier_arc( - p2, - Point::new(length - end_radius, end_radius), - p2 + Point::new( - angle_right.sin() * end_radius, - (1.0 - angle_right.cos()) * end_radius, - ), - ); - - let transform = match side { - Side::Left => Transform::rotate(Angle::deg(-90.0)) - .post_concat(Transform::translate(Abs::zero(), size.y)), - Side::Bottom => Transform::rotate(Angle::deg(180.0)) - .post_concat(Transform::translate(size.x, size.y)), - Side::Right => Transform::rotate(Angle::deg(90.0)) - .post_concat(Transform::translate(size.x, Abs::zero())), - _ => Transform::identity(), - }; - - arc1 = arc1.map(|x| x.transform(transform)); - arc2 = arc2.map(|x| x.transform(transform)); - - if !connection.prev { - path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] }); - } - - if !start_radius.is_zero() { - path.cubic_to(arc1[1], arc1[2], arc1[3]); - } - - path.line_to(arc2[0]); - - if !connection.next && !end_radius.is_zero() { - path.cubic_to(arc2[1], arc2[2], arc2[3]); - } -} - -/// Get the control points for a bezier curve that describes a circular arc for -/// a start point, an end point and a center of the circle whose arc connects -/// the two. -fn bezier_arc(start: Point, center: Point, end: Point) -> [Point; 4] { - // https://stackoverflow.com/a/44829356/1567835 - let a = start - center; - let b = end - center; - - let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw(); - let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw(); - let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2) - / (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw()); - - let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x); - let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x); - - [start, control_1, control_2, end] -} - -/// Indicates which sides of the border strokes in a 2D polygon are connected to -/// their neighboring sides. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -struct Connection { - prev: bool, - next: bool, -} - -impl Connection { - /// Advance to the next clockwise side of the polygon. The argument - /// indicates whether the border is connected on the right side of the next - /// edge. - pub fn advance(self, next: bool) -> Self { - Self { prev: self.next, next } - } -} diff --git a/src/geom/scalar.rs b/src/geom/scalar.rs deleted file mode 100644 index 71fb1755..00000000 --- a/src/geom/scalar.rs +++ /dev/null @@ -1,175 +0,0 @@ -use super::*; - -/// A 64-bit float that implements `Eq`, `Ord` and `Hash`. -/// -/// Panics if it's `NaN` during any of those operations. -#[derive(Default, Copy, Clone)] -pub struct Scalar(pub f64); - -impl Numeric for Scalar { - fn zero() -> Self { - Self(0.0) - } - - fn is_finite(self) -> bool { - self.0.is_finite() - } -} - -impl From<f64> for Scalar { - fn from(float: f64) -> Self { - Self(float) - } -} - -impl From<Scalar> for f64 { - fn from(scalar: Scalar) -> Self { - scalar.0 - } -} - -impl Debug for Scalar { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Eq for Scalar {} - -impl PartialEq for Scalar { - fn eq(&self, other: &Self) -> bool { - assert!(!self.0.is_nan() && !other.0.is_nan(), "float is NaN"); - self.0 == other.0 - } -} - -impl PartialEq<f64> for Scalar { - fn eq(&self, other: &f64) -> bool { - self == &Self(*other) - } -} - -impl Ord for Scalar { - fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).expect("float is NaN") - } -} - -impl PartialOrd for Scalar { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - self.0.partial_cmp(&other.0) - } - - fn lt(&self, other: &Self) -> bool { - self.0 < other.0 - } - - fn le(&self, other: &Self) -> bool { - self.0 <= other.0 - } - - fn gt(&self, other: &Self) -> bool { - self.0 > other.0 - } - - fn ge(&self, other: &Self) -> bool { - self.0 >= other.0 - } -} - -impl Hash for Scalar { - fn hash<H: Hasher>(&self, state: &mut H) { - debug_assert!(!self.0.is_nan(), "float is NaN"); - self.0.to_bits().hash(state); - } -} - -impl Neg for Scalar { - type Output = Self; - - fn neg(self) -> Self::Output { - Self(-self.0) - } -} - -impl<T: Into<Self>> Add<T> for Scalar { - type Output = Self; - - fn add(self, rhs: T) -> Self::Output { - Self(self.0 + rhs.into().0) - } -} - -impl<T: Into<Self>> AddAssign<T> for Scalar { - fn add_assign(&mut self, rhs: T) { - self.0 += rhs.into().0; - } -} - -impl<T: Into<Self>> Sub<T> for Scalar { - type Output = Self; - - fn sub(self, rhs: T) -> Self::Output { - Self(self.0 - rhs.into().0) - } -} - -impl<T: Into<Self>> SubAssign<T> for Scalar { - fn sub_assign(&mut self, rhs: T) { - self.0 -= rhs.into().0; - } -} - -impl<T: Into<Self>> Mul<T> for Scalar { - type Output = Self; - - fn mul(self, rhs: T) -> Self::Output { - Self(self.0 * rhs.into().0) - } -} - -impl<T: Into<Self>> MulAssign<T> for Scalar { - fn mul_assign(&mut self, rhs: T) { - self.0 *= rhs.into().0; - } -} - -impl<T: Into<Self>> Div<T> for Scalar { - type Output = Self; - - fn div(self, rhs: T) -> Self::Output { - Self(self.0 / rhs.into().0) - } -} - -impl<T: Into<Self>> DivAssign<T> for Scalar { - fn div_assign(&mut self, rhs: T) { - self.0 /= rhs.into().0; - } -} - -impl<T: Into<Self>> Rem<T> for Scalar { - type Output = Self; - - fn rem(self, rhs: T) -> Self::Output { - Self(self.0 % rhs.into().0) - } -} - -impl<T: Into<Self>> RemAssign<T> for Scalar { - fn rem_assign(&mut self, rhs: T) { - self.0 %= rhs.into().0; - } -} - -impl Sum for Scalar { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -impl<'a> Sum<&'a Self> for Scalar { - fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} diff --git a/src/geom/shape.rs b/src/geom/shape.rs deleted file mode 100644 index 5658c21f..00000000 --- a/src/geom/shape.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::*; - -/// A geometric shape with optional fill and stroke. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Shape { - /// The shape's geometry. - pub geometry: Geometry, - /// The shape's background fill. - pub fill: Option<Paint>, - /// The shape's border stroke. - pub stroke: Option<Stroke>, -} - -/// A shape's geometry. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Geometry { - /// A line to a point (relative to its position). - Line(Point), - /// A rectangle with its origin in the topleft corner. - Rect(Size), - /// A bezier path. - Path(Path), -} - -impl Geometry { - /// Fill the geometry without a stroke. - pub fn filled(self, fill: Paint) -> Shape { - Shape { geometry: self, fill: Some(fill), stroke: None } - } - - /// Stroke the geometry without a fill. - pub fn stroked(self, stroke: Stroke) -> Shape { - Shape { geometry: self, fill: None, stroke: Some(stroke) } - } -} diff --git a/src/geom/sides.rs b/src/geom/sides.rs deleted file mode 100644 index d4b72a9d..00000000 --- a/src/geom/sides.rs +++ /dev/null @@ -1,268 +0,0 @@ -use crate::eval::{CastInfo, FromValue, IntoValue, Reflect}; - -use super::*; - -/// A container with left, top, right and bottom components. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Sides<T> { - /// The value for the left side. - pub left: T, - /// The value for the top side. - pub top: T, - /// The value for the right side. - pub right: T, - /// The value for the bottom side. - pub bottom: T, -} - -impl<T> Sides<T> { - /// Create a new instance from the four components. - pub const fn new(left: T, top: T, right: T, bottom: T) -> Self { - Self { left, top, right, bottom } - } - - /// Create an instance with four equal components. - pub fn splat(value: T) -> Self - where - T: Clone, - { - Self { - left: value.clone(), - top: value.clone(), - right: value.clone(), - bottom: value, - } - } - - /// Map the individual fields with `f`. - pub fn map<F, U>(self, mut f: F) -> Sides<U> - where - F: FnMut(T) -> U, - { - Sides { - left: f(self.left), - top: f(self.top), - right: f(self.right), - bottom: f(self.bottom), - } - } - - /// Zip two instances into one. - pub fn zip<U>(self, other: Sides<U>) -> Sides<(T, U)> { - Sides { - left: (self.left, other.left), - top: (self.top, other.top), - right: (self.right, other.right), - bottom: (self.bottom, other.bottom), - } - } - - /// An iterator over the sides, starting with the left side, clockwise. - pub fn iter(&self) -> impl Iterator<Item = &T> { - [&self.left, &self.top, &self.right, &self.bottom].into_iter() - } - - /// Whether all sides are equal. - pub fn is_uniform(&self) -> bool - where - T: PartialEq, - { - self.left == self.top && self.top == self.right && self.right == self.bottom - } -} - -impl<T: Add> Sides<T> { - /// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`. - pub fn sum_by_axis(self) -> Axes<T::Output> { - Axes::new(self.left + self.right, self.top + self.bottom) - } -} - -impl Sides<Rel<Abs>> { - /// Evaluate the sides relative to the given `size`. - pub fn relative_to(self, size: Size) -> Sides<Abs> { - Sides { - left: self.left.relative_to(size.x), - top: self.top.relative_to(size.y), - right: self.right.relative_to(size.x), - bottom: self.bottom.relative_to(size.y), - } - } -} - -impl<T> Get<Side> for Sides<T> { - type Component = T; - - fn get_ref(&self, side: Side) -> &T { - match side { - Side::Left => &self.left, - Side::Top => &self.top, - Side::Right => &self.right, - Side::Bottom => &self.bottom, - } - } - - fn get_mut(&mut self, side: Side) -> &mut T { - match side { - Side::Left => &mut self.left, - Side::Top => &mut self.top, - Side::Right => &mut self.right, - Side::Bottom => &mut self.bottom, - } - } -} - -/// The four sides of objects. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Side { - /// The left side. - Left, - /// The top side. - Top, - /// The right side. - Right, - /// The bottom side. - Bottom, -} - -impl Side { - /// The opposite side. - pub fn inv(self) -> Self { - match self { - Self::Left => Self::Right, - Self::Top => Self::Bottom, - Self::Right => Self::Left, - Self::Bottom => Self::Top, - } - } - - /// The next side, clockwise. - pub fn next_cw(self) -> Self { - match self { - Self::Left => Self::Top, - Self::Top => Self::Right, - Self::Right => Self::Bottom, - Self::Bottom => Self::Left, - } - } - - /// The next side, counter-clockwise. - pub fn next_ccw(self) -> Self { - match self { - Self::Left => Self::Bottom, - Self::Top => Self::Left, - Self::Right => Self::Top, - Self::Bottom => Self::Right, - } - } - - /// The first corner of the side in clockwise order. - pub fn start_corner(self) -> Corner { - match self { - Self::Left => Corner::BottomLeft, - Self::Top => Corner::TopLeft, - Self::Right => Corner::TopRight, - Self::Bottom => Corner::BottomRight, - } - } - - /// The second corner of the side in clockwise order. - pub fn end_corner(self) -> Corner { - self.next_cw().start_corner() - } - - /// Return the corresponding axis. - pub fn axis(self) -> Axis { - match self { - Self::Left | Self::Right => Axis::Y, - Self::Top | Self::Bottom => Axis::X, - } - } -} - -impl<T: Reflect> Reflect for Sides<Option<T>> { - fn describe() -> CastInfo { - T::describe() + Dict::describe() - } - - fn castable(value: &Value) -> bool { - Dict::castable(value) || T::castable(value) - } -} - -impl<T> IntoValue for Sides<T> -where - T: PartialEq + IntoValue, -{ - fn into_value(self) -> Value { - if self.is_uniform() { - return self.left.into_value(); - } - - let mut dict = Dict::new(); - let mut handle = |key: &str, component: T| { - let value = component.into_value(); - if value != Value::None { - dict.insert(key.into(), value); - } - }; - - handle("left", self.left); - handle("top", self.top); - handle("right", self.right); - handle("bottom", self.bottom); - - Value::Dict(dict) - } -} - -impl<T> FromValue for Sides<Option<T>> -where - T: Default + FromValue + Clone, -{ - fn from_value(mut value: Value) -> StrResult<Self> { - let keys = ["left", "top", "right", "bottom", "x", "y", "rest"]; - if let Value::Dict(dict) = &mut value { - if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) { - let mut take = |key| dict.take(key).ok().map(T::from_value).transpose(); - let rest = take("rest")?; - let x = take("x")?.or_else(|| rest.clone()); - let y = take("y")?.or_else(|| rest.clone()); - let sides = Sides { - left: take("left")?.or_else(|| x.clone()), - top: take("top")?.or_else(|| y.clone()), - right: take("right")?.or_else(|| x.clone()), - bottom: take("bottom")?.or_else(|| y.clone()), - }; - - dict.finish(&keys)?; - return Ok(sides); - } - } - - if T::castable(&value) { - Ok(Self::splat(Some(T::from_value(value)?))) - } else { - Err(Self::error(&value)) - } - } -} - -impl<T: Resolve> Resolve for Sides<T> { - type Output = Sides<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T: Fold> Fold for Sides<Option<T>> { - type Output = Sides<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer).map(|(inner, outer)| match inner { - Some(value) => value.fold(outer), - None => outer, - }) - } -} diff --git a/src/geom/size.rs b/src/geom/size.rs deleted file mode 100644 index a2e32b77..00000000 --- a/src/geom/size.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::*; - -/// A size in 2D. -pub type Size = Axes<Abs>; - -impl Size { - /// The zero value. - pub const fn zero() -> Self { - Self { x: Abs::zero(), y: Abs::zero() } - } - - /// Whether the other size fits into this one (smaller width and height). - pub fn fits(self, other: Self) -> bool { - self.x.fits(other.x) && self.y.fits(other.y) - } - - /// Convert to a point. - pub fn to_point(self) -> Point { - Point::new(self.x, self.y) - } -} - -impl Numeric for Size { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.x.is_finite() && self.y.is_finite() - } -} - -impl Neg for Size { - type Output = Self; - - fn neg(self) -> Self { - Self { x: -self.x, y: -self.y } - } -} - -impl Add for Size { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { x: self.x + other.x, y: self.y + other.y } - } -} - -sub_impl!(Size - Size -> Size); - -impl Mul<f64> for Size { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self { x: self.x * other, y: self.y * other } - } -} - -impl Mul<Size> for f64 { - type Output = Size; - - fn mul(self, other: Size) -> Size { - other * self - } -} - -impl Div<f64> for Size { - type Output = Self; - - fn div(self, other: f64) -> Self { - Self { x: self.x / other, y: self.y / other } - } -} - -assign_impl!(Size -= Size); -assign_impl!(Size += Size); -assign_impl!(Size *= f64); -assign_impl!(Size /= f64); diff --git a/src/geom/smart.rs b/src/geom/smart.rs deleted file mode 100644 index a6271c20..00000000 --- a/src/geom/smart.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::eval::{AutoValue, CastInfo, FromValue, IntoValue, Reflect}; - -use super::*; - -/// A value that can be automatically determined. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Smart<T> { - /// The value should be determined smartly based on the circumstances. - Auto, - /// A specific value. - Custom(T), -} - -impl<T> Smart<T> { - /// Whether the value is `Auto`. - pub fn is_auto(&self) -> bool { - matches!(self, Self::Auto) - } - - /// Whether this holds a custom value. - pub fn is_custom(&self) -> bool { - matches!(self, Self::Custom(_)) - } - - /// Returns a reference the contained custom value. - /// If the value is [`Smart::Auto`], `None` is returned. - pub fn as_custom(self) -> Option<T> { - match self { - Self::Auto => None, - Self::Custom(x) => Some(x), - } - } - - /// Map the contained custom value with `f`. - pub fn map<F, U>(self, f: F) -> Smart<U> - where - F: FnOnce(T) -> U, - { - match self { - Self::Auto => Smart::Auto, - Self::Custom(x) => Smart::Custom(f(x)), - } - } - - /// Map the contained custom value with `f` if it contains a custom value, - /// otherwise returns `default`. - pub fn map_or<F, U>(self, default: U, f: F) -> U - where - F: FnOnce(T) -> U, - { - match self { - Self::Auto => default, - Self::Custom(x) => f(x), - } - } - - /// Keeps `self` if it contains a custom value, otherwise returns `other`. - pub fn or(self, other: Smart<T>) -> Self { - match self { - Self::Custom(x) => Self::Custom(x), - Self::Auto => other, - } - } - - /// Returns the contained custom value or a provided default value. - pub fn unwrap_or(self, default: T) -> T { - match self { - Self::Auto => default, - Self::Custom(x) => x, - } - } - - /// Returns the contained custom value or computes a default value. - pub fn unwrap_or_else<F>(self, f: F) -> T - where - F: FnOnce() -> T, - { - match self { - Self::Auto => f(), - Self::Custom(x) => x, - } - } - - /// Returns the contained custom value or the default value. - pub fn unwrap_or_default(self) -> T - where - T: Default, - { - self.unwrap_or_else(T::default) - } -} - -impl<T> Default for Smart<T> { - fn default() -> Self { - Self::Auto - } -} - -impl<T: Reflect> Reflect for Smart<T> { - fn castable(value: &Value) -> bool { - AutoValue::castable(value) || T::castable(value) - } - - fn describe() -> CastInfo { - T::describe() + AutoValue::describe() - } -} - -impl<T: IntoValue> IntoValue for Smart<T> { - fn into_value(self) -> Value { - match self { - Smart::Custom(v) => v.into_value(), - Smart::Auto => Value::Auto, - } - } -} - -impl<T: FromValue> FromValue for Smart<T> { - fn from_value(value: Value) -> StrResult<Self> { - match value { - Value::Auto => Ok(Self::Auto), - v if T::castable(&v) => Ok(Self::Custom(T::from_value(v)?)), - _ => Err(Self::error(&value)), - } - } -} - -impl<T: Resolve> Resolve for Smart<T> { - type Output = Smart<T::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl<T> Fold for Smart<T> -where - T: Fold, - T::Output: Default, -{ - type Output = Smart<T::Output>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} diff --git a/src/geom/stroke.rs b/src/geom/stroke.rs deleted file mode 100644 index 66264d5d..00000000 --- a/src/geom/stroke.rs +++ /dev/null @@ -1,387 +0,0 @@ -use crate::eval::{Cast, FromValue}; - -use super::*; - -/// A stroke of a geometric shape. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Stroke { - /// The stroke's paint. - pub paint: Paint, - /// The stroke's thickness. - pub thickness: Abs, - /// The stroke's line cap. - pub line_cap: LineCap, - /// The stroke's line join. - pub line_join: LineJoin, - /// The stroke's line dash pattern. - pub dash_pattern: Option<DashPattern<Abs, Abs>>, - /// The miter limit. Defaults to 4.0, same as `tiny-skia`. - pub miter_limit: Scalar, -} - -impl Default for Stroke { - fn default() -> Self { - Self { - paint: Paint::Solid(Color::BLACK), - thickness: Abs::pt(1.0), - line_cap: LineCap::Butt, - line_join: LineJoin::Miter, - dash_pattern: None, - miter_limit: Scalar(4.0), - } - } -} - -/// A partial stroke representation. -/// -/// In this representation, both fields are optional so that you can pass either -/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where -/// this is expected. -#[derive(Default, Clone, Eq, PartialEq, Hash)] -pub struct PartialStroke<T = Length> { - /// The stroke's paint. - pub paint: Smart<Paint>, - /// The stroke's thickness. - pub thickness: Smart<T>, - /// The stroke's line cap. - pub line_cap: Smart<LineCap>, - /// The stroke's line join. - pub line_join: Smart<LineJoin>, - /// The stroke's line dash pattern. - pub dash_pattern: Smart<Option<DashPattern<T>>>, - /// The miter limit. - pub miter_limit: Smart<Scalar>, -} - -impl<T> PartialStroke<T> { - /// Map the contained lengths with `f`. - pub fn map<F, U>(self, f: F) -> PartialStroke<U> - where - F: Fn(T) -> U, - { - PartialStroke { - paint: self.paint, - thickness: self.thickness.map(&f), - line_cap: self.line_cap, - line_join: self.line_join, - dash_pattern: self.dash_pattern.map(|pattern| { - pattern.map(|pattern| DashPattern { - array: pattern - .array - .into_iter() - .map(|l| match l { - DashLength::Length(v) => DashLength::Length(f(v)), - DashLength::LineWidth => DashLength::LineWidth, - }) - .collect(), - phase: f(pattern.phase), - }) - }), - miter_limit: self.miter_limit, - } - } -} - -impl PartialStroke<Abs> { - /// Unpack the stroke, filling missing fields from the `default`. - pub fn unwrap_or(self, default: Stroke) -> Stroke { - let thickness = self.thickness.unwrap_or(default.thickness); - let dash_pattern = self - .dash_pattern - .map(|pattern| { - pattern.map(|pattern| DashPattern { - array: pattern - .array - .into_iter() - .map(|l| l.finish(thickness)) - .collect(), - phase: pattern.phase, - }) - }) - .unwrap_or(default.dash_pattern); - - Stroke { - paint: self.paint.unwrap_or(default.paint), - thickness, - line_cap: self.line_cap.unwrap_or(default.line_cap), - line_join: self.line_join.unwrap_or(default.line_join), - dash_pattern, - miter_limit: self.miter_limit.unwrap_or(default.miter_limit), - } - } - - /// Unpack the stroke, filling missing fields with the default values. - pub fn unwrap_or_default(self) -> Stroke { - self.unwrap_or(Stroke::default()) - } -} - -impl<T: Debug> Debug for PartialStroke<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let Self { - paint, - thickness, - line_cap, - line_join, - dash_pattern, - miter_limit, - } = &self; - if line_cap.is_auto() - && line_join.is_auto() - && dash_pattern.is_auto() - && miter_limit.is_auto() - { - match (&self.paint, &self.thickness) { - (Smart::Custom(paint), Smart::Custom(thickness)) => { - write!(f, "{thickness:?} + {paint:?}") - } - (Smart::Custom(paint), Smart::Auto) => paint.fmt(f), - (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f), - (Smart::Auto, Smart::Auto) => f.pad("1pt + black"), - } - } else { - write!(f, "(")?; - let mut sep = ""; - if let Smart::Custom(paint) = &paint { - write!(f, "{}paint: {:?}", sep, paint)?; - sep = ", "; - } - if let Smart::Custom(thickness) = &thickness { - write!(f, "{}thickness: {:?}", sep, thickness)?; - sep = ", "; - } - if let Smart::Custom(cap) = &line_cap { - write!(f, "{}cap: {:?}", sep, cap)?; - sep = ", "; - } - if let Smart::Custom(join) = &line_join { - write!(f, "{}join: {:?}", sep, join)?; - sep = ", "; - } - if let Smart::Custom(dash) = &dash_pattern { - write!(f, "{}dash: {:?}", sep, dash)?; - sep = ", "; - } - if let Smart::Custom(miter_limit) = &miter_limit { - write!(f, "{}miter-limit: {:?}", sep, miter_limit)?; - } - write!(f, ")")?; - Ok(()) - } - } -} - -impl Resolve for PartialStroke { - type Output = PartialStroke<Abs>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - PartialStroke { - paint: self.paint, - thickness: self.thickness.resolve(styles), - line_cap: self.line_cap, - line_join: self.line_join, - dash_pattern: self.dash_pattern.resolve(styles), - miter_limit: self.miter_limit, - } - } -} - -impl Fold for PartialStroke<Abs> { - type Output = Self; - - fn fold(self, outer: Self::Output) -> Self::Output { - Self { - paint: self.paint.or(outer.paint), - thickness: self.thickness.or(outer.thickness), - line_cap: self.line_cap.or(outer.line_cap), - line_join: self.line_join.or(outer.line_join), - dash_pattern: self.dash_pattern.or(outer.dash_pattern), - miter_limit: self.miter_limit.or(outer.miter_limit), - } - } -} - -cast! { - type PartialStroke: "stroke", - thickness: Length => Self { - thickness: Smart::Custom(thickness), - ..Default::default() - }, - color: Color => Self { - paint: Smart::Custom(color.into()), - ..Default::default() - }, - mut dict: Dict => { - fn take<T: FromValue>(dict: &mut Dict, key: &str) -> StrResult<Smart<T>> { - Ok(dict.take(key).ok().map(T::from_value) - .transpose()?.map(Smart::Custom).unwrap_or(Smart::Auto)) - } - - let paint = take::<Paint>(&mut dict, "paint")?; - let thickness = take::<Length>(&mut dict, "thickness")?; - let line_cap = take::<LineCap>(&mut dict, "cap")?; - let line_join = take::<LineJoin>(&mut dict, "join")?; - let dash_pattern = take::<Option<DashPattern>>(&mut dict, "dash")?; - let miter_limit = take::<f64>(&mut dict, "miter-limit")?; - dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?; - - Self { - paint, - thickness, - line_cap, - line_join, - dash_pattern, - miter_limit: miter_limit.map(Scalar), - } - }, -} - -cast! { - PartialStroke<Abs>, - self => self.map(Length::from).into_value(), -} - -/// The line cap of a stroke -#[derive(Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum LineCap { - Butt, - Round, - Square, -} - -impl Debug for LineCap { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - LineCap::Butt => write!(f, "\"butt\""), - LineCap::Round => write!(f, "\"round\""), - LineCap::Square => write!(f, "\"square\""), - } - } -} - -/// The line join of a stroke -#[derive(Copy, Clone, Eq, PartialEq, Hash, Cast)] -pub enum LineJoin { - Miter, - Round, - Bevel, -} - -impl Debug for LineJoin { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - LineJoin::Miter => write!(f, "\"miter\""), - LineJoin::Round => write!(f, "\"round\""), - LineJoin::Bevel => write!(f, "\"bevel\""), - } - } -} - -/// A line dash pattern. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct DashPattern<T = Length, DT = DashLength<T>> { - /// The dash array. - pub array: Vec<DT>, - /// The dash phase. - pub phase: T, -} - -impl<T: Debug, DT: Debug> Debug for DashPattern<T, DT> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "(array: (")?; - for (i, elem) in self.array.iter().enumerate() { - if i == 0 { - write!(f, "{:?}", elem)?; - } else { - write!(f, ", {:?}", elem)?; - } - } - write!(f, "), phase: {:?})", self.phase)?; - Ok(()) - } -} - -impl<T: Default> From<Vec<DashLength<T>>> for DashPattern<T> { - fn from(array: Vec<DashLength<T>>) -> Self { - Self { array, phase: T::default() } - } -} - -impl Resolve for DashPattern { - type Output = DashPattern<Abs>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - DashPattern { - array: self.array.into_iter().map(|l| l.resolve(styles)).collect(), - phase: self.phase.resolve(styles), - } - } -} - -// Same names as tikz: -// https://tex.stackexchange.com/questions/45275/tikz-get-values-for-predefined-dash-patterns -cast! { - DashPattern, - - "solid" => Vec::new().into(), - "dotted" => vec![DashLength::LineWidth, Abs::pt(2.0).into()].into(), - "densely-dotted" => vec![DashLength::LineWidth, Abs::pt(1.0).into()].into(), - "loosely-dotted" => vec![DashLength::LineWidth, Abs::pt(4.0).into()].into(), - "dashed" => vec![Abs::pt(3.0).into(), Abs::pt(3.0).into()].into(), - "densely-dashed" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into()].into(), - "loosely-dashed" => vec![Abs::pt(3.0).into(), Abs::pt(6.0).into()].into(), - "dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into(), DashLength::LineWidth, Abs::pt(2.0).into()].into(), - "densely-dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(1.0).into(), DashLength::LineWidth, Abs::pt(1.0).into()].into(), - "loosely-dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(4.0).into(), DashLength::LineWidth, Abs::pt(4.0).into()].into(), - - array: Vec<DashLength> => Self { array, phase: Length::zero() }, - mut dict: Dict => { - let array: Vec<DashLength> = dict.take("array")?.cast()?; - let phase = dict.take("phase").ok().map(Value::cast) - .transpose()?.unwrap_or(Length::zero()); - dict.finish(&["array", "phase"])?; - Self { - array, - phase, - } - }, -} - -/// The length of a dash in a line dash pattern -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum DashLength<T = Length> { - LineWidth, - Length(T), -} - -impl From<Abs> for DashLength { - fn from(l: Abs) -> Self { - DashLength::Length(l.into()) - } -} - -impl<T> DashLength<T> { - fn finish(self, line_width: T) -> T { - match self { - Self::LineWidth => line_width, - Self::Length(l) => l, - } - } -} - -impl Resolve for DashLength { - type Output = DashLength<Abs>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - match self { - Self::LineWidth => DashLength::LineWidth, - Self::Length(v) => DashLength::Length(v.resolve(styles)), - } - } -} - -cast! { - DashLength, - "dot" => Self::LineWidth, - v: Length => Self::Length(v), -} diff --git a/src/geom/transform.rs b/src/geom/transform.rs deleted file mode 100644 index 1ff1dfdd..00000000 --- a/src/geom/transform.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::*; - -/// A scale-skew-translate transformation. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Transform { - pub sx: Ratio, - pub ky: Ratio, - pub kx: Ratio, - pub sy: Ratio, - pub tx: Abs, - pub ty: Abs, -} - -impl Transform { - /// The identity transformation. - pub const fn identity() -> Self { - Self { - sx: Ratio::one(), - ky: Ratio::zero(), - kx: Ratio::zero(), - sy: Ratio::one(), - tx: Abs::zero(), - ty: Abs::zero(), - } - } - - /// A translate transform. - pub const fn translate(tx: Abs, ty: Abs) -> Self { - Self { tx, ty, ..Self::identity() } - } - - /// A scale transform. - pub const fn scale(sx: Ratio, sy: Ratio) -> Self { - Self { sx, sy, ..Self::identity() } - } - - /// A rotate transform. - pub fn rotate(angle: Angle) -> Self { - let cos = Ratio::new(angle.cos()); - let sin = Ratio::new(angle.sin()); - Self { - sx: cos, - ky: sin, - kx: -sin, - sy: cos, - ..Self::default() - } - } - - /// Whether this is the identity transformation. - pub fn is_identity(self) -> bool { - self == Self::identity() - } - - /// Pre-concatenate another transformation. - pub fn pre_concat(self, prev: Self) -> Self { - Transform { - sx: self.sx * prev.sx + self.kx * prev.ky, - ky: self.ky * prev.sx + self.sy * prev.ky, - kx: self.sx * prev.kx + self.kx * prev.sy, - sy: self.ky * prev.kx + self.sy * prev.sy, - tx: self.sx.of(prev.tx) + self.kx.of(prev.ty) + self.tx, - ty: self.ky.of(prev.tx) + self.sy.of(prev.ty) + self.ty, - } - } - - /// Post-concatenate another transformation. - pub fn post_concat(self, next: Self) -> Self { - next.pre_concat(self) - } -} - -impl Default for Transform { - fn default() -> Self { - Self::identity() - } -} |
