diff options
Diffstat (limited to 'src/length.rs')
| -rw-r--r-- | src/length.rs | 546 |
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] } |
