diff --git a/src/geom/angle.rs b/src/geom/angle.rs new file mode 100644 index 000000000..47541cb8b --- /dev/null +++ b/src/geom/angle.rs @@ -0,0 +1,172 @@ +use std::f64::consts::PI; + +use super::*; + +/// An angle. +#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] +pub struct Angle { + /// The angle in raw units. + raw: f64, +} + +impl Angle { + /// The zero angle. + pub const ZERO: Self = Self { raw: 0.0 }; + + /// Create an angle from a number of radians. + pub fn rad(rad: f64) -> Self { + Self::with_unit(rad, AngularUnit::Rad) + } + + /// Create an angle from a number of degrees. + pub fn deg(deg: f64) -> Self { + Self::with_unit(deg, AngularUnit::Deg) + } + + /// Create an angle from a number of raw units. + pub fn raw(raw: f64) -> Self { + Self { raw } + } + + /// Convert this to a number of radians. + pub fn to_rad(self) -> f64 { + self.to_unit(AngularUnit::Rad) + } + + /// Convert this to a number of degrees. + pub fn to_deg(self) -> f64 { + self.to_unit(AngularUnit::Deg) + } + + /// Get the value of this angle in raw units. + pub fn to_raw(self) -> f64 { + self.raw + } + + /// Create an angle from a value in a unit. + pub fn with_unit(val: f64, unit: AngularUnit) -> Self { + Self { raw: val * unit.raw_scale() } + } + + /// Get the value of this length in unit. + pub fn to_unit(self, unit: AngularUnit) -> f64 { + self.raw / unit.raw_scale() + } +} + +impl Display for Angle { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}{}", self.to_deg(), AngularUnit::Deg) + } +} + +impl Debug for Angle { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Neg for Angle { + type Output = Self; + + fn neg(self) -> Self { + Self { raw: -self.raw } + } +} + +impl Add for Angle { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { raw: self.raw + other.raw } + } +} + +sub_impl!(Angle - Angle -> Angle); + +impl Mul for Angle { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { raw: self.raw * other } + } +} + +impl Mul for f64 { + type Output = Angle; + + fn mul(self, other: Angle) -> Angle { + other * self + } +} + +impl Div for Angle { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { raw: self.raw / other } + } +} + +impl Div for Angle { + type Output = f64; + + fn div(self, other: Self) -> f64 { + self.raw / other.raw + } +} + +assign_impl!(Angle += Angle); +assign_impl!(Angle -= Angle); +assign_impl!(Angle *= f64); +assign_impl!(Angle /= f64); + +impl Sum for Angle { + fn sum>(iter: I) -> Self { + iter.fold(Angle::ZERO, Add::add) + } +} +/// Different units of angular measurement. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum AngularUnit { + /// Radians. + Rad, + /// Degrees. + Deg, +} + +impl AngularUnit { + /// How many raw units correspond to a value of `1.0` in this unit. + fn raw_scale(self) -> f64 { + match self { + Self::Rad => 1.0, + Self::Deg => PI / 180.0, + } + } +} + +impl Display for AngularUnit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Rad => "rad", + Self::Deg => "deg", + }) + } +} + +impl Debug for AngularUnit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_angle_unit_conversion() { + assert!((Angle::rad(2.0 * PI).to_deg() - 360.0) < 1e-4); + assert!((Angle::deg(45.0).to_rad() - 0.7854) < 1e-4); + } +} diff --git a/src/geom/length.rs b/src/geom/length.rs index 23650e897..3e6c86019 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -13,22 +13,22 @@ impl Length { /// Create a length from a number of points. pub fn pt(pt: f64) -> Self { - Self::with_unit(pt, Unit::Pt) + Self::with_unit(pt, LengthUnit::Pt) } /// Create a length from a number of millimeters. pub fn mm(mm: f64) -> Self { - Self::with_unit(mm, Unit::Mm) + Self::with_unit(mm, LengthUnit::Mm) } /// Create a length from a number of centimeters. pub fn cm(cm: f64) -> Self { - Self::with_unit(cm, Unit::Cm) + Self::with_unit(cm, LengthUnit::Cm) } /// Create a length from a number of inches. pub fn inches(inches: f64) -> Self { - Self::with_unit(inches, Unit::In) + Self::with_unit(inches, LengthUnit::In) } /// Create a length from a number of raw units. @@ -38,22 +38,22 @@ impl Length { /// Convert this to a number of points. pub fn to_pt(self) -> f64 { - self.to_unit(Unit::Pt) + self.to_unit(LengthUnit::Pt) } /// Convert this to a number of millimeters. pub fn to_mm(self) -> f64 { - self.to_unit(Unit::Mm) + self.to_unit(LengthUnit::Mm) } /// Convert this to a number of centimeters. pub fn to_cm(self) -> f64 { - self.to_unit(Unit::Cm) + self.to_unit(LengthUnit::Cm) } /// Convert this to a number of inches. pub fn to_inches(self) -> f64 { - self.to_unit(Unit::In) + self.to_unit(LengthUnit::In) } /// Get the value of this length in raw units. @@ -62,12 +62,12 @@ impl Length { } /// Create a length from a value in a unit. - pub fn with_unit(val: f64, unit: Unit) -> Self { + pub fn with_unit(val: f64, unit: LengthUnit) -> Self { Self { raw: val * unit.raw_scale() } } /// Get the value of this length in unit. - pub fn to_unit(self, unit: Unit) -> f64 { + pub fn to_unit(self, unit: LengthUnit) -> f64 { self.raw / unit.raw_scale() } @@ -86,9 +86,9 @@ 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) + (self.to_pt(), LengthUnit::Pt) } else { - (self.to_cm(), Unit::Cm) + (self.to_cm(), LengthUnit::Cm) }; write!(f, "{}{}", (val * 100.0).round() / 100.0, unit) } @@ -161,9 +161,9 @@ impl Sum for Length { } } -/// Different units of measurement. +/// Different units of length measurement. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Unit { +pub enum LengthUnit { /// Points. Pt, /// Millimeters. @@ -174,30 +174,30 @@ pub enum Unit { In, } -impl Unit { +impl LengthUnit { /// 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, + LengthUnit::Pt => 1.0, + LengthUnit::Mm => 2.83465, + LengthUnit::Cm => 28.3465, + LengthUnit::In => 72.0, } } } -impl Display for Unit { +impl Display for LengthUnit { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - Unit::Mm => "mm", - Unit::Pt => "pt", - Unit::Cm => "cm", - Unit::In => "in", + LengthUnit::Mm => "mm", + LengthUnit::Pt => "pt", + LengthUnit::Cm => "cm", + LengthUnit::In => "in", }) } } -impl Debug for Unit { +impl Debug for LengthUnit { fn fmt(&self, f: &mut Formatter) -> fmt::Result { Display::fmt(self, f) } diff --git a/src/geom/mod.rs b/src/geom/mod.rs index 69bb08983..e601cb1f8 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -3,6 +3,7 @@ #[macro_use] mod macros; mod align; +mod angle; mod dir; mod gen; mod length; @@ -14,6 +15,7 @@ mod size; mod spec; pub use align::*; +pub use angle::*; pub use dir::*; pub use gen::*; pub use length::*; diff --git a/src/parse/tests.rs b/src/parse/tests.rs index 701d2a730..833d6661e 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -5,7 +5,7 @@ use std::fmt::Debug; use super::parse; use crate::color::RgbaColor; use crate::diag::{Diag, Level, Pass}; -use crate::geom::Unit; +use crate::geom::LengthUnit; use crate::syntax::*; use BinOp::*; @@ -549,7 +549,7 @@ fn test_parse_expressions() { t!(r#"{"x"+"y"}"# Block(Binary(Str("x"), Add, Str("y")))); t!("{1-2}" Block(Binary(Int(1), Sub, Int(2)))); t!("{a * b}" Block(Binary(Id("a"), Mul, Id("b")))); - t!("{12pt/.4}" Block(Binary(Length(12.0, Unit::Pt), Div, Float(0.4)))); + t!("{12pt/.4}" Block(Binary(Length(12.0, LengthUnit::Pt), Div, Float(0.4)))); // Associativity. t!("{1+2+3}" Block(Binary(Binary(Int(1), Add, Int(2)), Add, Int(3)))); @@ -593,8 +593,8 @@ fn test_parse_values() { t!("{1.0e-4}" Block(Float(1e-4))); t!("{3.15}" Block(Float(3.15))); t!("{50%}" Block(Percent(50.0))); - t!("{4.5cm}" Block(Length(4.5, Unit::Cm))); - t!("{12e1pt}" Block(Length(12e1, Unit::Pt))); + t!("{4.5cm}" Block(Length(4.5, LengthUnit::Cm))); + t!("{12e1pt}" Block(Length(12e1, LengthUnit::Pt))); // Strings. t!(r#"{"hi"}"# Block(Str("hi"))); diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 77c39a4c3..1e49d1c69 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter}; use super::{is_newline, Scanner}; -use crate::geom::Unit; +use crate::geom::LengthUnit; use crate::syntax::*; use TokenMode::*; @@ -349,7 +349,7 @@ fn parse_percent(string: &str) -> Option { string.strip_suffix('%').and_then(|prefix| prefix.parse::().ok()) } -fn parse_length(string: &str) -> Option<(f64, Unit)> { +fn parse_length(string: &str) -> Option<(f64, LengthUnit)> { let len = string.len(); // We need at least some number and the unit. @@ -362,10 +362,10 @@ fn parse_length(string: &str) -> Option<(f64, Unit)> { let split = len - 2; let bytes = string.as_bytes(); let unit = match &bytes[split ..] { - b"pt" => Unit::Pt, - b"mm" => Unit::Mm, - b"cm" => Unit::Cm, - b"in" => Unit::In, + b"pt" => LengthUnit::Pt, + b"mm" => LengthUnit::Mm, + b"cm" => LengthUnit::Cm, + b"in" => LengthUnit::In, _ => return None, }; @@ -378,9 +378,9 @@ mod tests { use super::*; use crate::parse::tests::check; + use LengthUnit::*; use Option::None; use Token::{Ident, *}; - use Unit::*; fn Raw(text: &str, backticks: usize, terminated: bool) -> Token { Token::Raw(TokenRaw { text, backticks, terminated }) @@ -737,7 +737,12 @@ mod tests { } // Test lengths. - for &unit in &[Unit::Mm, Unit::Pt, Unit::Cm, Unit::In] { + for &unit in &[ + LengthUnit::Mm, + LengthUnit::Pt, + LengthUnit::Cm, + LengthUnit::In, + ] { for (s, v) in nums.clone() { t!(Header[" /"]: format!("{}{}", s, unit) => Length(v, unit)); } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index ae8762092..78f4feee5 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,6 +1,6 @@ use super::*; use crate::color::RgbaColor; -use crate::geom::Unit; +use crate::geom::LengthUnit; /// An expression. #[derive(Debug, Clone, PartialEq)] @@ -16,7 +16,7 @@ pub enum Expr { /// A floating-point literal: `1.2`, `10e-4`. Float(f64), /// A length literal: `12pt`, `3cm`. - Length(f64, Unit), + Length(f64, LengthUnit), /// A percent literal: `50%`. /// /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 785199629..fb50c4ec3 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -1,4 +1,4 @@ -use crate::geom::Unit; +use crate::geom::LengthUnit; /// A minimal semantic entity of source code. #[derive(Debug, Copy, Clone, PartialEq)] @@ -70,7 +70,7 @@ pub enum Token<'s> { /// A floating-point number: `1.2`, `10e-4`. Float(f64), /// A length: `12pt`, `3cm`. - Length(f64, Unit), + Length(f64, LengthUnit), /// A percentage: `50%`. /// /// _Note_: `50%` is stored as `50.0` here, as in the corresponding