1use crate::parser::{Parse, ParserContext};
8use crate::values::computed::angle::Angle as ComputedAngle;
9use crate::values::computed::{Context, ToComputedValue};
10use crate::values::specified::calc::CalcNode;
11use crate::values::CSSFloat;
12use crate::Zero;
13use cssparser::{Parser, Token};
14use std::f32::consts::PI;
15use std::fmt::{self, Write};
16use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, ToCss};
17
18#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
20#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToCss, ToShmem)]
21pub enum AngleDimension {
22 #[css(dimension)]
24 Deg(CSSFloat),
25 #[css(dimension)]
27 Grad(CSSFloat),
28 #[css(dimension)]
30 Rad(CSSFloat),
31 #[css(dimension)]
33 Turn(CSSFloat),
34}
35
36impl Zero for AngleDimension {
37 fn zero() -> Self {
38 AngleDimension::Deg(0.)
39 }
40
41 fn is_zero(&self) -> bool {
42 self.unitless_value() == 0.0
43 }
44}
45
46impl AngleDimension {
47 #[inline]
49 fn degrees(&self) -> CSSFloat {
50 const DEG_PER_RAD: f32 = 180.0 / PI;
51 const DEG_PER_TURN: f32 = 360.0;
52 const DEG_PER_GRAD: f32 = 180.0 / 200.0;
53
54 match *self {
55 AngleDimension::Deg(d) => d,
56 AngleDimension::Rad(rad) => rad * DEG_PER_RAD,
57 AngleDimension::Turn(turns) => turns * DEG_PER_TURN,
58 AngleDimension::Grad(gradians) => gradians * DEG_PER_GRAD,
59 }
60 }
61
62 fn unitless_value(&self) -> CSSFloat {
63 match *self {
64 AngleDimension::Deg(v) |
65 AngleDimension::Rad(v) |
66 AngleDimension::Turn(v) |
67 AngleDimension::Grad(v) => v,
68 }
69 }
70
71 fn unit(&self) -> &'static str {
72 match *self {
73 AngleDimension::Deg(_) => "deg",
74 AngleDimension::Rad(_) => "rad",
75 AngleDimension::Turn(_) => "turn",
76 AngleDimension::Grad(_) => "grad",
77 }
78 }
79}
80
81#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
84#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
85pub struct Angle {
86 value: AngleDimension,
87 was_calc: bool,
88}
89
90impl Zero for Angle {
91 fn zero() -> Self {
92 Self {
93 value: Zero::zero(),
94 was_calc: false,
95 }
96 }
97
98 fn is_zero(&self) -> bool {
99 self.value.is_zero()
100 }
101}
102
103impl ToCss for Angle {
104 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
105 where
106 W: Write,
107 {
108 crate::values::serialize_specified_dimension(
109 self.value.unitless_value(),
110 self.value.unit(),
111 self.was_calc,
112 dest,
113 )
114 }
115}
116
117impl ToComputedValue for Angle {
118 type ComputedValue = ComputedAngle;
119
120 #[inline]
121 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
122 let degrees = self.degrees();
123
124 ComputedAngle::from_degrees(if degrees.is_finite() { degrees } else { 0.0 })
126 }
127
128 #[inline]
129 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
130 Angle {
131 value: AngleDimension::Deg(computed.degrees()),
132 was_calc: false,
133 }
134 }
135}
136
137impl Angle {
138 #[inline]
140 pub fn from_degrees(value: CSSFloat, was_calc: bool) -> Self {
141 Angle {
142 value: AngleDimension::Deg(value),
143 was_calc,
144 }
145 }
146
147 #[inline]
149 pub fn from_radians(value: CSSFloat) -> Self {
150 Angle {
151 value: AngleDimension::Rad(value),
152 was_calc: false,
153 }
154 }
155
156 pub fn zero() -> Self {
158 Self::from_degrees(0.0, false)
159 }
160
161 #[inline]
163 pub fn degrees(&self) -> CSSFloat {
164 self.value.degrees()
165 }
166
167 #[inline]
169 pub fn radians(&self) -> CSSFloat {
170 const RAD_PER_DEG: f32 = PI / 180.0;
171 self.value.degrees() * RAD_PER_DEG
172 }
173
174 #[inline]
176 pub fn was_calc(&self) -> bool {
177 self.was_calc
178 }
179
180 pub fn from_calc(degrees: CSSFloat) -> Self {
182 Angle {
183 value: AngleDimension::Deg(degrees),
184 was_calc: true,
185 }
186 }
187
188 #[inline]
190 pub fn unit(&self) -> &'static str {
191 self.value.unit()
192 }
193}
194
195#[allow(missing_docs)]
203pub enum AllowUnitlessZeroAngle {
204 Yes,
205 No,
206}
207
208impl Parse for Angle {
209 fn parse<'i, 't>(
211 context: &ParserContext,
212 input: &mut Parser<'i, 't>,
213 ) -> Result<Self, ParseError<'i>> {
214 Self::parse_internal(context, input, AllowUnitlessZeroAngle::No)
215 }
216}
217
218impl Angle {
219 pub fn parse_dimension(value: CSSFloat, unit: &str, was_calc: bool) -> Result<Angle, ()> {
221 let value = match_ignore_ascii_case! { unit,
222 "deg" => AngleDimension::Deg(value),
223 "grad" => AngleDimension::Grad(value),
224 "turn" => AngleDimension::Turn(value),
225 "rad" => AngleDimension::Rad(value),
226 _ => return Err(())
227 };
228
229 Ok(Self { value, was_calc })
230 }
231
232 #[inline]
236 pub fn parse_with_unitless<'i, 't>(
237 context: &ParserContext,
238 input: &mut Parser<'i, 't>,
239 ) -> Result<Self, ParseError<'i>> {
240 Self::parse_internal(context, input, AllowUnitlessZeroAngle::Yes)
241 }
242
243 pub(super) fn parse_internal<'i, 't>(
244 context: &ParserContext,
245 input: &mut Parser<'i, 't>,
246 allow_unitless_zero: AllowUnitlessZeroAngle,
247 ) -> Result<Self, ParseError<'i>> {
248 let location = input.current_source_location();
249 let t = input.next()?;
250 let allow_unitless_zero = matches!(allow_unitless_zero, AllowUnitlessZeroAngle::Yes);
251 match *t {
252 Token::Dimension {
253 value, ref unit, ..
254 } => {
255 match Angle::parse_dimension(value, unit, false) {
256 Ok(angle) => Ok(angle),
257 Err(()) => {
258 let t = t.clone();
259 Err(input.new_unexpected_token_error(t))
260 },
261 }
262 },
263 Token::Function(ref name) => {
264 let function = CalcNode::math_function(context, name, location)?;
265 CalcNode::parse_angle(context, input, function)
266 },
267 Token::Number { value, .. } if value == 0. && allow_unitless_zero => Ok(Angle::zero()),
268 ref t => {
269 let t = t.clone();
270 Err(input.new_unexpected_token_error(t))
271 },
272 }
273 }
274}
275
276impl SpecifiedValueInfo for Angle {}