1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{NumericValue, ToTyped, TypedValue, UnitValue};
10use crate::values::computed::angle::Angle as ComputedAngle;
11use crate::values::computed::{Context, ToComputedValue};
12use crate::values::specified::calc::{CalcNode, CalcNumeric, Leaf};
13use crate::values::tagged_numeric::{Extracted, NumericUnion, Unpacked};
14use crate::values::CSSFloat;
15use crate::Zero;
16use cssparser::{match_ignore_ascii_case, Parser, Token};
17use std::f32::consts::PI;
18use std::fmt::{self, Write};
19use std::ops::Neg;
20use style_traits::{CssString, CssWriter, ParseError, SpecifiedValueInfo, ToCss};
21use thin_vec::ThinVec;
22
23const DEG_PER_RAD: f32 = 180.0 / PI;
25const DEG_PER_TURN: f32 = 360.0;
27const DEG_PER_GRAD: f32 = 180.0 / 200.0;
29
30#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)]
32#[repr(u8)]
33pub enum AngleUnit {
34 Deg,
36 Grad,
38 Rad,
40 Turn,
42}
43
44impl AngleUnit {
45 #[inline]
47 pub fn from_str(unit: &str) -> Result<Self, ()> {
48 Ok(match_ignore_ascii_case! { unit,
49 "deg" => AngleUnit::Deg,
50 "grad" => AngleUnit::Grad,
51 "turn" => AngleUnit::Turn,
52 "rad" => AngleUnit::Rad,
53 _ => return Err(())
54 })
55 }
56
57 #[inline]
59 pub fn as_str(self) -> &'static str {
60 match self {
61 Self::Deg => "deg",
62 Self::Grad => "grad",
63 Self::Rad => "rad",
64 Self::Turn => "turn",
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)]
71#[repr(C)]
72pub struct NoCalcAngle {
73 unit: AngleUnit,
74 value: CSSFloat,
75}
76
77impl Zero for NoCalcAngle {
78 fn zero() -> Self {
79 Self::from_degrees(0.)
80 }
81
82 fn is_zero(&self) -> bool {
83 self.value == 0.0
84 }
85}
86
87impl ToCss for NoCalcAngle {
88 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
89 where
90 W: Write,
91 {
92 crate::values::serialize_specified_dimension(
93 self.value,
94 self.unit.as_str(),
95 false,
96 dest,
97 )
98 }
99}
100
101impl ToTyped for NoCalcAngle {
102 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
103 let value = self.unitless_value();
104 let unit = CssString::from(self.unit());
105 dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
106 value,
107 unit,
108 })));
109 Ok(())
110 }
111}
112
113impl SpecifiedValueInfo for NoCalcAngle {}
114
115impl NoCalcAngle {
116 #[inline]
118 pub fn new(unit: AngleUnit, value: CSSFloat) -> Self {
119 Self { unit, value }
120 }
121
122 #[inline]
124 pub fn from_degrees(value: CSSFloat) -> Self {
125 Self::new(AngleUnit::Deg, value)
126 }
127
128 #[inline]
130 pub fn from_radians(value: CSSFloat) -> Self {
131 Self::new(AngleUnit::Rad, value)
132 }
133
134 pub fn zero() -> Self {
136 Self::from_degrees(0.0)
137 }
138
139 #[inline]
141 pub fn degrees(&self) -> CSSFloat {
142 match self.unit {
143 AngleUnit::Deg => self.value,
144 AngleUnit::Rad => self.value * DEG_PER_RAD,
145 AngleUnit::Turn => self.value * DEG_PER_TURN,
146 AngleUnit::Grad => self.value * DEG_PER_GRAD,
147 }
148 }
149
150 #[inline]
152 pub fn radians(&self) -> CSSFloat {
153 const RAD_PER_DEG: f32 = PI / 180.0;
154 self.degrees() * RAD_PER_DEG
155 }
156
157 #[inline]
159 pub fn angle_unit(&self) -> AngleUnit {
160 self.unit
161 }
162
163 #[inline]
165 pub fn unitless_value(&self) -> CSSFloat {
166 self.value
167 }
168
169 #[inline]
171 pub fn unit(&self) -> &'static str {
172 self.unit.as_str()
173 }
174
175 pub fn canonical_unit(&self) -> Option<&'static str> {
177 Some("deg")
178 }
179
180 pub fn to(&self, unit: &str) -> Result<Self, ()> {
182 let degrees = self.degrees();
183 let unit = AngleUnit::from_str(unit)?;
184 let divisor = match unit {
185 AngleUnit::Deg => 1.0,
186 AngleUnit::Grad => DEG_PER_GRAD,
187 AngleUnit::Turn => DEG_PER_TURN,
188 AngleUnit::Rad => DEG_PER_RAD,
189 };
190 Ok(Self::new(unit, degrees / divisor))
191 }
192
193 pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Self, ()> {
195 let unit = AngleUnit::from_str(unit)?;
196 Ok(Self::new(unit, value))
197 }
198}
199
200impl Neg for NoCalcAngle {
201 type Output = NoCalcAngle;
202
203 #[inline]
204 fn neg(self) -> NoCalcAngle {
205 Self::new(self.unit, -self.value)
206 }
207}
208
209#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
213pub struct Angle(NumericUnion<AngleUnit, f32, CalcNumeric>);
214
215impl ToCss for Angle {
216 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
217 where
218 W: Write,
219 {
220 match self.0.unpack() {
221 Unpacked::Inline(unit, value) => NoCalcAngle::new(unit, value).to_css(dest),
222 Unpacked::Boxed(calc) => calc.to_css(dest),
223 }
224 }
225}
226
227impl ToTyped for Angle {
228 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
229 match self.0.unpack() {
230 Unpacked::Inline(unit, value) => NoCalcAngle::new(unit, value).to_typed(dest),
231 Unpacked::Boxed(calc) => calc.to_typed(dest),
232 }
233 }
234}
235
236impl SpecifiedValueInfo for Angle {}
237
238#[allow(missing_docs)]
246pub enum AllowUnitlessZeroAngle {
247 Yes,
248 No,
249}
250
251impl Parse for Angle {
252 fn parse<'i, 't>(
254 context: &ParserContext,
255 input: &mut Parser<'i, 't>,
256 ) -> Result<Self, ParseError<'i>> {
257 Self::parse_internal(context, input, AllowUnitlessZeroAngle::No)
258 }
259}
260
261impl Zero for Angle {
262 fn zero() -> Self {
263 Self::new(NoCalcAngle::zero())
264 }
265
266 fn is_zero(&self) -> bool {
267 match self.0.unpack() {
268 Unpacked::Inline(_, v) => v == 0.0,
269 Unpacked::Boxed(_) => false,
270 }
271 }
272}
273
274impl ToComputedValue for Angle {
275 type ComputedValue = ComputedAngle;
276
277 #[inline]
278 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
279 let degrees = match self.0.unpack() {
280 Unpacked::Inline(unit, value) => NoCalcAngle::new(unit, value).degrees(),
281 Unpacked::Boxed(ref calc) => calc.resolve(context, |result| match result {
282 Ok(Leaf::Angle(a)) => a.degrees(),
283 _ => {
284 debug_assert!(false, "Unexpected Angle::Calc without resolved angle");
285 f32::NAN
286 },
287 }),
288 };
289
290 ComputedAngle::from_degrees(if degrees.is_finite() { degrees } else { 0.0 })
292 }
293
294 #[inline]
295 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
296 Self::new(NoCalcAngle::from_degrees(computed.degrees()))
297 }
298}
299
300impl Angle {
301 #[inline]
303 pub fn new(angle: NoCalcAngle) -> Self {
304 Self(NumericUnion::inline(angle.unit, angle.value))
305 }
306
307 #[inline]
309 pub fn new_calc(calc: Box<CalcNumeric>) -> Self {
310 Self(NumericUnion::boxed(calc))
311 }
312
313 #[inline]
315 pub fn from_degrees(value: CSSFloat) -> Self {
316 Self::new(NoCalcAngle::from_degrees(value))
317 }
318
319 pub fn zero() -> Self {
321 Self::new(NoCalcAngle::zero())
322 }
323
324 #[inline]
326 pub fn is_calc(&self) -> bool {
327 self.0.is_boxed()
328 }
329
330 #[inline]
332 pub fn as_no_calc(&self) -> Option<NoCalcAngle> {
333 match self.0.unpack() {
334 Unpacked::Inline(unit, value) => Some(NoCalcAngle::new(unit, value)),
335 Unpacked::Boxed(_) => None,
336 }
337 }
338
339 #[inline]
343 pub fn degrees(&self) -> Option<CSSFloat> {
344 match self.0.unpack() {
345 Unpacked::Inline(unit, value) => Some(NoCalcAngle::new(unit, value).degrees()),
346 Unpacked::Boxed(ref calc) => calc
347 .as_angle()
348 .map(|a| calc.clamping_mode.clamp(a.degrees())),
349 }
350 }
351
352 #[inline]
356 pub fn parse_with_unitless<'i, 't>(
357 context: &ParserContext,
358 input: &mut Parser<'i, 't>,
359 ) -> Result<Self, ParseError<'i>> {
360 Self::parse_internal(context, input, AllowUnitlessZeroAngle::Yes)
361 }
362
363 pub(super) fn parse_internal<'i, 't>(
364 context: &ParserContext,
365 input: &mut Parser<'i, 't>,
366 allow_unitless_zero: AllowUnitlessZeroAngle,
367 ) -> Result<Self, ParseError<'i>> {
368 let location = input.current_source_location();
369 let t = input.next()?;
370 let allow_unitless_zero = matches!(allow_unitless_zero, AllowUnitlessZeroAngle::Yes);
371 match *t {
372 Token::Dimension {
373 value, ref unit, ..
374 } => match NoCalcAngle::parse_dimension(value, unit) {
375 Ok(angle) => Ok(Self::new(angle)),
376 Err(()) => {
377 let t = t.clone();
378 Err(input.new_unexpected_token_error(t))
379 },
380 },
381 Token::Function(ref name) => {
382 let function = CalcNode::math_function(context, name, location)?;
383 CalcNode::parse_angle(context, input, function)
384 .map(Box::new)
385 .map(Self::new_calc)
386 },
387 Token::Number { value, .. } if value == 0. && allow_unitless_zero => Ok(Angle::zero()),
388 ref t => {
389 let t = t.clone();
390 Err(input.new_unexpected_token_error(t))
391 },
392 }
393 }
394}
395
396impl Neg for Angle {
397 type Output = Angle;
398
399 #[inline]
400 fn neg(self) -> Angle {
401 match self.0.extract() {
402 Extracted::Inline(unit, value) => Self::new(NoCalcAngle::new(unit, -value)),
403 Extracted::Boxed(mut c) => {
404 c.node.negate();
405 Self::new_calc(c)
406 },
407 }
408 }
409}