1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{NumericType, 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" => Self::Deg,
50 "grad" => Self::Grad,
51 "turn" => Self::Turn,
52 "rad" => Self::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 numeric_type = NumericType::angle();
104 let value = self.unitless_value();
105 let unit = CssString::from(self.unit());
106 dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
107 numeric_type,
108 value,
109 unit,
110 })));
111 Ok(())
112 }
113}
114
115impl SpecifiedValueInfo for NoCalcAngle {}
116
117impl NoCalcAngle {
118 #[inline]
120 pub fn new(unit: AngleUnit, value: CSSFloat) -> Self {
121 Self { unit, value }
122 }
123
124 #[inline]
126 pub fn from_degrees(value: CSSFloat) -> Self {
127 Self::new(AngleUnit::Deg, value)
128 }
129
130 #[inline]
132 pub fn from_radians(value: CSSFloat) -> Self {
133 Self::new(AngleUnit::Rad, value)
134 }
135
136 pub fn zero() -> Self {
138 Self::from_degrees(0.0)
139 }
140
141 #[inline]
143 pub fn degrees(&self) -> CSSFloat {
144 match self.unit {
145 AngleUnit::Deg => self.value,
146 AngleUnit::Rad => self.value * DEG_PER_RAD,
147 AngleUnit::Turn => self.value * DEG_PER_TURN,
148 AngleUnit::Grad => self.value * DEG_PER_GRAD,
149 }
150 }
151
152 #[inline]
154 pub fn radians(&self) -> CSSFloat {
155 const RAD_PER_DEG: f32 = PI / 180.0;
156 self.degrees() * RAD_PER_DEG
157 }
158
159 #[inline]
161 pub fn angle_unit(&self) -> AngleUnit {
162 self.unit
163 }
164
165 #[inline]
167 pub fn unitless_value(&self) -> CSSFloat {
168 self.value
169 }
170
171 #[inline]
173 pub fn unit(&self) -> &'static str {
174 self.unit.as_str()
175 }
176
177 pub fn canonical_unit(&self) -> Option<&'static str> {
179 Some("deg")
180 }
181
182 pub fn to(&self, unit: &str) -> Result<Self, ()> {
184 let degrees = self.degrees();
185 let unit = AngleUnit::from_str(unit)?;
186 let divisor = match unit {
187 AngleUnit::Deg => 1.0,
188 AngleUnit::Grad => DEG_PER_GRAD,
189 AngleUnit::Turn => DEG_PER_TURN,
190 AngleUnit::Rad => DEG_PER_RAD,
191 };
192 Ok(Self::new(unit, degrees / divisor))
193 }
194
195 pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Self, ()> {
197 let unit = AngleUnit::from_str(unit)?;
198 Ok(Self::new(unit, value))
199 }
200}
201
202impl Neg for NoCalcAngle {
203 type Output = NoCalcAngle;
204
205 #[inline]
206 fn neg(self) -> NoCalcAngle {
207 Self::new(self.unit, -self.value)
208 }
209}
210
211#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
215pub struct Angle(NumericUnion<AngleUnit, f32, CalcNumeric>);
216
217impl ToCss for Angle {
218 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
219 where
220 W: Write,
221 {
222 match self.0.unpack() {
223 Unpacked::Inline(unit, value) => NoCalcAngle::new(unit, value).to_css(dest),
224 Unpacked::Boxed(calc) => calc.to_css(dest),
225 }
226 }
227}
228
229impl ToTyped for Angle {
230 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
231 match self.0.unpack() {
232 Unpacked::Inline(unit, value) => NoCalcAngle::new(unit, value).to_typed(dest),
233 Unpacked::Boxed(calc) => calc.to_typed(dest),
234 }
235 }
236}
237
238impl SpecifiedValueInfo for Angle {}
239
240#[allow(missing_docs)]
248pub enum AllowUnitlessZeroAngle {
249 Yes,
250 No,
251}
252
253impl Parse for Angle {
254 fn parse<'i, 't>(
256 context: &ParserContext,
257 input: &mut Parser<'i, 't>,
258 ) -> Result<Self, ParseError<'i>> {
259 Self::parse_internal(context, input, AllowUnitlessZeroAngle::No)
260 }
261}
262
263impl Zero for Angle {
264 fn zero() -> Self {
265 Self::new(NoCalcAngle::zero())
266 }
267
268 fn is_zero(&self) -> bool {
269 match self.0.unpack() {
270 Unpacked::Inline(_, v) => v == 0.0,
271 Unpacked::Boxed(_) => false,
272 }
273 }
274}
275
276impl ToComputedValue for Angle {
277 type ComputedValue = ComputedAngle;
278
279 #[inline]
280 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
281 let degrees = match self.0.unpack() {
282 Unpacked::Inline(unit, value) => NoCalcAngle::new(unit, value).degrees(),
283 Unpacked::Boxed(ref calc) => calc.resolve(context, |result| match result {
284 Ok(Leaf::Angle(a)) => a.degrees(),
285 _ => {
286 debug_assert!(false, "Unexpected Angle::Calc without resolved angle");
287 f32::NAN
288 },
289 }),
290 };
291
292 ComputedAngle::from_degrees(if degrees.is_finite() { degrees } else { 0.0 })
294 }
295
296 #[inline]
297 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
298 Self::new(NoCalcAngle::from_degrees(computed.degrees()))
299 }
300}
301
302impl Angle {
303 #[inline]
305 pub fn new(angle: NoCalcAngle) -> Self {
306 Self(NumericUnion::inline(angle.unit, angle.value))
307 }
308
309 #[inline]
311 pub fn new_calc(calc: Box<CalcNumeric>) -> Self {
312 Self(NumericUnion::boxed(calc))
313 }
314
315 #[inline]
317 pub fn from_degrees(value: CSSFloat) -> Self {
318 Self::new(NoCalcAngle::from_degrees(value))
319 }
320
321 pub fn zero() -> Self {
323 Self::new(NoCalcAngle::zero())
324 }
325
326 #[inline]
328 pub fn is_calc(&self) -> bool {
329 self.0.is_boxed()
330 }
331
332 #[inline]
334 pub fn as_no_calc(&self) -> Option<NoCalcAngle> {
335 match self.0.unpack() {
336 Unpacked::Inline(unit, value) => Some(NoCalcAngle::new(unit, value)),
337 Unpacked::Boxed(_) => None,
338 }
339 }
340
341 #[inline]
345 pub fn degrees(&self) -> Option<CSSFloat> {
346 match self.0.unpack() {
347 Unpacked::Inline(unit, value) => Some(NoCalcAngle::new(unit, value).degrees()),
348 Unpacked::Boxed(ref calc) => calc
349 .as_angle()
350 .map(|a| calc.clamping_mode.clamp(a.degrees())),
351 }
352 }
353
354 #[inline]
358 pub fn parse_with_unitless<'i, 't>(
359 context: &ParserContext,
360 input: &mut Parser<'i, 't>,
361 ) -> Result<Self, ParseError<'i>> {
362 Self::parse_internal(context, input, AllowUnitlessZeroAngle::Yes)
363 }
364
365 pub(super) fn parse_internal<'i, 't>(
366 context: &ParserContext,
367 input: &mut Parser<'i, 't>,
368 allow_unitless_zero: AllowUnitlessZeroAngle,
369 ) -> Result<Self, ParseError<'i>> {
370 let location = input.current_source_location();
371 let t = input.next()?;
372 let allow_unitless_zero = matches!(allow_unitless_zero, AllowUnitlessZeroAngle::Yes);
373 match *t {
374 Token::Dimension {
375 value, ref unit, ..
376 } => match NoCalcAngle::parse_dimension(value, unit) {
377 Ok(angle) => Ok(Self::new(angle)),
378 Err(()) => {
379 let t = t.clone();
380 Err(input.new_unexpected_token_error(t))
381 },
382 },
383 Token::Function(ref name) => {
384 let function = CalcNode::math_function(context, name, location)?;
385 CalcNode::parse_angle(context, input, function)
386 .map(Box::new)
387 .map(Self::new_calc)
388 },
389 Token::Number { value, .. } if value == 0. && allow_unitless_zero => Ok(Angle::zero()),
390 ref t => {
391 let t = t.clone();
392 Err(input.new_unexpected_token_error(t))
393 },
394 }
395 }
396}
397
398impl Neg for Angle {
399 type Output = Angle;
400
401 #[inline]
402 fn neg(self) -> Angle {
403 match self.0.extract() {
404 Extracted::Inline(unit, value) => Self::new(NoCalcAngle::new(unit, -value)),
405 Extracted::Boxed(mut c) => {
406 c.node.negate();
407 Self::new_calc(c)
408 },
409 }
410 }
411}