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