typst_library/layout/
angle.rs1use std::f64::consts::PI;
2use std::fmt::{self, Debug, Formatter};
3use std::iter::Sum;
4use std::ops::{Add, Div, Mul, Neg};
5
6use ecow::EcoString;
7use typst_utils::{Numeric, Scalar};
8
9use crate::foundations::{func, repr, scope, ty, Repr};
10
11#[ty(scope, cast)]
23#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
24pub struct Angle(Scalar);
25
26impl Angle {
27 pub const fn zero() -> Self {
29 Self(Scalar::ZERO)
30 }
31
32 pub const fn raw(raw: f64) -> Self {
34 Self(Scalar::new(raw))
35 }
36
37 pub fn with_unit(val: f64, unit: AngleUnit) -> Self {
39 Self(Scalar::new(val * unit.raw_scale()))
40 }
41
42 pub fn rad(rad: f64) -> Self {
44 Self::with_unit(rad, AngleUnit::Rad)
45 }
46
47 pub fn deg(deg: f64) -> Self {
49 Self::with_unit(deg, AngleUnit::Deg)
50 }
51
52 pub const fn to_raw(self) -> f64 {
54 (self.0).get()
55 }
56
57 pub fn to_unit(self, unit: AngleUnit) -> f64 {
59 self.to_raw() / unit.raw_scale()
60 }
61
62 pub fn abs(self) -> Self {
64 Self::raw(self.to_raw().abs())
65 }
66
67 pub fn sin(self) -> f64 {
69 self.to_rad().sin()
70 }
71
72 pub fn cos(self) -> f64 {
74 self.to_rad().cos()
75 }
76
77 pub fn tan(self) -> f64 {
79 self.to_rad().tan()
80 }
81
82 pub fn quadrant(self) -> Quadrant {
92 let angle = self.to_deg().rem_euclid(360.0);
93 if angle <= 90.0 {
94 Quadrant::First
95 } else if angle <= 180.0 {
96 Quadrant::Second
97 } else if angle <= 270.0 {
98 Quadrant::Third
99 } else {
100 Quadrant::Fourth
101 }
102 }
103}
104
105#[scope]
106impl Angle {
107 #[func(name = "rad", title = "Radians")]
109 pub fn to_rad(self) -> f64 {
110 self.to_unit(AngleUnit::Rad)
111 }
112
113 #[func(name = "deg", title = "Degrees")]
115 pub fn to_deg(self) -> f64 {
116 self.to_unit(AngleUnit::Deg)
117 }
118}
119
120impl Numeric for Angle {
121 fn zero() -> Self {
122 Self::zero()
123 }
124
125 fn is_finite(self) -> bool {
126 self.0.is_finite()
127 }
128}
129
130impl Debug for Angle {
131 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
132 write!(f, "{:?}deg", self.to_deg())
133 }
134}
135
136impl Repr for Angle {
137 fn repr(&self) -> EcoString {
138 repr::format_float_with_unit(self.to_deg(), "deg")
139 }
140}
141
142impl Neg for Angle {
143 type Output = Self;
144
145 fn neg(self) -> Self {
146 Self(-self.0)
147 }
148}
149
150impl Add for Angle {
151 type Output = Self;
152
153 fn add(self, other: Self) -> Self {
154 Self(self.0 + other.0)
155 }
156}
157
158typst_utils::sub_impl!(Angle - Angle -> Angle);
159
160impl Mul<f64> for Angle {
161 type Output = Self;
162
163 fn mul(self, other: f64) -> Self {
164 Self(self.0 * other)
165 }
166}
167
168impl Mul<Angle> for f64 {
169 type Output = Angle;
170
171 fn mul(self, other: Angle) -> Angle {
172 other * self
173 }
174}
175
176impl Div for Angle {
177 type Output = f64;
178
179 fn div(self, other: Self) -> f64 {
180 self.to_raw() / other.to_raw()
181 }
182}
183
184impl Div<f64> for Angle {
185 type Output = Self;
186
187 fn div(self, other: f64) -> Self {
188 Self(self.0 / other)
189 }
190}
191
192typst_utils::assign_impl!(Angle += Angle);
193typst_utils::assign_impl!(Angle -= Angle);
194typst_utils::assign_impl!(Angle *= f64);
195typst_utils::assign_impl!(Angle /= f64);
196
197impl Sum for Angle {
198 fn sum<I: Iterator<Item = Angle>>(iter: I) -> Self {
199 Self(iter.map(|s| s.0).sum())
200 }
201}
202
203#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
205pub enum AngleUnit {
206 Rad,
208 Deg,
210}
211
212impl AngleUnit {
213 fn raw_scale(self) -> f64 {
215 match self {
216 Self::Rad => 1.0,
217 Self::Deg => PI / 180.0,
218 }
219 }
220}
221
222#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
224pub enum Quadrant {
225 First,
227 Second,
229 Third,
231 Fourth,
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_angle_unit_conversion() {
241 assert!((Angle::rad(2.0 * PI).to_deg() - 360.0) < 1e-4);
242 assert!((Angle::deg(45.0).to_rad() - std::f64::consts::FRAC_PI_4) < 1e-4);
243 }
244}