typst_library/layout/
angle.rs1use std::f64::consts::{PI, TAU};
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::{Repr, func, repr, scope, ty};
10use crate::layout::Ratio;
11
12#[ty(scope, cast)]
24#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
25pub struct Angle(Scalar);
26
27impl Angle {
28 pub const fn zero() -> Self {
30 Self(Scalar::ZERO)
31 }
32
33 pub const fn raw(raw: f64) -> Self {
35 Self(Scalar::new(raw))
36 }
37
38 pub fn with_unit(val: f64, unit: AngleUnit) -> Self {
40 Self(Scalar::new(val * unit.raw_scale()))
41 }
42
43 pub fn rad(rad: f64) -> Self {
45 Self::with_unit(rad, AngleUnit::Rad)
46 }
47
48 pub fn deg(deg: f64) -> Self {
50 Self::with_unit(deg, AngleUnit::Deg)
51 }
52
53 pub fn from_ratio(r: Ratio) -> Angle {
55 Self::rad(r.get() * TAU)
56 }
57
58 pub const fn to_raw(self) -> f64 {
60 (self.0).get()
61 }
62
63 pub fn to_unit(self, unit: AngleUnit) -> f64 {
65 self.to_raw() / unit.raw_scale()
66 }
67
68 pub fn normalized(self) -> Self {
72 Self::rad(self.to_rad().rem_euclid(TAU))
73 }
74
75 pub fn abs(self) -> Self {
77 Self::raw(self.to_raw().abs())
78 }
79
80 pub fn sin(self) -> f64 {
82 libm::sin(self.to_rad())
83 }
84
85 pub fn cos(self) -> f64 {
87 libm::cos(self.to_rad())
88 }
89
90 pub fn tan(self) -> f64 {
92 libm::tan(self.to_rad())
93 }
94
95 pub fn asin(x: f64) -> Self {
97 Self::rad(libm::asin(x))
98 }
99
100 pub fn acos(x: f64) -> Self {
102 Self::rad(libm::acos(x))
103 }
104
105 pub fn atan(x: f64) -> Self {
107 Self::rad(libm::atan(x))
108 }
109
110 pub fn atan2(y: f64, x: f64) -> Self {
114 Self::rad(libm::atan2(y, x))
115 }
116
117 pub fn quadrant(self) -> Quadrant {
127 let angle = self.to_deg().rem_euclid(360.0);
128 if angle <= 90.0 {
129 Quadrant::First
130 } else if angle <= 180.0 {
131 Quadrant::Second
132 } else if angle <= 270.0 {
133 Quadrant::Third
134 } else {
135 Quadrant::Fourth
136 }
137 }
138}
139
140#[scope]
141impl Angle {
142 #[func(name = "rad", title = "Radians")]
144 pub fn to_rad(self) -> f64 {
145 self.to_unit(AngleUnit::Rad)
146 }
147
148 #[func(name = "deg", title = "Degrees")]
150 pub fn to_deg(self) -> f64 {
151 self.to_unit(AngleUnit::Deg)
152 }
153}
154
155impl Numeric for Angle {
156 fn zero() -> Self {
157 Self::zero()
158 }
159
160 fn is_finite(self) -> bool {
161 self.0.is_finite()
162 }
163}
164
165impl Debug for Angle {
166 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
167 write!(f, "{:?}deg", self.to_deg())
168 }
169}
170
171impl Repr for Angle {
172 fn repr(&self) -> EcoString {
173 repr::format_float_with_unit(self.to_deg(), "deg")
174 }
175}
176
177impl Neg for Angle {
178 type Output = Self;
179
180 fn neg(self) -> Self {
181 Self(-self.0)
182 }
183}
184
185impl Add for Angle {
186 type Output = Self;
187
188 fn add(self, other: Self) -> Self {
189 Self(self.0 + other.0)
190 }
191}
192
193typst_utils::sub_impl!(Angle - Angle -> Angle);
194
195impl Mul<f64> for Angle {
196 type Output = Self;
197
198 fn mul(self, other: f64) -> Self {
199 Self(self.0 * other)
200 }
201}
202
203impl Mul<Angle> for f64 {
204 type Output = Angle;
205
206 fn mul(self, other: Angle) -> Angle {
207 other * self
208 }
209}
210
211impl Div for Angle {
212 type Output = f64;
213
214 fn div(self, other: Self) -> f64 {
215 self.to_raw() / other.to_raw()
216 }
217}
218
219impl Div<f64> for Angle {
220 type Output = Self;
221
222 fn div(self, other: f64) -> Self {
223 Self(self.0 / other)
224 }
225}
226
227typst_utils::assign_impl!(Angle += Angle);
228typst_utils::assign_impl!(Angle -= Angle);
229typst_utils::assign_impl!(Angle *= f64);
230typst_utils::assign_impl!(Angle /= f64);
231
232impl Sum for Angle {
233 fn sum<I: Iterator<Item = Angle>>(iter: I) -> Self {
234 Self(iter.map(|s| s.0).sum())
235 }
236}
237
238#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
240pub enum AngleUnit {
241 Rad,
243 Deg,
245}
246
247impl AngleUnit {
248 fn raw_scale(self) -> f64 {
250 match self {
251 Self::Rad => 1.0,
252 Self::Deg => PI / 180.0,
253 }
254 }
255}
256
257#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
259pub enum Quadrant {
260 First,
262 Second,
264 Third,
266 Fourth,
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_angle_unit_conversion() {
276 assert!((Angle::rad(2.0 * PI).to_deg() - 360.0) < 1e-4);
277 assert!((Angle::deg(45.0).to_rad() - std::f64::consts::FRAC_PI_4) < 1e-4);
278 }
279}