From be7cfc85d08c545abfac08098b7b33b4bd71f37e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 27 Oct 2024 19:04:55 +0100 Subject: Split out four new crates (#5302) --- crates/typst-library/src/layout/angle.rs | 244 +++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 crates/typst-library/src/layout/angle.rs (limited to 'crates/typst-library/src/layout/angle.rs') diff --git a/crates/typst-library/src/layout/angle.rs b/crates/typst-library/src/layout/angle.rs new file mode 100644 index 00000000..d1410786 --- /dev/null +++ b/crates/typst-library/src/layout/angle.rs @@ -0,0 +1,244 @@ +use std::f64::consts::PI; +use std::fmt::{self, Debug, Formatter}; +use std::iter::Sum; +use std::ops::{Add, Div, Mul, Neg}; + +use ecow::EcoString; +use typst_utils::{Numeric, Scalar}; + +use crate::foundations::{func, repr, scope, ty, Repr}; + +/// An angle describing a rotation. +/// +/// Typst supports the following angular units: +/// +/// - Degrees: `{180deg}` +/// - Radians: `{3.14rad}` +/// +/// # Example +/// ```example +/// #rotate(10deg)[Hello there!] +/// ``` +#[ty(scope, cast)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Angle(Scalar); + +impl Angle { + /// The zero angle. + pub const fn zero() -> Self { + Self(Scalar::ZERO) + } + + /// Create an angle from a number of raw units. + pub const fn raw(raw: f64) -> Self { + Self(Scalar::new(raw)) + } + + /// Create an angle from a value in a unit. + pub fn with_unit(val: f64, unit: AngleUnit) -> Self { + Self(Scalar::new(val * unit.raw_scale())) + } + + /// Create an angle from a number of radians. + pub fn rad(rad: f64) -> Self { + Self::with_unit(rad, AngleUnit::Rad) + } + + /// Create an angle from a number of degrees. + pub fn deg(deg: f64) -> Self { + Self::with_unit(deg, AngleUnit::Deg) + } + + /// Get the value of this angle in raw units. + pub const fn to_raw(self) -> f64 { + (self.0).get() + } + + /// Get the value of this angle in a unit. + pub fn to_unit(self, unit: AngleUnit) -> f64 { + self.to_raw() / unit.raw_scale() + } + + /// The absolute value of the this angle. + pub fn abs(self) -> Self { + Self::raw(self.to_raw().abs()) + } + + /// Get the sine of this angle in radians. + pub fn sin(self) -> f64 { + self.to_rad().sin() + } + + /// Get the cosine of this angle in radians. + pub fn cos(self) -> f64 { + self.to_rad().cos() + } + + /// Get the tangent of this angle in radians. + pub fn tan(self) -> f64 { + self.to_rad().tan() + } + + /// Get the quadrant of the Cartesian plane that this angle lies in. + /// + /// The angle is automatically normalized to the range `0deg..=360deg`. + /// + /// The quadrants are defined as follows: + /// - First: `0deg..=90deg` (top-right) + /// - Second: `90deg..=180deg` (top-left) + /// - Third: `180deg..=270deg` (bottom-left) + /// - Fourth: `270deg..=360deg` (bottom-right) + pub fn quadrant(self) -> Quadrant { + let angle = self.to_deg().rem_euclid(360.0); + if angle <= 90.0 { + Quadrant::First + } else if angle <= 180.0 { + Quadrant::Second + } else if angle <= 270.0 { + Quadrant::Third + } else { + Quadrant::Fourth + } + } +} + +#[scope] +impl Angle { + /// Converts this angle to radians. + #[func(name = "rad", title = "Radians")] + pub fn to_rad(self) -> f64 { + self.to_unit(AngleUnit::Rad) + } + + /// Converts this angle to degrees. + #[func(name = "deg", title = "Degrees")] + pub fn to_deg(self) -> f64 { + self.to_unit(AngleUnit::Deg) + } +} + +impl Numeric for Angle { + fn zero() -> Self { + Self::zero() + } + + fn is_finite(self) -> bool { + self.0.is_finite() + } +} + +impl Debug for Angle { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}deg", self.to_deg()) + } +} + +impl Repr for Angle { + fn repr(&self) -> EcoString { + repr::format_float_with_unit(self.to_deg(), "deg") + } +} + +impl Neg for Angle { + type Output = Self; + + fn neg(self) -> Self { + Self(-self.0) + } +} + +impl Add for Angle { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +typst_utils::sub_impl!(Angle - Angle -> Angle); + +impl Mul for Angle { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self(self.0 * other) + } +} + +impl Mul for f64 { + type Output = Angle; + + fn mul(self, other: Angle) -> Angle { + other * self + } +} + +impl Div for Angle { + type Output = f64; + + fn div(self, other: Self) -> f64 { + self.to_raw() / other.to_raw() + } +} + +impl Div for Angle { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self(self.0 / other) + } +} + +typst_utils::assign_impl!(Angle += Angle); +typst_utils::assign_impl!(Angle -= Angle); +typst_utils::assign_impl!(Angle *= f64); +typst_utils::assign_impl!(Angle /= f64); + +impl Sum for Angle { + fn sum>(iter: I) -> Self { + Self(iter.map(|s| s.0).sum()) + } +} + +/// Different units of angular measurement. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AngleUnit { + /// Radians. + Rad, + /// Degrees. + Deg, +} + +impl AngleUnit { + /// 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, + } + } +} + +/// A quadrant of the Cartesian plane. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum Quadrant { + /// The first quadrant, containing positive x and y values. + First, + /// The second quadrant, containing negative x and positive y values. + Second, + /// The third quadrant, containing negative x and y values. + Third, + /// The fourth quadrant, containing positive x and negative y values. + Fourth, +} + +#[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() - std::f64::consts::FRAC_PI_4) < 1e-4); + } +} -- cgit v1.2.3