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