diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-10-28 16:43:38 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-10-28 16:43:38 +0200 |
| commit | 95e9134a3c7d7a14d8c8928413fdffc665671895 (patch) | |
| tree | 822b5f6c2d23aba33cfe4d199405e493e37c3d70 /src/geom | |
| parent | 66030ae5d73d85a0463562230b87f8ec7554c746 (diff) | |
Refactor `geom` module
Diffstat (limited to 'src/geom')
| -rw-r--r-- | src/geom/abs.rs | 261 | ||||
| -rw-r--r-- | src/geom/align.rs | 21 | ||||
| -rw-r--r-- | src/geom/axes.rs | 263 | ||||
| -rw-r--r-- | src/geom/dir.rs | 6 | ||||
| -rw-r--r-- | src/geom/ellipse.rs | 26 | ||||
| -rw-r--r-- | src/geom/em.rs | 10 | ||||
| -rw-r--r-- | src/geom/fr.rs (renamed from src/geom/fraction.rs) | 40 | ||||
| -rw-r--r-- | src/geom/gen.rs | 99 | ||||
| -rw-r--r-- | src/geom/length.rs | 266 | ||||
| -rw-r--r-- | src/geom/mod.rs | 26 | ||||
| -rw-r--r-- | src/geom/paint.rs | 4 | ||||
| -rw-r--r-- | src/geom/path.rs | 40 | ||||
| -rw-r--r-- | src/geom/point.rs | 34 | ||||
| -rw-r--r-- | src/geom/rect.rs | 184 | ||||
| -rw-r--r-- | src/geom/rel.rs (renamed from src/geom/relative.rs) | 65 | ||||
| -rw-r--r-- | src/geom/rounded.rs | 187 | ||||
| -rw-r--r-- | src/geom/sides.rs | 14 | ||||
| -rw-r--r-- | src/geom/size.rs | 78 | ||||
| -rw-r--r-- | src/geom/spec.rs | 358 | ||||
| -rw-r--r-- | src/geom/transform.rs | 10 |
20 files changed, 1003 insertions, 989 deletions
diff --git a/src/geom/abs.rs b/src/geom/abs.rs new file mode 100644 index 00000000..4429e46d --- /dev/null +++ b/src/geom/abs.rs @@ -0,0 +1,261 @@ +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()) + } +} + +/// 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 index 3d5f96e5..a7ee2763 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -19,16 +19,16 @@ pub enum Align { impl Align { /// Top-left alignment. - pub const LEFT_TOP: Spec<Self> = Spec { x: Align::Left, y: Align::Top }; + pub const LEFT_TOP: Axes<Self> = Axes { x: Align::Left, y: Align::Top }; /// Center-horizon alignment. - pub const CENTER_HORIZON: Spec<Self> = Spec { x: Align::Center, y: Align::Horizon }; + pub const CENTER_HORIZON: Axes<Self> = Axes { x: Align::Center, y: Align::Horizon }; /// The axis this alignment belongs to. - pub const fn axis(self) -> SpecAxis { + pub const fn axis(self) -> Axis { match self { - Self::Left | Self::Center | Self::Right => SpecAxis::Horizontal, - Self::Top | Self::Horizon | Self::Bottom => SpecAxis::Vertical, + Self::Left | Self::Center | Self::Right => Axis::X, + Self::Top | Self::Horizon | Self::Bottom => Axis::Y, } } @@ -44,12 +44,13 @@ impl Align { } } - /// Returns the position of this alignment in the given length. - pub fn position(self, length: Length) -> Length { + /// Returns the position of this alignment in a container with the given + /// extentq. + pub fn position(self, extent: Abs) -> Abs { match self { - Self::Left | Self::Top => Length::zero(), - Self::Center | Self::Horizon => length / 2.0, - Self::Right | Self::Bottom => length, + Self::Left | Self::Top => Abs::zero(), + Self::Center | Self::Horizon => extent / 2.0, + Self::Right | Self::Bottom => extent, } } } diff --git a/src/geom/axes.rs b/src/geom/axes.rs new file mode 100644 index 00000000..bfc40c2e --- /dev/null +++ b/src/geom/axes.rs @@ -0,0 +1,263 @@ +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), + } + } +} + +impl<T> Get<Axis> for Axes<T> { + type Component = T; + + fn get(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::<Align>()) + { + 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 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; + } +} diff --git a/src/geom/dir.rs b/src/geom/dir.rs index b8244c0a..b2fd6e5a 100644 --- a/src/geom/dir.rs +++ b/src/geom/dir.rs @@ -15,10 +15,10 @@ pub enum Dir { impl Dir { /// The specific axis this direction belongs to. - pub const fn axis(self) -> SpecAxis { + pub const fn axis(self) -> Axis { match self { - Self::LTR | Self::RTL => SpecAxis::Horizontal, - Self::TTB | Self::BTT => SpecAxis::Vertical, + Self::LTR | Self::RTL => Axis::X, + Self::TTB | Self::BTT => Axis::Y, } } diff --git a/src/geom/ellipse.rs b/src/geom/ellipse.rs new file mode 100644 index 00000000..e734682e --- /dev/null +++ b/src/geom/ellipse.rs @@ -0,0 +1,26 @@ +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 index 99b1163c..d0ad9d98 100644 --- a/src/geom/em.rs +++ b/src/geom/em.rs @@ -7,7 +7,7 @@ use super::*; pub struct Em(Scalar); impl Em { - /// The zero length. + /// The zero em length. pub const fn zero() -> Self { Self(Scalar(0.0)) } @@ -28,7 +28,7 @@ impl Em { } /// Create an em length from a length at the given font size. - pub fn from_length(length: Length, font_size: Length) -> Self { + pub fn from_length(length: Abs, font_size: Abs) -> Self { let result = length / font_size; if result.is_finite() { Self(Scalar(result)) @@ -42,10 +42,10 @@ impl Em { (self.0).0 } - /// Convert to a length at the given font size. - pub fn at(self, font_size: Length) -> Length { + /// 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 { Length::zero() } + if resolved.is_finite() { resolved } else { Abs::zero() } } } diff --git a/src/geom/fraction.rs b/src/geom/fr.rs index 9d4b3aab..a737c953 100644 --- a/src/geom/fraction.rs +++ b/src/geom/fr.rs @@ -2,9 +2,9 @@ use super::*; /// A fraction of remaining space. #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Fraction(Scalar); +pub struct Fr(Scalar); -impl Fraction { +impl Fr { /// Takes up zero space: `0fr`. pub const fn zero() -> Self { Self(Scalar(0.0)) @@ -31,17 +31,17 @@ impl Fraction { } /// Determine this fraction's share in the remaining space. - pub fn share(self, total: Self, remaining: Length) -> Length { + pub fn share(self, total: Self, remaining: Abs) -> Abs { let ratio = self / total; if ratio.is_finite() && remaining.is_finite() { ratio * remaining } else { - Length::zero() + Abs::zero() } } } -impl Numeric for Fraction { +impl Numeric for Fr { fn zero() -> Self { Self::zero() } @@ -51,13 +51,13 @@ impl Numeric for Fraction { } } -impl Debug for Fraction { +impl Debug for Fr { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}fr", round_2(self.get())) } } -impl Neg for Fraction { +impl Neg for Fr { type Output = Self; fn neg(self) -> Self { @@ -65,7 +65,7 @@ impl Neg for Fraction { } } -impl Add for Fraction { +impl Add for Fr { type Output = Self; fn add(self, other: Self) -> Self { @@ -73,9 +73,9 @@ impl Add for Fraction { } } -sub_impl!(Fraction - Fraction -> Fraction); +sub_impl!(Fr - Fr -> Fr); -impl Mul<f64> for Fraction { +impl Mul<f64> for Fr { type Output = Self; fn mul(self, other: f64) -> Self { @@ -83,15 +83,15 @@ impl Mul<f64> for Fraction { } } -impl Mul<Fraction> for f64 { - type Output = Fraction; +impl Mul<Fr> for f64 { + type Output = Fr; - fn mul(self, other: Fraction) -> Fraction { + fn mul(self, other: Fr) -> Fr { other * self } } -impl Div<f64> for Fraction { +impl Div<f64> for Fr { type Output = Self; fn div(self, other: f64) -> Self { @@ -99,7 +99,7 @@ impl Div<f64> for Fraction { } } -impl Div for Fraction { +impl Div for Fr { type Output = f64; fn div(self, other: Self) -> f64 { @@ -107,12 +107,12 @@ impl Div for Fraction { } } -assign_impl!(Fraction += Fraction); -assign_impl!(Fraction -= Fraction); -assign_impl!(Fraction *= f64); -assign_impl!(Fraction /= f64); +assign_impl!(Fr += Fr); +assign_impl!(Fr -= Fr); +assign_impl!(Fr *= f64); +assign_impl!(Fr /= f64); -impl Sum for Fraction { +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/gen.rs b/src/geom/gen.rs deleted file mode 100644 index 462de357..00000000 --- a/src/geom/gen.rs +++ /dev/null @@ -1,99 +0,0 @@ -use super::*; - -/// A container with a main and cross component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Gen<T> { - /// The main component. - pub cross: T, - /// The cross component. - pub main: T, -} - -impl<T> Gen<T> { - /// Create a new instance from the two components. - pub const fn new(cross: T, main: T) -> Self { - Self { cross, main } - } - - /// Create a new instance with two equal components. - pub fn splat(value: T) -> Self - where - T: Clone, - { - Self { cross: value.clone(), main: value } - } - - /// Maps the individual fields with `f`. - pub fn map<F, U>(self, mut f: F) -> Gen<U> - where - F: FnMut(T) -> U, - { - Gen { cross: f(self.cross), main: f(self.main) } - } - - /// Convert to the specific representation, given the current main axis. - pub fn to_spec(self, main: SpecAxis) -> Spec<T> { - match main { - SpecAxis::Horizontal => Spec::new(self.main, self.cross), - SpecAxis::Vertical => Spec::new(self.cross, self.main), - } - } -} - -impl Gen<Length> { - /// The zero value. - pub fn zero() -> Self { - Self { - cross: Length::zero(), - main: Length::zero(), - } - } - - /// Convert to a point. - pub fn to_point(self, main: SpecAxis) -> Point { - self.to_spec(main).to_point() - } -} - -impl<T> Get<GenAxis> for Gen<T> { - type Component = T; - - fn get(self, axis: GenAxis) -> T { - match axis { - GenAxis::Cross => self.cross, - GenAxis::Main => self.main, - } - } - - fn get_mut(&mut self, axis: GenAxis) -> &mut T { - match axis { - GenAxis::Cross => &mut self.cross, - GenAxis::Main => &mut self.main, - } - } -} - -impl<T: Debug> Debug for Gen<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Gen({:?}, {:?})", self.cross, self.main) - } -} - -/// Two generic axes of a container. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum GenAxis { - /// The minor / inline axis. - Cross, - /// The major / block axis. - Main, -} - -impl GenAxis { - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::Cross => Self::Main, - Self::Main => Self::Cross, - } - } -} diff --git a/src/geom/length.rs b/src/geom/length.rs index 773fd2fd..cd526f99 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -1,157 +1,101 @@ use super::*; -/// An absolute length. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Length(Scalar); +/// A length, 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(Scalar(0.0)) - } - - /// The infinite length. - pub const fn inf() -> Self { - Self(Scalar(f64::INFINITY)) - } - - /// Create a length from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) - } - - /// Create a length from a value in a unit. - pub fn with_unit(val: f64, unit: LengthUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) - } - - /// Create a length from a number of points. - pub fn pt(pt: f64) -> Self { - Self::with_unit(pt, LengthUnit::Pt) - } - - /// Create a length from a number of millimeters. - pub fn mm(mm: f64) -> Self { - Self::with_unit(mm, LengthUnit::Mm) + Self { abs: Abs::zero(), em: Em::zero() } } - /// Create a length from a number of centimeters. - pub fn cm(cm: f64) -> Self { - Self::with_unit(cm, LengthUnit::Cm) - } - - /// Create a length from a number of inches. - pub fn inches(inches: f64) -> Self { - Self::with_unit(inches, LengthUnit::In) - } - - /// Get the value of this length in raw units. - pub const fn to_raw(self) -> f64 { - (self.0).0 - } - - /// Get the value of this length in a unit. - pub fn to_unit(self, unit: LengthUnit) -> f64 { - self.to_raw() / unit.raw_scale() - } - - /// Convert this to a number of points. - pub fn to_pt(self) -> f64 { - self.to_unit(LengthUnit::Pt) - } - - /// Convert this to a number of millimeters. - pub fn to_mm(self) -> f64 { - self.to_unit(LengthUnit::Mm) - } - - /// Convert this to a number of centimeters. - pub fn to_cm(self) -> f64 { - self.to_unit(LengthUnit::Cm) - } - - /// Convert this to a number of inches. - pub fn to_inches(self) -> f64 { - self.to_unit(LengthUnit::In) - } - - /// The absolute value of this length. - pub fn abs(self) -> Self { - Self::raw(self.to_raw().abs()) - } - - /// The minimum of this and another length. - pub fn min(self, other: Self) -> Self { - Self(self.0.min(other.0)) - } - - /// Set to the minimum of this and another length. - pub fn set_min(&mut self, other: Self) { - *self = (*self).min(other); - } - - /// The maximum of this and another length. - pub fn max(self, other: Self) -> Self { - Self(self.0.max(other.0)) + /// 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 + } } +} - /// Set to the maximum of this and another length. - pub fn set_max(&mut self, other: Self) { - *self = (*self).max(other); +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), + } } +} - /// Whether the other 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 +impl Numeric for Length { + fn zero() -> Self { + Self::zero() } - /// Compares two lengths for whether they are approximately equal. - pub fn approx_eq(self, other: Self) -> bool { - self == other || (self - other).to_raw().abs() < 1e-6 + fn is_finite(self) -> bool { + self.abs.is_finite() && self.em.is_finite() } +} - /// 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) +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 { - Self::zero() + None } } } -impl Numeric for Length { - fn zero() -> Self { - Self::zero() +impl From<Abs> for Length { + fn from(abs: Abs) -> Self { + Self { abs, em: Em::zero() } } +} - fn is_finite(self) -> bool { - self.0.is_finite() +impl From<Em> for Length { + fn from(em: Em) -> Self { + Self { abs: Abs::zero(), em } } } -impl Debug for Length { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}pt", round_2(self.to_pt())) +impl From<Abs> for Rel<Length> { + fn from(abs: Abs) -> Self { + Rel::from(Length::from(abs)) } } impl Neg for Length { type Output = Self; - fn neg(self) -> Self { - Self(-self.0) + fn neg(self) -> Self::Output { + Self { abs: -self.abs, em: -self.em } } } impl Add for Length { type Output = Self; - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) + fn add(self, rhs: Self) -> Self::Output { + Self { + abs: self.abs + rhs.abs, + em: self.em + rhs.em, + } } } @@ -160,32 +104,16 @@ sub_impl!(Length - Length -> Length); impl Mul<f64> for Length { type Output = Self; - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul<Length> for f64 { - type Output = Length; - - fn mul(self, other: Length) -> Length { - other * 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, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Length { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.to_raw() / other.to_raw() + fn div(self, rhs: f64) -> Self::Output { + Self { abs: self.abs / rhs, em: self.em / rhs } } } @@ -193,69 +121,3 @@ assign_impl!(Length += Length); assign_impl!(Length -= Length); assign_impl!(Length *= f64); assign_impl!(Length /= f64); - -impl Rem for Length { - type Output = Self; - - fn rem(self, other: Self) -> Self::Output { - Self(self.0 % other.0) - } -} - -impl Sum for Length { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -impl<'a> Sum<&'a Self> for Length { - fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -/// Different units of length measurement. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum LengthUnit { - /// Points. - Pt, - /// Millimeters. - Mm, - /// Centimeters. - Cm, - /// Inches. - In, -} - -impl LengthUnit { - /// How many raw units correspond to a value of `1.0` in this unit. - fn raw_scale(self) -> f64 { - match self { - LengthUnit::Pt => 1.0, - LengthUnit::Mm => 2.83465, - LengthUnit::Cm => 28.3465, - LengthUnit::In => 72.0, - } - } -} - -impl Debug for LengthUnit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - LengthUnit::Mm => "mm", - LengthUnit::Pt => "pt", - LengthUnit::Cm => "cm", - LengthUnit::In => "in", - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_length_unit_conversion() { - assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4); - } -} diff --git a/src/geom/mod.rs b/src/geom/mod.rs index fc8ccb6c..6e2b8f9b 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -2,42 +2,46 @@ #[macro_use] mod macros; +mod abs; mod align; mod angle; +mod axes; mod corners; mod dir; +mod ellipse; mod em; -mod fraction; -mod gen; +mod fr; mod length; mod paint; mod path; mod point; mod ratio; -mod rect; -mod relative; +mod rel; +mod rounded; mod scalar; mod sides; -mod spec; +mod size; mod transform; +pub use abs::*; pub use align::*; pub use angle::*; +pub use axes::*; pub use corners::*; pub use dir::*; +pub use ellipse::*; pub use em::*; -pub use fraction::*; -pub use gen::*; +pub use fr::*; pub use length::*; pub use paint::*; pub use path::*; pub use point::*; pub use ratio::*; -pub use rect::*; -pub use relative::*; +pub use rel::*; +pub use rounded::*; pub use scalar::*; pub use sides::*; -pub use spec::*; +pub use size::*; pub use transform::*; use std::cmp::Ordering; @@ -82,8 +86,6 @@ pub enum Geometry { Line(Point), /// A rectangle with its origin in the topleft corner. Rect(Size), - /// A ellipse with its origin in the topleft corner. - Ellipse(Size), /// A bezier path. Path(Path), } diff --git a/src/geom/paint.rs b/src/geom/paint.rs index 02ae3ee5..c5bbefa4 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -394,14 +394,14 @@ pub struct Stroke { /// The stroke's paint. pub paint: Paint, /// The stroke's thickness. - pub thickness: Length, + pub thickness: Abs, } impl Default for Stroke { fn default() -> Self { Self { paint: Paint::Solid(Color::BLACK.into()), - thickness: Length::pt(1.0), + thickness: Abs::pt(1.0), } } } diff --git a/src/geom/path.rs b/src/geom/path.rs index d0c3c75d..ffd3db1c 100644 --- a/src/geom/path.rs +++ b/src/geom/path.rs @@ -21,7 +21,7 @@ impl Path { /// Create a path that describes a rectangle. pub fn rect(size: Size) -> Self { - let z = Length::zero(); + let z = Abs::zero(); let point = Point::new; let mut path = Self::new(); path.move_to(point(z, z)); @@ -32,25 +32,6 @@ impl Path { path } - /// Create a path that approximates an axis-aligned ellipse. - pub fn ellipse(size: Size) -> Self { - // https://stackoverflow.com/a/2007782 - let z = Length::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 = Self::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)); - path - } - /// Push a [`MoveTo`](PathElement::MoveTo) element. pub fn move_to(&mut self, p: Point) { self.0.push(PathElement::MoveTo(p)); @@ -71,22 +52,3 @@ impl Path { self.0.push(PathElement::ClosePath); } } - -/// 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. -pub 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] -} diff --git a/src/geom/point.rs b/src/geom/point.rs index 7f67d353..34d3dcd8 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -4,35 +4,35 @@ use super::*; #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Point { /// The x coordinate. - pub x: Length, + pub x: Abs, /// The y coordinate. - pub y: Length, + pub y: Abs, } impl Point { /// The origin point. pub const fn zero() -> Self { - Self { x: Length::zero(), y: Length::zero() } + Self { x: Abs::zero(), y: Abs::zero() } } /// Create a new point from x and y coordinates. - pub const fn new(x: Length, y: Length) -> Self { + pub const fn new(x: Abs, y: Abs) -> Self { Self { x, y } } /// Create an instance with two equal components. - pub const fn splat(value: Length) -> Self { + 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: Length) -> Self { - Self { x, y: Length::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: Length) -> Self { - Self { x: Length::zero(), y } + pub const fn with_y(y: Abs) -> Self { + Self { x: Abs::zero(), y } } /// Transform the point with the given transformation. @@ -54,20 +54,20 @@ impl Numeric for Point { } } -impl Get<SpecAxis> for Point { - type Component = Length; +impl Get<Axis> for Point { + type Component = Abs; - fn get(self, axis: SpecAxis) -> Length { + fn get(self, axis: Axis) -> Abs { match axis { - SpecAxis::Horizontal => self.x, - SpecAxis::Vertical => self.y, + Axis::X => self.x, + Axis::Y => self.y, } } - fn get_mut(&mut self, axis: SpecAxis) -> &mut Length { + fn get_mut(&mut self, axis: Axis) -> &mut Abs { match axis { - SpecAxis::Horizontal => &mut self.x, - SpecAxis::Vertical => &mut self.y, + Axis::X => &mut self.x, + Axis::Y => &mut self.y, } } } diff --git a/src/geom/rect.rs b/src/geom/rect.rs deleted file mode 100644 index dfea2c45..00000000 --- a/src/geom/rect.rs +++ /dev/null @@ -1,184 +0,0 @@ -use super::*; - -use std::mem; - -/// A rectangle with rounded corners. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct RoundedRect { - /// The size of the rectangle. - pub size: Size, - /// The radius at each corner. - pub radius: Corners<Length>, -} - -impl RoundedRect { - /// Create a new rounded rectangle. - pub fn new(size: Size, radius: Corners<Length>) -> Self { - Self { size, radius } - } - - /// Output all constituent shapes of the rectangle in order. The last one is - /// in the foreground. The function will output multiple items if the stroke - /// properties differ by side. - pub fn shapes( - self, - 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: self.fill_geometry(), - fill, - stroke: if stroke.is_uniform() { stroke.top } else { None }, - }); - } - - if !stroke.is_uniform() { - for (path, stroke) in self.stroke_segments(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(self) -> Geometry { - if self.radius.iter().copied().all(Length::is_zero) { - Geometry::Rect(self.size) - } else { - let mut paths = self.stroke_segments(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( - self, - strokes: 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; - - for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] { - let continuous = strokes.get(side) == strokes.get(side.next_cw()); - connection = connection.advance(continuous && side != Side::Left); - always_continuous &= continuous; - - draw_side( - &mut path, - side, - self.size, - self.radius.get(side.start_corner()), - self.radius.get(side.end_corner()), - connection, - ); - - if !continuous { - res.push((mem::take(&mut path), strokes.get(side))); - } - } - - if always_continuous { - path.close_path(); - } - - if !path.0.is_empty() { - res.push((path, strokes.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: Length, - end_radius: Length, - 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(Length::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, Length::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]); - } -} - -/// 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/relative.rs b/src/geom/rel.rs index 42e60d39..5c3b0b43 100644 --- a/src/geom/relative.rs +++ b/src/geom/rel.rs @@ -2,14 +2,14 @@ use super::*; /// A value that is composed of a relative and an absolute part. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Relative<T: Numeric> { +pub struct Rel<T: Numeric> { /// The relative part. pub rel: Ratio, /// The absolute part. pub abs: T, } -impl<T: Numeric> Relative<T> { +impl<T: Numeric> Rel<T> { /// The zero relative. pub fn zero() -> Self { Self { rel: Ratio::zero(), abs: T::zero() } @@ -41,16 +41,29 @@ impl<T: Numeric> Relative<T> { } /// Map the absolute part with `f`. - pub fn map<F, U>(self, f: F) -> Relative<U> + pub fn map<F, U>(self, f: F) -> Rel<U> where F: FnOnce(T) -> U, U: Numeric, { - Relative { rel: self.rel, abs: f(self.abs) } + Rel { rel: self.rel, abs: f(self.abs) } } } -impl<T: Numeric> Debug for Relative<T> { +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), @@ -60,19 +73,19 @@ impl<T: Numeric> Debug for Relative<T> { } } -impl<T: Numeric> From<T> for Relative<T> { +impl<T: Numeric> From<T> for Rel<T> { fn from(abs: T) -> Self { Self { rel: Ratio::zero(), abs } } } -impl<T: Numeric> From<Ratio> for Relative<T> { +impl<T: Numeric> From<Ratio> for Rel<T> { fn from(rel: Ratio) -> Self { Self { rel, abs: T::zero() } } } -impl<T: Numeric + PartialOrd> PartialOrd for Relative<T> { +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) @@ -84,7 +97,7 @@ impl<T: Numeric + PartialOrd> PartialOrd for Relative<T> { } } -impl<T: Numeric> Neg for Relative<T> { +impl<T: Numeric> Neg for Rel<T> { type Output = Self; fn neg(self) -> Self { @@ -92,7 +105,7 @@ impl<T: Numeric> Neg for Relative<T> { } } -impl<T: Numeric> Add for Relative<T> { +impl<T: Numeric> Add for Rel<T> { type Output = Self; fn add(self, other: Self) -> Self::Output { @@ -103,7 +116,7 @@ impl<T: Numeric> Add for Relative<T> { } } -impl<T: Numeric> Sub for Relative<T> { +impl<T: Numeric> Sub for Rel<T> { type Output = Self; fn sub(self, other: Self) -> Self::Output { @@ -111,7 +124,7 @@ impl<T: Numeric> Sub for Relative<T> { } } -impl<T: Numeric> Mul<f64> for Relative<T> { +impl<T: Numeric> Mul<f64> for Rel<T> { type Output = Self; fn mul(self, other: f64) -> Self::Output { @@ -122,15 +135,15 @@ impl<T: Numeric> Mul<f64> for Relative<T> { } } -impl<T: Numeric> Mul<Relative<T>> for f64 { - type Output = Relative<T>; +impl<T: Numeric> Mul<Rel<T>> for f64 { + type Output = Rel<T>; - fn mul(self, other: Relative<T>) -> Self::Output { + fn mul(self, other: Rel<T>) -> Self::Output { other * self } } -impl<T: Numeric> Div<f64> for Relative<T> { +impl<T: Numeric> Div<f64> for Rel<T> { type Output = Self; fn div(self, other: f64) -> Self::Output { @@ -141,28 +154,28 @@ impl<T: Numeric> Div<f64> for Relative<T> { } } -impl<T: Numeric + AddAssign> AddAssign for Relative<T> { +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 Relative<T> { +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 Relative<T> { +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 Relative<T> { +impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Rel<T> { fn div_assign(&mut self, other: f64) { self.rel /= other; self.abs /= other; @@ -170,25 +183,25 @@ impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Relative<T> { } impl<T: Numeric> Add<T> for Ratio { - type Output = Relative<T>; + type Output = Rel<T>; fn add(self, other: T) -> Self::Output { - Relative::from(self) + Relative::from(other) + Rel::from(self) + Rel::from(other) } } -impl<T: Numeric> Add<T> for Relative<T> { +impl<T: Numeric> Add<T> for Rel<T> { type Output = Self; fn add(self, other: T) -> Self::Output { - self + Relative::from(other) + self + Rel::from(other) } } -impl<T: Numeric> Add<Ratio> for Relative<T> { +impl<T: Numeric> Add<Ratio> for Rel<T> { type Output = Self; fn add(self, other: Ratio) -> Self::Output { - self + Relative::from(other) + self + Rel::from(other) } } diff --git a/src/geom/rounded.rs b/src/geom/rounded.rs new file mode 100644 index 00000000..70d351ee --- /dev/null +++ b/src/geom/rounded.rs @@ -0,0 +1,187 @@ +use super::*; + +use std::mem; + +/// 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 } 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; + + for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] { + let continuous = stroke.get(side) == stroke.get(side.next_cw()); + connection = connection.advance(continuous && side != Side::Left); + always_continuous &= continuous; + + draw_side( + &mut path, + side, + size, + radius.get(side.start_corner()), + radius.get(side.end_corner()), + connection, + ); + + if !continuous { + res.push((mem::take(&mut path), stroke.get(side))); + } + } + + 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/sides.rs b/src/geom/sides.rs index 72748916..9b8d9a6b 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -74,14 +74,14 @@ impl<T> Sides<T> { impl<T: Add> Sides<T> { /// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`. - pub fn sum_by_axis(self) -> Spec<T::Output> { - Spec::new(self.left + self.right, self.top + self.bottom) + pub fn sum_by_axis(self) -> Axes<T::Output> { + Axes::new(self.left + self.right, self.top + self.bottom) } } -impl Sides<Relative<Length>> { +impl Sides<Rel<Abs>> { /// Evaluate the sides relative to the given `size`. - pub fn relative_to(self, size: Size) -> Sides<Length> { + pub fn relative_to(self, size: Size) -> Sides<Abs> { Sides { left: self.left.relative_to(size.x), top: self.top.relative_to(size.y), @@ -173,10 +173,10 @@ impl Side { } /// Return the corresponding axis. - pub fn axis(self) -> SpecAxis { + pub fn axis(self) -> Axis { match self { - Self::Left | Self::Right => SpecAxis::Vertical, - Self::Top | Self::Bottom => SpecAxis::Horizontal, + Self::Left | Self::Right => Axis::Y, + Self::Top | Self::Bottom => Axis::X, } } } diff --git a/src/geom/size.rs b/src/geom/size.rs new file mode 100644 index 00000000..a2e32b77 --- /dev/null +++ b/src/geom/size.rs @@ -0,0 +1,78 @@ +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/spec.rs b/src/geom/spec.rs deleted file mode 100644 index e80df870..00000000 --- a/src/geom/spec.rs +++ /dev/null @@ -1,358 +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 Spec<T> { - /// The horizontal component. - pub x: T, - /// The vertical component. - pub y: T, -} - -impl<T> Spec<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) -> Spec<U> - where - F: FnMut(T) -> U, - { - Spec { x: f(self.x), y: f(self.y) } - } - - /// Convert from `&Spec<T>` to `Spec<&T>`. - pub fn as_ref(&self) -> Spec<&T> { - Spec { x: &self.x, y: &self.y } - } - - /// Convert from `&Spec<T>` to `Spec<&<T as Deref>::Target>`. - pub fn as_deref(&self) -> Spec<&T::Target> - where - T: Deref, - { - Spec { x: &self.x, y: &self.y } - } - - /// Convert from `&mut Spec<T>` to `Spec<&mut T>`. - pub fn as_mut(&mut self) -> Spec<&mut T> { - Spec { x: &mut self.x, y: &mut self.y } - } - - /// Zip two instances into an instance over a tuple. - pub fn zip<U>(self, other: Spec<U>) -> Spec<(T, U)> { - Spec { - 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: Spec<bool>) -> Spec<Option<T>> { - Spec { - x: if mask.x { Some(self.x) } else { None }, - y: if mask.y { Some(self.y) } else { None }, - } - } - - /// Convert to the generic representation. - pub fn to_gen(self, main: SpecAxis) -> Gen<T> { - match main { - SpecAxis::Horizontal => Gen::new(self.y, self.x), - SpecAxis::Vertical => Gen::new(self.x, self.y), - } - } -} - -impl<T: Default> Spec<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> Spec<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), - } - } -} - -impl<T> Get<SpecAxis> for Spec<T> { - type Component = T; - - fn get(self, axis: SpecAxis) -> T { - match axis { - SpecAxis::Horizontal => self.x, - SpecAxis::Vertical => self.y, - } - } - - fn get_mut(&mut self, axis: SpecAxis) -> &mut T { - match axis { - SpecAxis::Horizontal => &mut self.x, - SpecAxis::Vertical => &mut self.y, - } - } -} - -impl<T> Debug for Spec<T> -where - T: Debug + 'static, -{ - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Spec { x: Some(x), y: Some(y) } = - self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<Align>()) - { - write!(f, "{:?}-{:?}", x, y) - } else if (&self.x as &dyn Any).is::<Length>() { - write!(f, "Size({:?}, {:?})", self.x, self.y) - } else { - write!(f, "Spec({:?}, {:?})", self.x, self.y) - } - } -} - -/// The two specific layouting axes. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum SpecAxis { - /// The horizontal layouting axis. - Horizontal, - /// The vertical layouting axis. - Vertical, -} - -impl SpecAxis { - /// The direction with the given positivity for this axis. - pub fn dir(self, positive: bool) -> Dir { - match (self, positive) { - (Self::Horizontal, true) => Dir::LTR, - (Self::Horizontal, false) => Dir::RTL, - (Self::Vertical, true) => Dir::TTB, - (Self::Vertical, false) => Dir::BTT, - } - } - - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::Horizontal => Self::Vertical, - Self::Vertical => Self::Horizontal, - } - } -} - -impl Debug for SpecAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Horizontal => "horizontal", - Self::Vertical => "vertical", - }) - } -} - -/// A size in 2D. -pub type Size = Spec<Length>; - -impl Size { - /// The zero value. - pub const fn zero() -> Self { - Self { x: Length::zero(), y: Length::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); - -impl<T> Spec<Option<T>> { - /// Whether the individual fields are some. - pub fn map_is_some(&self) -> Spec<bool> { - self.as_ref().map(Option::is_some) - } - - /// Whether the individual fields are none. - pub fn map_is_none(&self) -> Spec<bool> { - self.as_ref().map(Option::is_none) - } - - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Spec<T>) -> Spec<T> { - Spec { - x: self.x.unwrap_or(other.x), - y: self.y.unwrap_or(other.y), - } - } -} - -impl Spec<bool> { - /// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`. - pub fn select<T>(self, t: Spec<T>, f: Spec<T>) -> Spec<T> { - Spec { - x: if self.x { t.x } else { f.x }, - y: if self.y { t.y } else { f.y }, - } - } -} - -impl Not for Spec<bool> { - type Output = Self; - - fn not(self) -> Self::Output { - Self { x: !self.x, y: !self.y } - } -} - -impl BitOr for Spec<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 Spec<bool> { - type Output = Self; - - fn bitor(self, rhs: bool) -> Self::Output { - Self { x: self.x | rhs, y: self.y | rhs } - } -} - -impl BitAnd for Spec<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 Spec<bool> { - type Output = Self; - - fn bitand(self, rhs: bool) -> Self::Output { - Self { x: self.x & rhs, y: self.y & rhs } - } -} - -impl BitOrAssign for Spec<bool> { - fn bitor_assign(&mut self, rhs: Self) { - self.x |= rhs.x; - self.y |= rhs.y; - } -} - -impl BitAndAssign for Spec<bool> { - fn bitand_assign(&mut self, rhs: Self) { - self.x &= rhs.x; - self.y &= rhs.y; - } -} diff --git a/src/geom/transform.rs b/src/geom/transform.rs index b82807fd..1ff1dfdd 100644 --- a/src/geom/transform.rs +++ b/src/geom/transform.rs @@ -7,8 +7,8 @@ pub struct Transform { pub ky: Ratio, pub kx: Ratio, pub sy: Ratio, - pub tx: Length, - pub ty: Length, + pub tx: Abs, + pub ty: Abs, } impl Transform { @@ -19,13 +19,13 @@ impl Transform { ky: Ratio::zero(), kx: Ratio::zero(), sy: Ratio::one(), - tx: Length::zero(), - ty: Length::zero(), + tx: Abs::zero(), + ty: Abs::zero(), } } /// A translate transform. - pub const fn translate(tx: Length, ty: Length) -> Self { + pub const fn translate(tx: Abs, ty: Abs) -> Self { Self { tx, ty, ..Self::identity() } } |
