summaryrefslogtreecommitdiff
path: root/src/length.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/length.rs')
-rw-r--r--src/length.rs546
1 files changed, 154 insertions, 392 deletions
diff --git a/src/length.rs b/src/length.rs
index 8131a6e9..4c14c894 100644
--- a/src/length.rs
+++ b/src/length.rs
@@ -1,79 +1,113 @@
-//! Different-dimensional value and spacing types.
+//! A length type with a unit.
use std::fmt::{self, Debug, Display, Formatter};
-use std::iter::Sum;
-use std::ops::*;
use std::str::FromStr;
-use serde::Serialize;
-use crate::layout::prelude::*;
-
-/// A general spacing type.
-#[derive(Default, Copy, Clone, PartialEq, PartialOrd, Serialize)]
-#[serde(transparent)]
+/// A length with a unit.
+#[derive(Copy, Clone, PartialEq)]
pub struct Length {
- /// The length in typographic points (1/72 inches).
- pub points: f64,
+ /// The length in the given unit.
+ pub val: f64,
+ /// The unit of measurement.
+ pub unit: Unit,
+}
+
+/// Different units of measurement.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum Unit {
+ /// Points.
+ Pt,
+ /// Millimeters.
+ Mm,
+ /// Centimeters.
+ Cm,
+ /// Inches.
+ In,
+ /// Raw units (the implicit unit of all bare `f64` lengths).
+ Raw,
}
impl Length {
- /// The zeroed length.
- pub const ZERO: Length = Length { points: 0.0 };
-
- /// Create a length from an amount of points.
- pub fn pt(points: f64) -> Length { Length { points } }
+ /// Create a length from a value with a unit.
+ pub const fn new(val: f64, unit: Unit) -> Self {
+ Self { val, unit }
+ }
- /// Create a length from an amount of millimeters.
- pub fn mm(mm: f64) -> Length { Length { points: 2.83465 * mm } }
+ /// Create a length from a number of points.
+ pub const fn pt(pt: f64) -> Self {
+ Self::new(pt, Unit::Pt)
+ }
- /// Create a length from an amount of centimeters.
- pub fn cm(cm: f64) -> Length { Length { points: 28.3465 * cm } }
+ /// Create a length from a number of millimeters.
+ pub const fn mm(mm: f64) -> Self {
+ Self::new(mm, Unit::Mm)
+ }
- /// Create a length from an amount of inches.
- pub fn inches(inches: f64) -> Length { Length { points: 72.0 * inches } }
+ /// Create a length from a number of centimeters.
+ pub const fn cm(cm: f64) -> Self {
+ Self::new(cm, Unit::Cm)
+ }
- /// Convert this length into points.
- pub fn to_pt(self) -> f64 { self.points }
+ /// Create a length from a number of inches.
+ pub const fn inches(inches: f64) -> Self {
+ Self::new(inches, Unit::In)
+ }
- /// Convert this length into millimeters.
- pub fn to_mm(self) -> f64 { self.points * 0.352778 }
+ /// Create a length from a number of raw units.
+ pub const fn raw(raw: f64) -> Self {
+ Self::new(raw, Unit::Raw)
+ }
- /// Convert this length into centimeters.
- pub fn to_cm(self) -> f64 { self.points * 0.0352778 }
+ /// Convert this to a number of points.
+ pub fn as_pt(self) -> f64 {
+ self.with_unit(Unit::Pt).val
+ }
- /// Convert this length into inches.
- pub fn to_inches(self) -> f64 { self.points * 0.0138889 }
+ /// Convert this to a number of millimeters.
+ pub fn as_mm(self) -> f64 {
+ self.with_unit(Unit::Mm).val
+ }
- /// The maximum of this and the other length.
- pub fn max(self, other: Length) -> Length {
- if self > other { self } else { other }
+ /// Convert this to a number of centimeters.
+ pub fn as_cm(self) -> f64 {
+ self.with_unit(Unit::Cm).val
}
- /// The minimum of this and the other length.
- pub fn min(self, other: Length) -> Length {
- if self <= other { self } else { other }
+ /// Convert this to a number of inches.
+ pub fn as_inches(self) -> f64 {
+ self.with_unit(Unit::In).val
}
- /// Set this length to the maximum of itself and the other length.
- pub fn max_eq(&mut self, other: Length) { *self = self.max(other); }
+ /// Get the value of this length in raw units.
+ pub fn as_raw(self) -> f64 {
+ self.with_unit(Unit::Raw).val
+ }
- /// Set this length to the minimum of itself and the other length.
- pub fn min_eq(&mut self, other: Length) { *self = self.min(other); }
+ /// Convert this to a length with a different unit.
+ pub fn with_unit(self, unit: Unit) -> Length {
+ Self {
+ val: self.val * self.unit.raw_scale() / unit.raw_scale(),
+ unit,
+ }
+ }
+}
- /// The anchor position along the given direction for an item with the given
- /// alignment in a container with this length.
- pub fn anchor(self, alignment: Alignment, direction: Direction) -> Length {
- match (direction.is_positive(), alignment) {
- (true, Origin) | (false, End) => Length::ZERO,
- (_, Center) => self / 2,
- (true, End) | (false, Origin) => self,
+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,
+ Unit::Raw => 1.0,
}
}
}
impl Display for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "{}pt", self.points)
+ write!(f, "{:.2}{}", self.val, self.unit)
}
}
@@ -83,18 +117,63 @@ impl Debug for Length {
}
}
-impl Neg for Length {
- type Output = Length;
+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",
+ Unit::Raw => "rw",
+ })
+ }
+}
+
+impl Debug for Unit {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+impl FromStr for Length {
+ type Err = ParseLengthError;
+
+ fn from_str(src: &str) -> Result<Self, Self::Err> {
+ let len = src.len();
+
+ // We need at least some number and the unit.
+ if len <= 2 {
+ return Err(ParseLengthError);
+ }
- fn neg(self) -> Length {
- Length { points: -self.points }
+ // We can view the string as bytes since a multibyte UTF-8 char cannot
+ // have valid ASCII chars as subbytes.
+ let split = len - 2;
+ let bytes = src.as_bytes();
+ let unit = match &bytes[split..] {
+ b"pt" => Unit::Pt,
+ b"mm" => Unit::Mm,
+ b"cm" => Unit::Cm,
+ b"in" => Unit::In,
+ _ => return Err(ParseLengthError),
+ };
+
+ src[..split]
+ .parse::<f64>()
+ .map(|val| Length::new(val, unit))
+ .map_err(|_| ParseLengthError)
}
}
-impl Sum for Length {
- fn sum<I>(iter: I) -> Length
- where I: Iterator<Item = Length> {
- iter.fold(Length::ZERO, Add::add)
+/// The error when parsing a length fails.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub struct ParseLengthError;
+
+impl std::error::Error for ParseLengthError {}
+
+impl Display for ParseLengthError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("invalid string for length")
}
}
@@ -108,10 +187,10 @@ pub enum ScaleLength {
impl ScaleLength {
/// Use the absolute value or scale the entity.
- pub fn scaled(&self, entity: Length) -> Length {
- match self {
- ScaleLength::Absolute(s) => *s,
- ScaleLength::Scaled(s) => *s * entity,
+ pub fn raw_scaled(&self, entity: f64) -> f64 {
+ match *self {
+ ScaleLength::Absolute(l) => l.as_raw(),
+ ScaleLength::Scaled(s) => s * entity,
}
}
}
@@ -131,344 +210,27 @@ impl Debug for ScaleLength {
}
}
-/// A value in two dimensions.
-#[derive(Default, Copy, Clone, Eq, PartialEq, Serialize)]
-pub struct Value2<T> {
- /// The horizontal component.
- pub x: T,
- /// The vertical component.
- pub y: T,
-}
-
-impl<T: Clone> Value2<T> {
- /// Create a new 2D-value from two values.
- pub fn new(x: T, y: T) -> Value2<T> { Value2 { x, y } }
-
- /// Create a new 2D-value with `x` set to a value and `y` to default.
- pub fn with_x(x: T) -> Value2<T> where T: Default {
- Value2 { x, y: T::default() }
- }
-
- /// Create a new 2D-value with `y` set to a value and `x` to default.
- pub fn with_y(y: T) -> Value2<T> where T: Default {
- Value2 { x: T::default(), y }
- }
-
- /// Create a new 2D-value with the primary axis set to a value and the other
- /// one to default.
- pub fn with_primary(v: T, axes: LayoutAxes) -> Value2<T> where T: Default {
- Value2::with_x(v).generalized(axes)
- }
-
- /// Create a new 2D-value with the secondary axis set to a value and the
- /// other one to default.
- pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2<T> where T: Default {
- Value2::with_y(v).generalized(axes)
- }
-
- /// Create a 2D-value with `x` and `y` set to the same value `s`.
- pub fn with_all(s: T) -> Value2<T> { Value2 { x: s.clone(), y: s } }
-
- /// Get the specificed component.
- pub fn get(self, axis: SpecificAxis) -> T {
- match axis {
- Horizontal => self.x,
- Vertical => self.y,
- }
- }
-
- /// Borrow the specificed component mutably.
- pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut T {
- match axis {
- Horizontal => &mut self.x,
- Vertical => &mut self.y,
- }
- }
-
- /// Return the primary value of this specialized 2D-value.
- pub fn primary(self, axes: LayoutAxes) -> T {
- if axes.primary.axis() == Horizontal { self.x } else { self.y }
- }
-
- /// Borrow the primary value of this specialized 2D-value mutably.
- pub fn primary_mut(&mut self, axes: LayoutAxes) -> &mut T {
- if axes.primary.axis() == Horizontal { &mut self.x } else { &mut self.y }
- }
+#[cfg(test)]
+mod tests {
+ use super::*;
- /// Return the secondary value of this specialized 2D-value.
- pub fn secondary(self, axes: LayoutAxes) -> T {
- if axes.primary.axis() == Horizontal { self.y } else { self.x }
+ #[test]
+ fn test_length_from_str_parses_correct_value_and_unit() {
+ assert_eq!(Length::from_str("2.5cm"), Ok(Length::cm(2.5)));
}
- /// Borrow the secondary value of this specialized 2D-value mutably.
- pub fn secondary_mut(&mut self, axes: LayoutAxes) -> &mut T {
- if axes.primary.axis() == Horizontal { &mut self.y } else { &mut self.x }
+ #[test]
+ fn test_length_from_str_works_with_non_ascii_chars() {
+ assert_eq!(Length::from_str("123🚚"), Err(ParseLengthError));
}
- /// Returns the generalized version of a `Size2D` dependent on the layouting
- /// axes, that is:
- /// - `x` describes the primary axis instead of the horizontal one.
- /// - `y` describes the secondary axis instead of the vertical one.
- pub fn generalized(self, axes: LayoutAxes) -> Value2<T> {
- match axes.primary.axis() {
- Horizontal => self,
- Vertical => Value2 { x: self.y, y: self.x },
- }
- }
-
- /// Returns the specialized version of this generalized Size2D (inverse to
- /// `generalized`).
- pub fn specialized(self, axes: LayoutAxes) -> Value2<T> {
- // In fact, generalized is its own inverse. For reasons of clarity
- // at the call site, we still have this second function.
- self.generalized(axes)
+ #[test]
+ fn test_length_formats_correctly() {
+ assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
}
- /// Swap the `x` and `y` values.
- pub fn swap(&mut self) {
- std::mem::swap(&mut self.x, &mut self.y);
+ #[test]
+ fn test_length_unit_conversion() {
+ assert!((Length::mm(150.0).as_cm() - 15.0) < 1e-4);
}
}
-
-impl<T> Debug for Value2<T> where T: Debug {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.debug_list()
- .entry(&self.x)
- .entry(&self.y)
- .finish()
- }
-}
-
-/// A position or extent in 2-dimensional space.
-pub type Size = Value2<Length>;
-
-impl Size {
- /// The zeroed 2D-length.
- pub const ZERO: Size = Size { x: Length::ZERO, y: Length::ZERO };
-
- /// Whether the given 2D-length fits into this one, that is, both coordinate
- /// values are smaller or equal.
- pub fn fits(self, other: Size) -> bool {
- self.x >= other.x && self.y >= other.y
- }
-
- /// Return a 2D-length padded by the paddings of the given box.
- pub fn padded(self, padding: Margins) -> Size {
- Size {
- x: self.x + padding.left + padding.right,
- y: self.y + padding.top + padding.bottom,
- }
- }
-
- /// Return a 2D-length reduced by the paddings of the given box.
- pub fn unpadded(self, padding: Margins) -> Size {
- Size {
- x: self.x - padding.left - padding.right,
- y: self.y - padding.top - padding.bottom,
- }
- }
-
- /// The anchor position along the given axis for an item with the given
- /// alignment in a container with this length.
- ///
- /// This assumes the length to be generalized such that `x` corresponds to the
- /// primary axis.
- pub fn anchor(self, alignment: LayoutAlignment, axes: LayoutAxes) -> Size {
- Size {
- x: self.x.anchor(alignment.primary, axes.primary),
- y: self.y.anchor(alignment.secondary, axes.secondary),
- }
- }
-}
-
-impl Neg for Size {
- type Output = Size;
-
- fn neg(self) -> Size {
- Size {
- x: -self.x,
- y: -self.y,
- }
- }
-}
-
-/// A value in four dimensions.
-#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)]
-pub struct Value4<T> {
- /// The left extent.
- pub left: T,
- /// The top extent.
- pub top: T,
- /// The right extent.
- pub right: T,
- /// The bottom extent.
- pub bottom: T,
-}
-
-impl<T: Clone> Value4<T> {
- /// Create a new box from four sizes.
- pub fn new(left: T, top: T, right: T, bottom: T) -> Value4<T> {
- Value4 { left, top, right, bottom }
- }
-
- /// Create a box with all four fields set to the same value `s`.
- pub fn with_all(value: T) -> Value4<T> {
- Value4 {
- left: value.clone(),
- top: value.clone(),
- right: value.clone(),
- bottom: value
- }
- }
-
- /// Get a mutable reference to the value for the specified direction at the
- /// alignment.
- ///
- /// Center alignment is treated the same as origin alignment.
- pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut T {
- if alignment == End {
- direction = direction.inv();
- }
-
- match direction {
- LeftToRight => &mut self.left,
- RightToLeft => &mut self.right,
- TopToBottom => &mut self.top,
- BottomToTop => &mut self.bottom,
- }
- }
-
- /// Set all values to the given value.
- pub fn set_all(&mut self, value: T) {
- *self = Value4::with_all(value);
- }
-
- /// Set the `left` and `right` values.
- pub fn set_horizontal(&mut self, value: T) {
- self.left = value.clone();
- self.right = value;
- }
-
- /// Set the `top` and `bottom` values.
- pub fn set_vertical(&mut self, value: T) {
- self.top = value.clone();
- self.bottom = value;
- }
-}
-
-/// A length in four dimensions.
-pub type Margins = Value4<Length>;
-
-impl Margins {
- /// The zeroed length box.
- pub const ZERO: Margins = Margins {
- left: Length::ZERO,
- top: Length::ZERO,
- right: Length::ZERO,
- bottom: Length::ZERO,
- };
-}
-
-impl FromStr for Length {
- type Err = ParseLengthError;
-
- fn from_str(src: &str) -> Result<Length, ParseLengthError> {
- let func = match () {
- _ if src.ends_with("pt") => Length::pt,
- _ if src.ends_with("mm") => Length::mm,
- _ if src.ends_with("cm") => Length::cm,
- _ if src.ends_with("in") => Length::inches,
- _ => return Err(ParseLengthError),
- };
-
- Ok(func(src[..src.len() - 2]
- .parse::<f64>()
- .map_err(|_| ParseLengthError)?))
-
- }
-}
-
-/// An error which can be returned when parsing a length.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct ParseLengthError;
-
-impl std::error::Error for ParseLengthError {}
-
-impl Display for ParseLengthError {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_str("invalid string for length")
- }
-}
-
-macro_rules! implement_traits {
- ($ty:ident, $t:ident, $o:ident
- reflexive {$(
- ($tr:ident($tf:ident), $at:ident($af:ident), [$($f:ident),*])
- )*}
- numbers { $(($w:ident: $($rest:tt)*))* }
- ) => {
- $(impl $tr for $ty {
- type Output = $ty;
- fn $tf($t, $o: $ty) -> $ty {
- $ty { $($f: $tr::$tf($t.$f, $o.$f),)* }
- }
- }
-
- impl $at for $ty {
- fn $af(&mut $t, $o: $ty) { $($at::$af(&mut $t.$f, $o.$f);)* }
- })*
-
- $(implement_traits!(@$w i32, $ty $t $o $($rest)*);)*
- $(implement_traits!(@$w f64, $ty $t $o $($rest)*);)*
- };
-
- (@front $num:ty, $ty:ident $t:ident $o:ident
- $tr:ident($tf:ident),
- [$($f:ident),*]
- ) => {
- impl $tr<$ty> for $num {
- type Output = $ty;
- fn $tf($t, $o: $ty) -> $ty {
- $ty { $($f: $tr::$tf($t as f64, $o.$f),)* }
- }
- }
- };
-
- (@back $num:ty, $ty:ident $t:ident $o:ident
- $tr:ident($tf:ident), $at:ident($af:ident),
- [$($f:ident),*]
- ) => {
- impl $tr<$num> for $ty {
- type Output = $ty;
- fn $tf($t, $o: $num) -> $ty {
- $ty { $($f: $tr::$tf($t.$f, $o as f64),)* }
- }
- }
-
- impl $at<$num> for $ty {
- fn $af(&mut $t, $o: $num) { $($at::$af(&mut $t.$f, $o as f64);)* }
- }
- };
-}
-
-macro_rules! implement_size {
- ($ty:ident($t:ident, $o:ident) [$($f:ident),*]) => {
- implement_traits! {
- $ty, $t, $o
-
- reflexive {
- (Add(add), AddAssign(add_assign), [$($f),*])
- (Sub(sub), SubAssign(sub_assign), [$($f),*])
- }
-
- numbers {
- (front: Mul(mul), [$($f),*])
- (back: Mul(mul), MulAssign(mul_assign), [$($f),*])
- (back: Div(div), DivAssign(div_assign), [$($f),*])
- }
- }
- };
-}
-
-implement_size! { Length(self, other) [points] }
-implement_size! { Size(self, other) [x, y] }