summaryrefslogtreecommitdiff
path: root/src/geom
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-10-28 16:43:38 +0200
committerLaurenz <laurmaedje@gmail.com>2022-10-28 16:43:38 +0200
commit95e9134a3c7d7a14d8c8928413fdffc665671895 (patch)
tree822b5f6c2d23aba33cfe4d199405e493e37c3d70 /src/geom
parent66030ae5d73d85a0463562230b87f8ec7554c746 (diff)
Refactor `geom` module
Diffstat (limited to 'src/geom')
-rw-r--r--src/geom/abs.rs261
-rw-r--r--src/geom/align.rs21
-rw-r--r--src/geom/axes.rs263
-rw-r--r--src/geom/dir.rs6
-rw-r--r--src/geom/ellipse.rs26
-rw-r--r--src/geom/em.rs10
-rw-r--r--src/geom/fr.rs (renamed from src/geom/fraction.rs)40
-rw-r--r--src/geom/gen.rs99
-rw-r--r--src/geom/length.rs266
-rw-r--r--src/geom/mod.rs26
-rw-r--r--src/geom/paint.rs4
-rw-r--r--src/geom/path.rs40
-rw-r--r--src/geom/point.rs34
-rw-r--r--src/geom/rect.rs184
-rw-r--r--src/geom/rel.rs (renamed from src/geom/relative.rs)65
-rw-r--r--src/geom/rounded.rs187
-rw-r--r--src/geom/sides.rs14
-rw-r--r--src/geom/size.rs78
-rw-r--r--src/geom/spec.rs358
-rw-r--r--src/geom/transform.rs10
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() }
}