angle.rs (8632B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Specified angles. 6 7 use crate::derives::*; 8 use crate::parser::{Parse, ParserContext}; 9 use crate::values::computed::angle::Angle as ComputedAngle; 10 use crate::values::computed::{Context, ToComputedValue}; 11 use crate::values::specified::calc::CalcNode; 12 use crate::values::CSSFloat; 13 use crate::Zero; 14 use cssparser::{match_ignore_ascii_case, Parser, Token}; 15 use std::f32::consts::PI; 16 use std::fmt::{self, Write}; 17 use std::ops::Neg; 18 use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, ToCss}; 19 20 /// A specified angle dimension. 21 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 22 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToCss, ToShmem)] 23 pub enum AngleDimension { 24 /// An angle with degree unit. 25 #[css(dimension)] 26 Deg(CSSFloat), 27 /// An angle with gradian unit. 28 #[css(dimension)] 29 Grad(CSSFloat), 30 /// An angle with radian unit. 31 #[css(dimension)] 32 Rad(CSSFloat), 33 /// An angle with turn unit. 34 #[css(dimension)] 35 Turn(CSSFloat), 36 } 37 38 impl 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 48 impl AngleDimension { 49 /// Returns the amount of degrees this angle represents. 50 #[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 /// A specified Angle value, which is just the angle dimension, plus whether it 84 /// was specified as `calc()` or not. 85 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 86 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)] 87 pub struct Angle { 88 value: AngleDimension, 89 was_calc: bool, 90 } 91 92 impl 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 105 impl 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 119 impl 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 // NaN and +-infinity should degenerate to 0: https://github.com/w3c/csswg-drafts/issues/6105 127 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 139 impl Angle { 140 /// Creates an angle with the given value in degrees. 141 #[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 /// Creates an angle with the given value in radians. 150 #[inline] 151 pub fn from_radians(value: CSSFloat) -> Self { 152 Angle { 153 value: AngleDimension::Rad(value), 154 was_calc: false, 155 } 156 } 157 158 /// Return `0deg`. 159 pub fn zero() -> Self { 160 Self::from_degrees(0.0, false) 161 } 162 163 /// Returns the value of the angle in degrees, mostly for `calc()`. 164 #[inline] 165 pub fn degrees(&self) -> CSSFloat { 166 self.value.degrees() 167 } 168 169 /// Returns the value of the angle in radians. 170 #[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 /// Whether this specified angle came from a `calc()` expression. 177 #[inline] 178 pub fn was_calc(&self) -> bool { 179 self.was_calc 180 } 181 182 /// Returns an `Angle` parsed from a `calc()` expression. 183 pub fn from_calc(degrees: CSSFloat) -> Self { 184 Angle { 185 value: AngleDimension::Deg(degrees), 186 was_calc: true, 187 } 188 } 189 190 /// Returns the unit of the angle. 191 #[inline] 192 pub fn unit(&self) -> &'static str { 193 self.value.unit() 194 } 195 } 196 197 /// Whether to allow parsing an unitless zero as a valid angle. 198 /// 199 /// This should always be `No`, except for exceptions like: 200 /// 201 /// https://github.com/w3c/fxtf-drafts/issues/228 202 /// 203 /// See also: https://github.com/w3c/csswg-drafts/issues/1162. 204 #[allow(missing_docs)] 205 pub enum AllowUnitlessZeroAngle { 206 Yes, 207 No, 208 } 209 210 impl Parse for Angle { 211 /// Parses an angle according to CSS-VALUES ยง 6.1. 212 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 220 impl Angle { 221 /// Parse an `<angle>` value given a value and an unit. 222 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 /// Parse an `<angle>` allowing unitless zero to represent a zero angle. 235 /// 236 /// See the comment in `AllowUnitlessZeroAngle` for why. 237 #[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, /* from_calc = */ 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 278 impl SpecifiedValueInfo for Angle {} 279 280 impl 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 }