summaryrefslogtreecommitdiff
path: root/src/geom
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-10-10 22:19:36 +0200
committerLaurenz <laurmaedje@gmail.com>2020-10-10 22:19:36 +0200
commit92c01da36016e94ff20163806ddcbcf7e33d4031 (patch)
tree1a900b3c11edcc93e9153fada3ce92310db5768b /src/geom
parent42500d5ed85539c5ab04dd3544beaf802da29be9 (diff)
Switch back to custom geometry types, unified with layout primitives 🏞
Diffstat (limited to 'src/geom')
-rw-r--r--src/geom/align.rs48
-rw-r--r--src/geom/dir.rs83
-rw-r--r--src/geom/gen.rs90
-rw-r--r--src/geom/length.rs213
-rw-r--r--src/geom/linear.rs172
-rw-r--r--src/geom/macros.rs47
-rw-r--r--src/geom/mod.rs53
-rw-r--r--src/geom/point.rs102
-rw-r--r--src/geom/relative.rs92
-rw-r--r--src/geom/sides.rs101
-rw-r--r--src/geom/size.rs119
-rw-r--r--src/geom/spec.rs105
12 files changed, 1225 insertions, 0 deletions
diff --git a/src/geom/align.rs b/src/geom/align.rs
new file mode 100644
index 00000000..1030a133
--- /dev/null
+++ b/src/geom/align.rs
@@ -0,0 +1,48 @@
+use super::*;
+
+/// Where to align something along a directed axis.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum Align {
+ /// Align at the start of the axis.
+ Start,
+ /// Align in the middle of the axis.
+ Center,
+ /// Align at the end of the axis.
+ End,
+}
+
+impl Align {
+ /// Returns the position of this alignment in the given range.
+ pub fn apply(self, range: Range<Length>) -> Length {
+ match self {
+ Self::Start => range.start,
+ Self::Center => (range.start + range.end) / 2.0,
+ Self::End => range.end,
+ }
+ }
+
+ /// The inverse alignment.
+ pub fn inv(self) -> Self {
+ match self {
+ Self::Start => Self::End,
+ Self::Center => Self::Center,
+ Self::End => Self::Start,
+ }
+ }
+}
+
+impl Default for Align {
+ fn default() -> Self {
+ Self::Start
+ }
+}
+
+impl Display for Align {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Start => "start",
+ Self::Center => "center",
+ Self::End => "end",
+ })
+ }
+}
diff --git a/src/geom/dir.rs b/src/geom/dir.rs
new file mode 100644
index 00000000..cfcb4c09
--- /dev/null
+++ b/src/geom/dir.rs
@@ -0,0 +1,83 @@
+use super::*;
+
+/// The four directions into which content can be laid out.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Dir {
+ /// Left to right.
+ LTR,
+ /// Right to left.
+ RTL,
+ /// Top to bottom.
+ TTB,
+ /// Bottom to top.
+ BTT,
+}
+
+impl Dir {
+ /// The side this direction starts at.
+ pub fn start(self) -> Side {
+ match self {
+ Self::LTR => Side::Left,
+ Self::RTL => Side::Right,
+ Self::TTB => Side::Top,
+ Self::BTT => Side::Bottom,
+ }
+ }
+
+ /// The side this direction ends at.
+ pub fn end(self) -> Side {
+ match self {
+ Self::LTR => Side::Right,
+ Self::RTL => Side::Left,
+ Self::TTB => Side::Bottom,
+ Self::BTT => Side::Top,
+ }
+ }
+
+ /// The specific axis this direction belongs to.
+ pub fn axis(self) -> SpecAxis {
+ match self {
+ Self::LTR | Self::RTL => SpecAxis::Horizontal,
+ Self::TTB | Self::BTT => SpecAxis::Vertical,
+ }
+ }
+
+ /// Whether this direction points into the positive coordinate direction.
+ ///
+ /// The positive directions are left-to-right and top-to-bottom.
+ pub fn is_positive(self) -> bool {
+ match self {
+ Self::LTR | Self::TTB => true,
+ Self::RTL | Self::BTT => false,
+ }
+ }
+
+ /// The factor for this direction.
+ ///
+ /// - `1.0` if the direction is positive.
+ /// - `-1.0` if the direction is negative.
+ pub fn factor(self) -> f64 {
+ if self.is_positive() { 1.0 } else { -1.0 }
+ }
+
+ /// The inverse direction.
+ pub fn inv(self) -> Self {
+ match self {
+ Self::LTR => Self::RTL,
+ Self::RTL => Self::LTR,
+ Self::TTB => Self::BTT,
+ Self::BTT => Self::TTB,
+ }
+ }
+}
+
+impl Display for Dir {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::LTR => "ltr",
+ Self::RTL => "rtl",
+ Self::TTB => "ttb",
+ Self::BTT => "btt",
+ })
+ }
+}
diff --git a/src/geom/gen.rs b/src/geom/gen.rs
new file mode 100644
index 00000000..d877713b
--- /dev/null
+++ b/src/geom/gen.rs
@@ -0,0 +1,90 @@
+use super::*;
+
+/// A container with a main and cross component.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
+pub struct Gen<T> {
+ /// The main component.
+ pub main: T,
+ /// The cross component.
+ pub cross: T,
+}
+
+impl<T> Gen<T> {
+ /// Create a new instance from the two components.
+ pub fn new(main: T, cross: T) -> Self {
+ Self { main, cross }
+ }
+}
+
+impl Gen<Length> {
+ /// The zero value.
+ pub const ZERO: Self = Self { main: Length::ZERO, cross: Length::ZERO };
+}
+
+impl<T> Get<GenAxis> for Gen<T> {
+ type Component = T;
+
+ fn get(self, axis: GenAxis) -> T {
+ match axis {
+ GenAxis::Main => self.main,
+ GenAxis::Cross => self.cross,
+ }
+ }
+
+ fn get_mut(&mut self, axis: GenAxis) -> &mut T {
+ match axis {
+ GenAxis::Main => &mut self.main,
+ GenAxis::Cross => &mut self.cross,
+ }
+ }
+}
+
+impl<T> Switch for Gen<T> {
+ type Other = Spec<T>;
+
+ fn switch(self, dirs: Gen<Dir>) -> Self::Other {
+ match dirs.main.axis() {
+ SpecAxis::Horizontal => Spec::new(self.main, self.cross),
+ SpecAxis::Vertical => Spec::new(self.cross, self.main),
+ }
+ }
+}
+
+/// The two generic layouting axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum GenAxis {
+ /// The axis pages and paragraphs are set along.
+ Main,
+ /// The axis words and lines are set along.
+ Cross,
+}
+
+impl GenAxis {
+ /// The other axis.
+ pub fn other(self) -> Self {
+ match self {
+ Self::Main => Self::Cross,
+ Self::Cross => Self::Main,
+ }
+ }
+}
+
+impl Switch for GenAxis {
+ type Other = SpecAxis;
+
+ fn switch(self, dirs: Gen<Dir>) -> Self::Other {
+ match self {
+ Self::Main => dirs.main.axis(),
+ Self::Cross => dirs.cross.axis(),
+ }
+ }
+}
+
+impl Display for GenAxis {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Main => "main",
+ Self::Cross => "cross",
+ })
+ }
+}
diff --git a/src/geom/length.rs b/src/geom/length.rs
new file mode 100644
index 00000000..60ccce2b
--- /dev/null
+++ b/src/geom/length.rs
@@ -0,0 +1,213 @@
+use super::*;
+
+/// An absolute length.
+#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
+pub struct Length {
+ /// The length in raw units.
+ raw: f64,
+}
+
+impl Length {
+ /// The zero length.
+ pub const ZERO: Self = Self { raw: 0.0 };
+
+ /// Create a length from a number of points.
+ pub fn pt(pt: f64) -> Self {
+ Self::with_unit(pt, Unit::Pt)
+ }
+
+ /// Create a length from a number of millimeters.
+ pub fn mm(mm: f64) -> Self {
+ Self::with_unit(mm, Unit::Mm)
+ }
+
+ /// Create a length from a number of centimeters.
+ pub fn cm(cm: f64) -> Self {
+ Self::with_unit(cm, Unit::Cm)
+ }
+
+ /// Create a length from a number of inches.
+ pub fn inches(inches: f64) -> Self {
+ Self::with_unit(inches, Unit::In)
+ }
+
+ /// Create a length from a number of raw units.
+ pub fn raw(raw: f64) -> Self {
+ Self { raw }
+ }
+
+ /// Convert this to a number of points.
+ pub fn to_pt(self) -> f64 {
+ self.to_unit(Unit::Pt)
+ }
+
+ /// Convert this to a number of millimeters.
+ pub fn to_mm(self) -> f64 {
+ self.to_unit(Unit::Mm)
+ }
+
+ /// Convert this to a number of centimeters.
+ pub fn to_cm(self) -> f64 {
+ self.to_unit(Unit::Cm)
+ }
+
+ /// Convert this to a number of inches.
+ pub fn to_inches(self) -> f64 {
+ self.to_unit(Unit::In)
+ }
+
+ /// Get the value of this length in raw units.
+ pub fn to_raw(self) -> f64 {
+ self.raw
+ }
+
+ /// Create a length from a value in a unit.
+ pub fn with_unit(val: f64, unit: Unit) -> Self {
+ Self { raw: val * unit.raw_scale() }
+ }
+
+ /// Get the value of this length in unit.
+ pub fn to_unit(self, unit: Unit) -> f64 {
+ self.raw / unit.raw_scale()
+ }
+
+ /// The minimum of this and another length.
+ pub fn min(self, other: Self) -> Self {
+ Self { raw: self.raw.min(other.raw) }
+ }
+
+ /// The maximum of this and another length.
+ pub fn max(self, other: Self) -> Self {
+ Self { raw: self.raw.max(other.raw) }
+ }
+}
+
+impl Display for Length {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ // Format small lengths as points and large ones as centimeters.
+ let (val, unit) = if self.to_pt().abs() < 25.0 {
+ (self.to_pt(), Unit::Pt)
+ } else {
+ (self.to_cm(), Unit::Cm)
+ };
+ write!(f, "{:.2}{}", val, unit)
+ }
+}
+
+impl Debug for Length {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+impl Neg for Length {
+ type Output = Self;
+
+ fn neg(self) -> Self {
+ Self { raw: -self.raw }
+ }
+}
+
+impl Add for Length {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ Self { raw: self.raw + other.raw }
+ }
+}
+
+sub_impl!(Length - Length -> Length);
+
+impl Mul<f64> for Length {
+ type Output = Self;
+
+ fn mul(self, other: f64) -> Self {
+ Self { raw: self.raw * other }
+ }
+}
+
+impl Mul<Length> for f64 {
+ type Output = Length;
+
+ fn mul(self, other: Length) -> Length {
+ other * self
+ }
+}
+
+impl Div<f64> for Length {
+ type Output = Self;
+
+ fn div(self, other: f64) -> Self {
+ Self { raw: self.raw / other }
+ }
+}
+
+assign_impl!(Length += Length);
+assign_impl!(Length -= Length);
+assign_impl!(Length *= f64);
+assign_impl!(Length /= f64);
+
+impl Sum for Length {
+ fn sum<I: Iterator<Item = Length>>(iter: I) -> Self {
+ iter.fold(Length::ZERO, Add::add)
+ }
+}
+
+/// Different units of measurement.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum Unit {
+ /// Points.
+ Pt,
+ /// Millimeters.
+ Mm,
+ /// Centimeters.
+ Cm,
+ /// Inches.
+ In,
+}
+
+impl Unit {
+ /// How many raw units correspond to a value of `1.0` in this unit.
+ fn raw_scale(self) -> f64 {
+ match self {
+ Unit::Pt => 1.0,
+ Unit::Mm => 2.83465,
+ Unit::Cm => 28.3465,
+ Unit::In => 72.0,
+ }
+ }
+}
+
+impl Display for Unit {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Unit::Mm => "mm",
+ Unit::Pt => "pt",
+ Unit::Cm => "cm",
+ Unit::In => "in",
+ })
+ }
+}
+
+impl Debug for Unit {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_length_formats_correctly() {
+ assert_eq!(Length::pt(-28.34).to_string(), "-1.00cm".to_string());
+ assert_eq!(Length::pt(23.0).to_string(), "23.00pt".to_string());
+ assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
+ }
+
+ #[test]
+ fn test_length_unit_conversion() {
+ assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4);
+ }
+}
diff --git a/src/geom/linear.rs b/src/geom/linear.rs
new file mode 100644
index 00000000..2567d264
--- /dev/null
+++ b/src/geom/linear.rs
@@ -0,0 +1,172 @@
+use super::*;
+
+/// A combined relative and absolute length.
+#[derive(Default, Copy, Clone, PartialEq)]
+pub struct Linear {
+ /// The relative part.
+ pub rel: Relative,
+ /// The absolute part.
+ pub abs: Length,
+}
+
+impl Linear {
+ /// The zero linear.
+ pub const ZERO: Linear = Linear { rel: Relative::ZERO, abs: Length::ZERO };
+
+ /// Create a new linear.
+ pub fn new(rel: Relative, abs: Length) -> Self {
+ Self { rel, abs }
+ }
+
+ /// Evaluate the linear length with `one` being `100%` for the relative
+ /// part.
+ pub fn eval(self, one: Length) -> Length {
+ self.rel.eval(one) + self.abs
+ }
+
+ /// Whether this linear's relative component is zero.
+ pub fn is_absolute(self) -> bool {
+ self.rel == Relative::ZERO
+ }
+}
+
+impl Display for Linear {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{} + {}", self.rel, self.abs)
+ }
+}
+
+impl Debug for Linear {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+impl From<Length> for Linear {
+ fn from(abs: Length) -> Self {
+ Self { rel: Relative::ZERO, abs }
+ }
+}
+
+impl From<Relative> for Linear {
+ fn from(rel: Relative) -> Self {
+ Self { rel, abs: Length::ZERO }
+ }
+}
+
+impl Neg for Linear {
+ type Output = Self;
+
+ fn neg(self) -> Self {
+ Self { rel: -self.rel, abs: -self.abs }
+ }
+}
+
+impl Add for Linear {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ Self {
+ rel: self.rel + other.rel,
+ abs: self.abs + other.abs,
+ }
+ }
+}
+
+impl Add<Relative> for Length {
+ type Output = Linear;
+
+ fn add(self, other: Relative) -> Linear {
+ Linear { rel: other, abs: self }
+ }
+}
+
+impl Add<Length> for Relative {
+ type Output = Linear;
+
+ fn add(self, other: Length) -> Linear {
+ other + self
+ }
+}
+
+impl Add<Length> for Linear {
+ type Output = Self;
+
+ fn add(self, other: Length) -> Self {
+ Self { rel: self.rel, abs: self.abs + other }
+ }
+}
+
+impl Add<Linear> for Length {
+ type Output = Linear;
+
+ fn add(self, other: Linear) -> Linear {
+ other + self
+ }
+}
+
+impl Add<Relative> for Linear {
+ type Output = Self;
+
+ fn add(self, other: Relative) -> Self {
+ Self { rel: self.rel + other, abs: self.abs }
+ }
+}
+
+impl Add<Linear> for Relative {
+ type Output = Linear;
+
+ fn add(self, other: Linear) -> Linear {
+ other + self
+ }
+}
+
+sub_impl!(Linear - Linear -> Linear);
+sub_impl!(Length - Relative -> Linear);
+sub_impl!(Relative - Length -> Linear);
+sub_impl!(Linear - Length -> Linear);
+sub_impl!(Length - Linear -> Linear);
+sub_impl!(Linear - Relative -> Linear);
+sub_impl!(Relative - Linear -> Linear);
+
+impl Mul<f64> for Linear {
+ type Output = Self;
+
+ fn mul(self, other: f64) -> Self {
+ Self {
+ rel: self.rel * other,
+ abs: self.abs * other,
+ }
+ }
+}
+
+impl Mul<Linear> for f64 {
+ type Output = Linear;
+
+ fn mul(self, other: Linear) -> Linear {
+ Linear {
+ rel: self * other.rel,
+ abs: self * other.abs,
+ }
+ }
+}
+
+impl Div<f64> for Linear {
+ type Output = Self;
+
+ fn div(self, other: f64) -> Self {
+ Self {
+ rel: self.rel / other,
+ abs: self.abs / other,
+ }
+ }
+}
+
+assign_impl!(Linear += Linear);
+assign_impl!(Linear += Length);
+assign_impl!(Linear += Relative);
+assign_impl!(Linear -= Linear);
+assign_impl!(Linear -= Length);
+assign_impl!(Linear -= Relative);
+assign_impl!(Linear *= f64);
+assign_impl!(Linear /= f64);
diff --git a/src/geom/macros.rs b/src/geom/macros.rs
new file mode 100644
index 00000000..615eb31c
--- /dev/null
+++ b/src/geom/macros.rs
@@ -0,0 +1,47 @@
+/// Implement the `Sub` trait based on existing `Neg` and `Add` impls.
+macro_rules! sub_impl {
+ ($a:ident - $b:ident -> $c:ident) => {
+ impl Sub<$b> for $a {
+ type Output = $c;
+
+ fn sub(self, other: $b) -> $c {
+ self + -other
+ }
+ }
+ };
+}
+
+/// Implement an assign trait based on an existing non-assign trait.
+macro_rules! assign_impl {
+ ($a:ident += $b:ident) => {
+ impl AddAssign<$b> for $a {
+ fn add_assign(&mut self, other: $b) {
+ *self = *self + other;
+ }
+ }
+ };
+
+ ($a:ident -= $b:ident) => {
+ impl SubAssign<$b> for $a {
+ fn sub_assign(&mut self, other: $b) {
+ *self = *self - other;
+ }
+ }
+ };
+
+ ($a:ident *= $b:ident) => {
+ impl MulAssign<$b> for $a {
+ fn mul_assign(&mut self, other: $b) {
+ *self = *self * other;
+ }
+ }
+ };
+
+ ($a:ident /= $b:ident) => {
+ impl DivAssign<$b> for $a {
+ fn div_assign(&mut self, other: $b) {
+ *self = *self / other;
+ }
+ }
+ };
+}
diff --git a/src/geom/mod.rs b/src/geom/mod.rs
new file mode 100644
index 00000000..c9c3040c
--- /dev/null
+++ b/src/geom/mod.rs
@@ -0,0 +1,53 @@
+//! Geometrical primitivies.
+
+#[macro_use]
+mod macros;
+mod align;
+mod dir;
+mod gen;
+mod length;
+mod linear;
+mod point;
+mod relative;
+mod sides;
+mod size;
+mod spec;
+
+pub use align::*;
+pub use dir::*;
+pub use gen::*;
+pub use length::*;
+pub use linear::*;
+pub use point::*;
+pub use relative::*;
+pub use sides::*;
+pub use size::*;
+pub use spec::*;
+
+use std::fmt::{self, Debug, Display, Formatter};
+use std::iter::Sum;
+use std::ops::*;
+
+/// Generic access to a structure's components.
+pub trait Get<Index> {
+ /// The structure's component type.
+ type Component;
+
+ /// Return the component for the specified index.
+ fn get(self, index: Index) -> Self::Component;
+
+ /// Borrow the component for the specified index mutably.
+ fn get_mut(&mut self, index: Index) -> &mut Self::Component;
+}
+
+/// Switch between the specific and generic representations of a type.
+///
+/// The generic representation deals with main and cross axes while the specific
+/// representation deals with horizontal and vertical axes.
+pub trait Switch {
+ /// The type of the other version.
+ type Other;
+
+ /// The other version of this type based on the current directions.
+ fn switch(self, dirs: Gen<Dir>) -> Self::Other;
+}
diff --git a/src/geom/point.rs b/src/geom/point.rs
new file mode 100644
index 00000000..31b84d81
--- /dev/null
+++ b/src/geom/point.rs
@@ -0,0 +1,102 @@
+use super::*;
+
+/// A point in 2D.
+#[derive(Default, Copy, Clone, PartialEq)]
+pub struct Point {
+ /// The x coordinate.
+ pub x: Length,
+ /// The y coordinate.
+ pub y: Length,
+}
+
+impl Point {
+ /// The origin point.
+ pub const ZERO: Self = Self { x: Length::ZERO, y: Length::ZERO };
+
+ /// Create a new point from x and y coordinate.
+ pub fn new(x: Length, y: Length) -> Self {
+ Self { x, y }
+ }
+}
+
+impl Get<SpecAxis> for Point {
+ type Component = Length;
+
+ fn get(self, axis: SpecAxis) -> Length {
+ match axis {
+ SpecAxis::Horizontal => self.x,
+ SpecAxis::Vertical => self.y,
+ }
+ }
+
+ fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
+ match axis {
+ SpecAxis::Horizontal => &mut self.x,
+ SpecAxis::Vertical => &mut self.y,
+ }
+ }
+}
+
+impl Switch for Point {
+ type Other = Gen<Length>;
+
+ fn switch(self, dirs: Gen<Dir>) -> Self::Other {
+ match dirs.main.axis() {
+ SpecAxis::Horizontal => Gen::new(self.x, self.y),
+ SpecAxis::Vertical => Gen::new(self.y, self.x),
+ }
+ }
+}
+
+impl Debug for Point {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "({}, {})", self.x, self.y)
+ }
+}
+
+impl Neg for Point {
+ type Output = Self;
+
+ fn neg(self) -> Self {
+ Self { x: -self.x, y: -self.y }
+ }
+}
+
+impl Add for Point {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ Self { x: self.x + other.x, y: self.y + other.y }
+ }
+}
+
+sub_impl!(Point - Point -> Point);
+
+impl Mul<f64> for Point {
+ type Output = Self;
+
+ fn mul(self, other: f64) -> Self {
+ Self { x: self.x * other, y: self.y * other }
+ }
+}
+
+impl Mul<Point> for f64 {
+ type Output = Point;
+
+ fn mul(self, other: Point) -> Point {
+ other * self
+ }
+}
+
+impl Div<f64> for Point {
+ type Output = Self;
+
+ fn div(self, other: f64) -> Self {
+ Self { x: self.x / other, y: self.y / other }
+ }
+}
+
+assign_impl!(Point += Point);
+assign_impl!(Point -= Point);
+assign_impl!(Point *= f64);
+assign_impl!(Point /= f64);
diff --git a/src/geom/relative.rs b/src/geom/relative.rs
new file mode 100644
index 00000000..037c83dc
--- /dev/null
+++ b/src/geom/relative.rs
@@ -0,0 +1,92 @@
+use super::*;
+
+/// A relative length.
+///
+/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
+/// corresponding [literal].
+///
+/// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent
+#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
+pub struct Relative(f64);
+
+impl Relative {
+ /// A ratio of `0%` represented as `0.0`.
+ pub const ZERO: Self = Self(0.0);
+
+ /// A ratio of `100%` represented as `1.0`.
+ pub const ONE: Self = Self(1.0);
+
+ /// Create a new relative value.
+ pub fn new(ratio: f64) -> Self {
+ Self(ratio)
+ }
+
+ /// Get the underlying ratio.
+ pub fn get(self) -> f64 {
+ self.0
+ }
+
+ /// Evaluate the relative length with `one` being `100%`.
+ pub fn eval(self, one: Length) -> Length {
+ self.get() * one
+ }
+}
+
+impl Display for Relative {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{:.2}%", self.0)
+ }
+}
+
+impl Debug for Relative {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+impl Neg for Relative {
+ type Output = Self;
+
+ fn neg(self) -> Self {
+ Self(-self.0)
+ }
+}
+
+impl Add for Relative {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ Self(self.0 + other.0)
+ }
+}
+
+sub_impl!(Relative - Relative -> Relative);
+
+impl Mul<f64> for Relative {
+ type Output = Self;
+
+ fn mul(self, other: f64) -> Self {
+ Self(self.0 * other)
+ }
+}
+
+impl Mul<Relative> for f64 {
+ type Output = Relative;
+
+ fn mul(self, other: Relative) -> Relative {
+ other * self
+ }
+}
+
+impl Div<f64> for Relative {
+ type Output = Self;
+
+ fn div(self, other: f64) -> Self {
+ Self(self.0 / other)
+ }
+}
+
+assign_impl!(Relative += Relative);
+assign_impl!(Relative -= Relative);
+assign_impl!(Relative *= f64);
+assign_impl!(Relative /= f64);
diff --git a/src/geom/sides.rs b/src/geom/sides.rs
new file mode 100644
index 00000000..770fad58
--- /dev/null
+++ b/src/geom/sides.rs
@@ -0,0 +1,101 @@
+use super::*;
+
+/// A container with left, top, right and bottom components.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
+pub struct Sides<T> {
+ /// The value for the left side.
+ pub left: T,
+ /// The value for the top side.
+ pub top: T,
+ /// The value for the right side.
+ pub right: T,
+ /// The value for the bottom side.
+ pub bottom: T,
+}
+
+impl<T> Sides<T> {
+ /// Create a new box from four sizes.
+ pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
+ Self { left, top, right, bottom }
+ }
+
+ /// Create an instance with all four components set to the same `value`.
+ pub fn uniform(value: T) -> Self
+ where
+ T: Clone,
+ {
+ Self {
+ left: value.clone(),
+ top: value.clone(),
+ right: value.clone(),
+ bottom: value,
+ }
+ }
+}
+
+impl Sides<Linear> {
+ /// Evaluate the linear values in this container.
+ pub fn eval(self, size: Size) -> Sides<Length> {
+ Sides {
+ left: self.left.eval(size.width),
+ top: self.top.eval(size.height),
+ right: self.right.eval(size.width),
+ bottom: self.bottom.eval(size.height),
+ }
+ }
+}
+
+impl Sides<Length> {
+ /// A size with `left` and `right` summed into `width`, and `top` and
+ /// `bottom` summed into `height`.
+ pub fn size(self) -> Size {
+ Size::new(self.left + self.right, self.top + self.bottom)
+ }
+}
+
+impl<T> Get<Side> for Sides<T> {
+ type Component = T;
+
+ fn get(self, side: Side) -> T {
+ match side {
+ Side::Left => self.left,
+ Side::Top => self.top,
+ Side::Right => self.right,
+ Side::Bottom => self.bottom,
+ }
+ }
+
+ fn get_mut(&mut self, side: Side) -> &mut T {
+ match side {
+ Side::Left => &mut self.left,
+ Side::Top => &mut self.top,
+ Side::Right => &mut self.right,
+ Side::Bottom => &mut self.bottom,
+ }
+ }
+}
+
+/// The four sides of objects.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Side {
+ /// The left side.
+ Left,
+ /// The top side.
+ Top,
+ /// The right side.
+ Right,
+ /// The bottom side.
+ Bottom,
+}
+
+impl Side {
+ /// The opposite side.
+ pub fn inv(self) -> Self {
+ match self {
+ Self::Left => Self::Right,
+ Self::Top => Self::Bottom,
+ Self::Right => Self::Left,
+ Self::Bottom => Self::Top,
+ }
+ }
+}
diff --git a/src/geom/size.rs b/src/geom/size.rs
new file mode 100644
index 00000000..8a3951f7
--- /dev/null
+++ b/src/geom/size.rs
@@ -0,0 +1,119 @@
+use super::*;
+
+/// A size in 2D.
+#[derive(Default, Copy, Clone, PartialEq)]
+pub struct Size {
+ /// The width.
+ pub width: Length,
+ /// The height.
+ pub height: Length,
+}
+
+impl Size {
+ /// The zero size.
+ pub const ZERO: Self = Self {
+ width: Length::ZERO,
+ height: Length::ZERO,
+ };
+
+ /// Create a new size from width and height.
+ pub fn new(width: Length, height: Length) -> Self {
+ Self { width, height }
+ }
+
+ /// Whether the other size fits into this one (smaller width and height).
+ pub fn fits(self, other: Self) -> bool {
+ self.width >= other.width && self.height >= other.height
+ }
+}
+
+impl Get<SpecAxis> for Size {
+ type Component = Length;
+
+ fn get(self, axis: SpecAxis) -> Length {
+ match axis {
+ SpecAxis::Horizontal => self.width,
+ SpecAxis::Vertical => self.height,
+ }
+ }
+
+ fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
+ match axis {
+ SpecAxis::Horizontal => &mut self.width,
+ SpecAxis::Vertical => &mut self.height,
+ }
+ }
+}
+
+impl Switch for Size {
+ type Other = Gen<Length>;
+
+ fn switch(self, dirs: Gen<Dir>) -> Self::Other {
+ match dirs.main.axis() {
+ SpecAxis::Horizontal => Gen::new(self.width, self.height),
+ SpecAxis::Vertical => Gen::new(self.height, self.width),
+ }
+ }
+}
+
+impl Debug for Size {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "({} x {})", self.width, self.height)
+ }
+}
+
+impl Neg for Size {
+ type Output = Self;
+
+ fn neg(self) -> Self {
+ Self { width: -self.width, height: -self.height }
+ }
+}
+
+impl Add for Size {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ Self {
+ width: self.width + other.width,
+ height: self.height + other.height,
+ }
+ }
+}
+
+sub_impl!(Size - Size -> Size);
+
+impl Mul<f64> for Size {
+ type Output = Self;
+
+ fn mul(self, other: f64) -> Self {
+ Self {
+ width: self.width * other,
+ height: self.height * 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 {
+ width: self.width / other,
+ height: self.height / 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
new file mode 100644
index 00000000..8a9519bc
--- /dev/null
+++ b/src/geom/spec.rs
@@ -0,0 +1,105 @@
+use super::*;
+
+/// A container with a horizontal and vertical component.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
+pub struct Spec<T> {
+ /// The horizontal component.
+ pub horizontal: T,
+ /// The vertical component.
+ pub vertical: T,
+}
+
+impl<T> Spec<T> {
+ /// Create a new instance from the two components.
+ pub fn new(horizontal: T, vertical: T) -> Self {
+ Self { horizontal, vertical }
+ }
+}
+
+impl Spec<Length> {
+ /// The zero value.
+ pub const ZERO: Self = Self {
+ horizontal: Length::ZERO,
+ vertical: Length::ZERO,
+ };
+
+ /// Convert to a point.
+ pub fn to_point(self) -> Point {
+ Point::new(self.horizontal, self.vertical)
+ }
+
+ /// Convert to a size.
+ pub fn to_size(self) -> Size {
+ Size::new(self.horizontal, self.vertical)
+ }
+}
+
+impl<T> Get<SpecAxis> for Spec<T> {
+ type Component = T;
+
+ fn get(self, axis: SpecAxis) -> T {
+ match axis {
+ SpecAxis::Horizontal => self.horizontal,
+ SpecAxis::Vertical => self.vertical,
+ }
+ }
+
+ fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
+ match axis {
+ SpecAxis::Horizontal => &mut self.horizontal,
+ SpecAxis::Vertical => &mut self.vertical,
+ }
+ }
+}
+
+impl<T> Switch for Spec<T> {
+ type Other = Gen<T>;
+
+ fn switch(self, dirs: Gen<Dir>) -> Self::Other {
+ match dirs.main.axis() {
+ SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical),
+ SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal),
+ }
+ }
+}
+
+/// The two specific layouting axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum SpecAxis {
+ /// The vertical layouting axis.
+ Vertical,
+ /// The horizontal layouting axis.
+ Horizontal,
+}
+
+impl SpecAxis {
+ /// The other axis.
+ pub fn other(self) -> Self {
+ match self {
+ Self::Horizontal => Self::Vertical,
+ Self::Vertical => Self::Horizontal,
+ }
+ }
+}
+
+impl Switch for SpecAxis {
+ type Other = GenAxis;
+
+ fn switch(self, dirs: Gen<Dir>) -> Self::Other {
+ if self == dirs.main.axis() {
+ GenAxis::Main
+ } else {
+ debug_assert_eq!(self, dirs.cross.axis());
+ GenAxis::Cross
+ }
+ }
+}
+
+impl Display for SpecAxis {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Vertical => "vertical",
+ Self::Horizontal => "horizontal",
+ })
+ }
+}