calc.rs (52292B)
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 //! [Calc expressions][calc]. 6 //! 7 //! [calc]: https://drafts.csswg.org/css-values/#calc-notation 8 9 use crate::color::parsing::ChannelKeyword; 10 use crate::derives::*; 11 use crate::parser::{Parse, ParserContext}; 12 use crate::values::generics::calc::{ 13 self as generic, CalcNodeLeaf, CalcUnits, MinMaxOp, ModRemOp, PositivePercentageBasis, 14 RoundingStrategy, SortKey, 15 }; 16 use crate::values::generics::length::GenericAnchorSizeFunction; 17 use crate::values::generics::position::{ 18 AnchorSideKeyword, GenericAnchorFunction, GenericAnchorSide, 19 }; 20 use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength}; 21 use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength}; 22 use crate::values::specified::{self, Angle, Resolution, Time}; 23 use crate::values::{serialize_number, serialize_percentage, CSSFloat, DashedIdent}; 24 use cssparser::{match_ignore_ascii_case, CowRcStr, Parser, Token}; 25 use debug_unreachable::debug_unreachable; 26 use smallvec::SmallVec; 27 use std::cmp; 28 use std::fmt::{self, Write}; 29 use style_traits::values::specified::AllowedNumericType; 30 use style_traits::{ 31 CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss, ToTyped, TypedValue, 32 }; 33 34 /// The name of the mathematical function that we're parsing. 35 #[derive(Clone, Copy, Debug, Parse)] 36 pub enum MathFunction { 37 /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc 38 Calc, 39 /// `min()`: https://drafts.csswg.org/css-values-4/#funcdef-min 40 Min, 41 /// `max()`: https://drafts.csswg.org/css-values-4/#funcdef-max 42 Max, 43 /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp 44 Clamp, 45 /// `round()`: https://drafts.csswg.org/css-values-4/#funcdef-round 46 Round, 47 /// `mod()`: https://drafts.csswg.org/css-values-4/#funcdef-mod 48 Mod, 49 /// `rem()`: https://drafts.csswg.org/css-values-4/#funcdef-rem 50 Rem, 51 /// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin 52 Sin, 53 /// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos 54 Cos, 55 /// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan 56 Tan, 57 /// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin 58 Asin, 59 /// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos 60 Acos, 61 /// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan 62 Atan, 63 /// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2 64 Atan2, 65 /// `pow()`: https://drafts.csswg.org/css-values-4/#funcdef-pow 66 Pow, 67 /// `sqrt()`: https://drafts.csswg.org/css-values-4/#funcdef-sqrt 68 Sqrt, 69 /// `hypot()`: https://drafts.csswg.org/css-values-4/#funcdef-hypot 70 Hypot, 71 /// `log()`: https://drafts.csswg.org/css-values-4/#funcdef-log 72 Log, 73 /// `exp()`: https://drafts.csswg.org/css-values-4/#funcdef-exp 74 Exp, 75 /// `abs()`: https://drafts.csswg.org/css-values-4/#funcdef-abs 76 Abs, 77 /// `sign()`: https://drafts.csswg.org/css-values-4/#funcdef-sign 78 Sign, 79 } 80 81 /// A leaf node inside a `Calc` expression's AST. 82 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] 83 #[repr(u8)] 84 pub enum Leaf { 85 /// `<length>` 86 Length(NoCalcLength), 87 /// `<angle>` 88 Angle(Angle), 89 /// `<time>` 90 Time(Time), 91 /// `<resolution>` 92 Resolution(Resolution), 93 /// A component of a color. 94 ColorComponent(ChannelKeyword), 95 /// `<percentage>` 96 Percentage(CSSFloat), 97 /// `<number>` 98 Number(CSSFloat), 99 } 100 101 impl Leaf { 102 fn as_length(&self) -> Option<&NoCalcLength> { 103 match *self { 104 Self::Length(ref l) => Some(l), 105 _ => None, 106 } 107 } 108 } 109 110 impl ToCss for Leaf { 111 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 112 where 113 W: Write, 114 { 115 match *self { 116 Self::Length(ref l) => l.to_css(dest), 117 Self::Number(n) => serialize_number(n, /* was_calc = */ false, dest), 118 Self::Resolution(ref r) => r.to_css(dest), 119 Self::Percentage(p) => serialize_percentage(p, dest), 120 Self::Angle(ref a) => a.to_css(dest), 121 Self::Time(ref t) => t.to_css(dest), 122 Self::ColorComponent(ref s) => s.to_css(dest), 123 } 124 } 125 } 126 127 impl ToTyped for Leaf { 128 fn to_typed(&self) -> Option<TypedValue> { 129 // XXX Only supporting Length for now 130 match *self { 131 Self::Length(ref l) => l.to_typed(), 132 _ => None, 133 } 134 } 135 } 136 137 /// A struct to hold a simplified `<length>` or `<percentage>` expression. 138 /// 139 /// In some cases, e.g. DOMMatrix, we support calc(), but reject all the 140 /// relative lengths, and to_computed_pixel_length_without_context() handles 141 /// this case. Therefore, if you want to add a new field, please make sure this 142 /// function work properly. 143 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem, ToTyped)] 144 #[allow(missing_docs)] 145 #[typed_value(derive_fields)] 146 pub struct CalcLengthPercentage { 147 #[css(skip)] 148 pub clamping_mode: AllowedNumericType, 149 pub node: CalcNode, 150 } 151 152 impl CalcLengthPercentage { 153 fn same_unit_length_as(a: &Self, b: &Self) -> Option<(CSSFloat, CSSFloat)> { 154 debug_assert_eq!(a.clamping_mode, b.clamping_mode); 155 debug_assert_eq!(a.clamping_mode, AllowedNumericType::All); 156 157 let a = a.node.as_leaf()?; 158 let b = b.node.as_leaf()?; 159 160 if a.sort_key() != b.sort_key() { 161 return None; 162 } 163 164 let a = a.as_length()?.unitless_value(); 165 let b = b.as_length()?.unitless_value(); 166 return Some((a, b)); 167 } 168 } 169 170 impl SpecifiedValueInfo for CalcLengthPercentage {} 171 172 /// Should parsing anchor-positioning functions in `calc()` be allowed? 173 #[derive(Clone, Copy, PartialEq)] 174 pub enum AllowAnchorPositioningFunctions { 175 /// Don't allow any anchor positioning function. 176 No, 177 /// Allow `anchor-size()` to be parsed. 178 AllowAnchorSize, 179 /// Allow `anchor()` and `anchor-size()` to be parsed. 180 AllowAnchorAndAnchorSize, 181 } 182 183 bitflags! { 184 /// Additional functions within math functions that are permitted to be parsed depending on 185 /// the context of parsing (e.g. Parsing `inset` allows use of `anchor()` within `calc()`). 186 #[derive(Clone, Copy, PartialEq, Eq)] 187 struct AdditionalFunctions: u8 { 188 /// `anchor()` function. 189 const ANCHOR = 1 << 0; 190 /// `anchor-size()` function. 191 const ANCHOR_SIZE = 1 << 1; 192 } 193 } 194 195 /// What is allowed to be parsed for math functions within in this context? 196 #[derive(Clone, Copy)] 197 pub struct AllowParse { 198 /// Units allowed to be parsed. 199 units: CalcUnits, 200 /// Additional functions allowed to be parsed in this context. 201 additional_functions: AdditionalFunctions, 202 } 203 204 impl AllowParse { 205 /// Allow only specified units to be parsed, without any additional functions. 206 pub fn new(units: CalcUnits) -> Self { 207 Self { 208 units, 209 additional_functions: AdditionalFunctions::empty(), 210 } 211 } 212 213 /// Add new units to the allowed units to be parsed. 214 fn new_including(mut self, units: CalcUnits) -> Self { 215 self.units |= units; 216 self 217 } 218 219 /// Should given unit be allowed to parse? 220 fn includes(&self, unit: CalcUnits) -> bool { 221 self.units.intersects(unit) 222 } 223 } 224 225 impl generic::CalcNodeLeaf for Leaf { 226 fn unit(&self) -> CalcUnits { 227 match self { 228 Leaf::Length(_) => CalcUnits::LENGTH, 229 Leaf::Angle(_) => CalcUnits::ANGLE, 230 Leaf::Time(_) => CalcUnits::TIME, 231 Leaf::Resolution(_) => CalcUnits::RESOLUTION, 232 Leaf::ColorComponent(_) => CalcUnits::COLOR_COMPONENT, 233 Leaf::Percentage(_) => CalcUnits::PERCENTAGE, 234 Leaf::Number(_) => CalcUnits::empty(), 235 } 236 } 237 238 fn unitless_value(&self) -> Option<f32> { 239 Some(match *self { 240 Self::Length(ref l) => l.unitless_value(), 241 Self::Percentage(n) | Self::Number(n) => n, 242 Self::Resolution(ref r) => r.dppx(), 243 Self::Angle(ref a) => a.degrees(), 244 Self::Time(ref t) => t.seconds(), 245 Self::ColorComponent(_) => return None, 246 }) 247 } 248 249 fn new_number(value: f32) -> Self { 250 Self::Number(value) 251 } 252 253 fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option<cmp::Ordering> { 254 use self::Leaf::*; 255 256 if std::mem::discriminant(self) != std::mem::discriminant(other) { 257 return None; 258 } 259 260 if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) { 261 return None; 262 } 263 264 let self_negative = self.is_negative().unwrap_or(false); 265 if self_negative != other.is_negative().unwrap_or(false) { 266 return Some(if self_negative { 267 cmp::Ordering::Less 268 } else { 269 cmp::Ordering::Greater 270 }); 271 } 272 273 match (self, other) { 274 (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other), 275 (&Length(ref one), &Length(ref other)) => one.partial_cmp(other), 276 (&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()), 277 (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()), 278 (&Resolution(ref one), &Resolution(ref other)) => one.dppx().partial_cmp(&other.dppx()), 279 (&Number(ref one), &Number(ref other)) => one.partial_cmp(other), 280 (&ColorComponent(ref one), &ColorComponent(ref other)) => one.partial_cmp(other), 281 _ => { 282 match *self { 283 Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) 284 | Resolution(..) | ColorComponent(..) => {}, 285 } 286 unsafe { 287 debug_unreachable!("Forgot a branch?"); 288 } 289 }, 290 } 291 } 292 293 fn as_number(&self) -> Option<f32> { 294 match *self { 295 Leaf::Length(_) 296 | Leaf::Angle(_) 297 | Leaf::Time(_) 298 | Leaf::Resolution(_) 299 | Leaf::Percentage(_) 300 | Leaf::ColorComponent(_) => None, 301 Leaf::Number(value) => Some(value), 302 } 303 } 304 305 fn sort_key(&self) -> SortKey { 306 match *self { 307 Self::Number(..) => SortKey::Number, 308 Self::Percentage(..) => SortKey::Percentage, 309 Self::Time(..) => SortKey::S, 310 Self::Resolution(..) => SortKey::Dppx, 311 Self::Angle(..) => SortKey::Deg, 312 Self::Length(ref l) => match *l { 313 NoCalcLength::Absolute(..) => SortKey::Px, 314 NoCalcLength::FontRelative(ref relative) => match *relative { 315 FontRelativeLength::Em(..) => SortKey::Em, 316 FontRelativeLength::Ex(..) => SortKey::Ex, 317 FontRelativeLength::Rex(..) => SortKey::Rex, 318 FontRelativeLength::Ch(..) => SortKey::Ch, 319 FontRelativeLength::Rch(..) => SortKey::Rch, 320 FontRelativeLength::Cap(..) => SortKey::Cap, 321 FontRelativeLength::Rcap(..) => SortKey::Rcap, 322 FontRelativeLength::Ic(..) => SortKey::Ic, 323 FontRelativeLength::Ric(..) => SortKey::Ric, 324 FontRelativeLength::Rem(..) => SortKey::Rem, 325 FontRelativeLength::Lh(..) => SortKey::Lh, 326 FontRelativeLength::Rlh(..) => SortKey::Rlh, 327 }, 328 NoCalcLength::ViewportPercentage(ref vp) => match *vp { 329 ViewportPercentageLength::Vh(..) => SortKey::Vh, 330 ViewportPercentageLength::Svh(..) => SortKey::Svh, 331 ViewportPercentageLength::Lvh(..) => SortKey::Lvh, 332 ViewportPercentageLength::Dvh(..) => SortKey::Dvh, 333 ViewportPercentageLength::Vw(..) => SortKey::Vw, 334 ViewportPercentageLength::Svw(..) => SortKey::Svw, 335 ViewportPercentageLength::Lvw(..) => SortKey::Lvw, 336 ViewportPercentageLength::Dvw(..) => SortKey::Dvw, 337 ViewportPercentageLength::Vmax(..) => SortKey::Vmax, 338 ViewportPercentageLength::Svmax(..) => SortKey::Svmax, 339 ViewportPercentageLength::Lvmax(..) => SortKey::Lvmax, 340 ViewportPercentageLength::Dvmax(..) => SortKey::Dvmax, 341 ViewportPercentageLength::Vmin(..) => SortKey::Vmin, 342 ViewportPercentageLength::Svmin(..) => SortKey::Svmin, 343 ViewportPercentageLength::Lvmin(..) => SortKey::Lvmin, 344 ViewportPercentageLength::Dvmin(..) => SortKey::Dvmin, 345 ViewportPercentageLength::Vb(..) => SortKey::Vb, 346 ViewportPercentageLength::Svb(..) => SortKey::Svb, 347 ViewportPercentageLength::Lvb(..) => SortKey::Lvb, 348 ViewportPercentageLength::Dvb(..) => SortKey::Dvb, 349 ViewportPercentageLength::Vi(..) => SortKey::Vi, 350 ViewportPercentageLength::Svi(..) => SortKey::Svi, 351 ViewportPercentageLength::Lvi(..) => SortKey::Lvi, 352 ViewportPercentageLength::Dvi(..) => SortKey::Dvi, 353 }, 354 NoCalcLength::ContainerRelative(ref cq) => match *cq { 355 ContainerRelativeLength::Cqw(..) => SortKey::Cqw, 356 ContainerRelativeLength::Cqh(..) => SortKey::Cqh, 357 ContainerRelativeLength::Cqi(..) => SortKey::Cqi, 358 ContainerRelativeLength::Cqb(..) => SortKey::Cqb, 359 ContainerRelativeLength::Cqmin(..) => SortKey::Cqmin, 360 ContainerRelativeLength::Cqmax(..) => SortKey::Cqmax, 361 }, 362 NoCalcLength::ServoCharacterWidth(..) => unreachable!(), 363 }, 364 Self::ColorComponent(..) => SortKey::ColorComponent, 365 } 366 } 367 368 fn simplify(&mut self) { 369 if let Self::Length(NoCalcLength::Absolute(ref mut abs)) = *self { 370 *abs = AbsoluteLength::Px(abs.to_px()); 371 } 372 } 373 374 /// Tries to merge one sum to another, that is, perform `x` + `y`. 375 /// 376 /// Only handles leaf nodes, it's the caller's responsibility to simplify 377 /// them before calling this if needed. 378 fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { 379 use self::Leaf::*; 380 381 if std::mem::discriminant(self) != std::mem::discriminant(other) { 382 return Err(()); 383 } 384 385 match (self, other) { 386 (&mut Number(ref mut one), &Number(ref other)) 387 | (&mut Percentage(ref mut one), &Percentage(ref other)) => { 388 *one += *other; 389 }, 390 (&mut Angle(ref mut one), &Angle(ref other)) => { 391 *one = specified::Angle::from_calc(one.degrees() + other.degrees()); 392 }, 393 (&mut Time(ref mut one), &Time(ref other)) => { 394 *one = specified::Time::from_seconds(one.seconds() + other.seconds()); 395 }, 396 (&mut Resolution(ref mut one), &Resolution(ref other)) => { 397 *one = specified::Resolution::from_dppx(one.dppx() + other.dppx()); 398 }, 399 (&mut Length(ref mut one), &Length(ref other)) => { 400 *one = one.try_op(other, std::ops::Add::add)?; 401 }, 402 (&mut ColorComponent(_), &ColorComponent(_)) => { 403 // Can not get the sum of color components, because they haven't been resolved yet. 404 return Err(()); 405 }, 406 _ => { 407 match *other { 408 Number(..) | Percentage(..) | Angle(..) | Time(..) | Resolution(..) 409 | Length(..) | ColorComponent(..) => {}, 410 } 411 unsafe { 412 debug_unreachable!(); 413 } 414 }, 415 } 416 417 Ok(()) 418 } 419 420 fn try_product_in_place(&mut self, other: &mut Self) -> bool { 421 if let Self::Number(ref mut left) = *self { 422 if let Self::Number(ref right) = *other { 423 // Both sides are numbers, so we can just modify the left side. 424 *left *= *right; 425 true 426 } else { 427 // The right side is not a number, so the result should be in the units of the right 428 // side. 429 if other.map(|v| v * *left).is_ok() { 430 std::mem::swap(self, other); 431 true 432 } else { 433 false 434 } 435 } 436 } else if let Self::Number(ref right) = *other { 437 // The left side is not a number, but the right side is, so the result is the left 438 // side unit. 439 self.map(|v| v * *right).is_ok() 440 } else { 441 // Neither side is a number, so a product is not possible. 442 false 443 } 444 } 445 446 fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> 447 where 448 O: Fn(f32, f32) -> f32, 449 { 450 use self::Leaf::*; 451 452 if std::mem::discriminant(self) != std::mem::discriminant(other) { 453 return Err(()); 454 } 455 456 match (self, other) { 457 (&Number(one), &Number(other)) => { 458 return Ok(Leaf::Number(op(one, other))); 459 }, 460 (&Percentage(one), &Percentage(other)) => { 461 return Ok(Leaf::Percentage(op(one, other))); 462 }, 463 (&Angle(ref one), &Angle(ref other)) => { 464 return Ok(Leaf::Angle(specified::Angle::from_calc(op( 465 one.degrees(), 466 other.degrees(), 467 )))); 468 }, 469 (&Resolution(ref one), &Resolution(ref other)) => { 470 return Ok(Leaf::Resolution(specified::Resolution::from_dppx(op( 471 one.dppx(), 472 other.dppx(), 473 )))); 474 }, 475 (&Time(ref one), &Time(ref other)) => { 476 return Ok(Leaf::Time(specified::Time::from_seconds(op( 477 one.seconds(), 478 other.seconds(), 479 )))); 480 }, 481 (&Length(ref one), &Length(ref other)) => { 482 return Ok(Leaf::Length(one.try_op(other, op)?)); 483 }, 484 _ => { 485 match *other { 486 Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) 487 | Resolution(..) | ColorComponent(..) => {}, 488 } 489 unsafe { 490 debug_unreachable!(); 491 } 492 }, 493 } 494 } 495 496 fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> { 497 Ok(match self { 498 Leaf::Length(one) => *one = one.map(op), 499 Leaf::Angle(one) => *one = specified::Angle::from_calc(op(one.degrees())), 500 Leaf::Time(one) => *one = specified::Time::from_seconds(op(one.seconds())), 501 Leaf::Resolution(one) => *one = specified::Resolution::from_dppx(op(one.dppx())), 502 Leaf::Percentage(one) => *one = op(*one), 503 Leaf::Number(one) => *one = op(*one), 504 Leaf::ColorComponent(..) => return Err(()), 505 }) 506 } 507 } 508 509 impl GenericAnchorSide<Box<CalcNode>> { 510 fn parse_in_calc<'i, 't>( 511 context: &ParserContext, 512 input: &mut Parser<'i, 't>, 513 ) -> Result<Self, ParseError<'i>> { 514 if let Ok(k) = input.try_parse(|i| AnchorSideKeyword::parse(i)) { 515 return Ok(Self::Keyword(k)); 516 } 517 Ok(Self::Percentage(Box::new(CalcNode::parse_argument( 518 context, 519 input, 520 AllowParse::new(CalcUnits::PERCENTAGE), 521 )?))) 522 } 523 } 524 525 impl GenericAnchorFunction<Box<CalcNode>, Box<CalcNode>> { 526 fn parse_in_calc<'i, 't>( 527 context: &ParserContext, 528 additional_functions: AdditionalFunctions, 529 input: &mut Parser<'i, 't>, 530 ) -> Result<Self, ParseError<'i>> { 531 if !static_prefs::pref!("layout.css.anchor-positioning.enabled") { 532 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 533 } 534 input.parse_nested_block(|i| { 535 let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok(); 536 let side = GenericAnchorSide::parse_in_calc(context, i)?; 537 let target_element = if target_element.is_none() { 538 i.try_parse(|i| DashedIdent::parse(context, i)).ok() 539 } else { 540 target_element 541 }; 542 let fallback = i 543 .try_parse(|i| { 544 i.expect_comma()?; 545 Ok::<Box<CalcNode>, ParseError<'i>>(Box::new( 546 CalcNode::parse_argument( 547 context, 548 i, 549 AllowParse { 550 units: CalcUnits::LENGTH_PERCENTAGE, 551 additional_functions, 552 }, 553 )? 554 .into_length_or_percentage(AllowedNumericType::All) 555 .map_err(|_| i.new_custom_error(StyleParseErrorKind::UnspecifiedError))? 556 .node, 557 )) 558 }) 559 .ok(); 560 Ok(Self { 561 target_element: target_element.unwrap_or_else(DashedIdent::empty), 562 side, 563 fallback: fallback.into(), 564 }) 565 }) 566 } 567 } 568 569 impl GenericAnchorSizeFunction<Box<CalcNode>> { 570 fn parse_in_calc<'i, 't>( 571 context: &ParserContext, 572 input: &mut Parser<'i, 't>, 573 ) -> Result<Self, ParseError<'i>> { 574 if !static_prefs::pref!("layout.css.anchor-positioning.enabled") { 575 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 576 } 577 GenericAnchorSizeFunction::parse_inner(context, input, |i| { 578 Ok(Box::new( 579 CalcNode::parse_argument( 580 context, 581 i, 582 AllowParse::new(CalcUnits::LENGTH_PERCENTAGE), 583 )? 584 .into_length_or_percentage(AllowedNumericType::All) 585 .map_err(|_| i.new_custom_error(StyleParseErrorKind::UnspecifiedError))? 586 .node, 587 )) 588 }) 589 } 590 } 591 592 /// Specified `anchor()` function in math functions. 593 pub type CalcAnchorFunction = generic::GenericCalcAnchorFunction<Leaf>; 594 /// Specified `anchor-size()` function in math functions. 595 pub type CalcAnchorSizeFunction = generic::GenericCalcAnchorSizeFunction<Leaf>; 596 597 /// A calc node representation for specified values. 598 pub type CalcNode = generic::GenericCalcNode<Leaf>; 599 impl CalcNode { 600 /// Tries to parse a single element in the expression, that is, a 601 /// `<length>`, `<angle>`, `<time>`, `<percentage>`, `<resolution>`, etc. 602 /// 603 /// May return a "complex" `CalcNode`, in the presence of a parenthesized 604 /// expression, for example. 605 fn parse_one<'i, 't>( 606 context: &ParserContext, 607 input: &mut Parser<'i, 't>, 608 allowed: AllowParse, 609 ) -> Result<Self, ParseError<'i>> { 610 let location = input.current_source_location(); 611 match input.next()? { 612 &Token::Number { value, .. } => Ok(CalcNode::Leaf(Leaf::Number(value))), 613 &Token::Dimension { 614 value, ref unit, .. 615 } => { 616 if allowed.includes(CalcUnits::LENGTH) { 617 if let Ok(l) = NoCalcLength::parse_dimension(context, value, unit) { 618 return Ok(CalcNode::Leaf(Leaf::Length(l))); 619 } 620 } 621 if allowed.includes(CalcUnits::ANGLE) { 622 if let Ok(a) = Angle::parse_dimension(value, unit, /* from_calc = */ true) { 623 return Ok(CalcNode::Leaf(Leaf::Angle(a))); 624 } 625 } 626 if allowed.includes(CalcUnits::TIME) { 627 if let Ok(t) = Time::parse_dimension(value, unit) { 628 return Ok(CalcNode::Leaf(Leaf::Time(t))); 629 } 630 } 631 if allowed.includes(CalcUnits::RESOLUTION) { 632 if let Ok(t) = Resolution::parse_dimension(value, unit) { 633 return Ok(CalcNode::Leaf(Leaf::Resolution(t))); 634 } 635 } 636 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 637 }, 638 &Token::Percentage { unit_value, .. } if allowed.includes(CalcUnits::PERCENTAGE) => { 639 Ok(CalcNode::Leaf(Leaf::Percentage(unit_value))) 640 }, 641 &Token::ParenthesisBlock => { 642 input.parse_nested_block(|input| CalcNode::parse_argument(context, input, allowed)) 643 }, 644 &Token::Function(ref name) 645 if allowed 646 .additional_functions 647 .intersects(AdditionalFunctions::ANCHOR) 648 && name.eq_ignore_ascii_case("anchor") => 649 { 650 let anchor_function = GenericAnchorFunction::parse_in_calc( 651 context, 652 allowed.additional_functions, 653 input, 654 )?; 655 Ok(CalcNode::Anchor(Box::new(anchor_function))) 656 }, 657 &Token::Function(ref name) 658 if allowed 659 .additional_functions 660 .intersects(AdditionalFunctions::ANCHOR_SIZE) 661 && name.eq_ignore_ascii_case("anchor-size") => 662 { 663 let anchor_size_function = 664 GenericAnchorSizeFunction::parse_in_calc(context, input)?; 665 Ok(CalcNode::AnchorSize(Box::new(anchor_size_function))) 666 }, 667 &Token::Function(ref name) => { 668 let function = CalcNode::math_function(context, name, location)?; 669 CalcNode::parse(context, input, function, allowed) 670 }, 671 &Token::Ident(ref ident) => { 672 let leaf = match_ignore_ascii_case! { &**ident, 673 "e" => Leaf::Number(std::f32::consts::E), 674 "pi" => Leaf::Number(std::f32::consts::PI), 675 "infinity" => Leaf::Number(f32::INFINITY), 676 "-infinity" => Leaf::Number(f32::NEG_INFINITY), 677 "nan" => Leaf::Number(f32::NAN), 678 _ => { 679 if crate::color::parsing::rcs_enabled() && 680 allowed.includes(CalcUnits::COLOR_COMPONENT) 681 { 682 if let Ok(channel_keyword) = ChannelKeyword::from_ident(&ident) { 683 Leaf::ColorComponent(channel_keyword) 684 } else { 685 return Err(location 686 .new_unexpected_token_error(Token::Ident(ident.clone()))); 687 } 688 } else { 689 return Err( 690 location.new_unexpected_token_error(Token::Ident(ident.clone())) 691 ); 692 } 693 }, 694 }; 695 Ok(CalcNode::Leaf(leaf)) 696 }, 697 t => Err(location.new_unexpected_token_error(t.clone())), 698 } 699 } 700 701 /// Parse a top-level `calc` expression, with all nested sub-expressions. 702 /// 703 /// This is in charge of parsing, for example, `2 + 3 * 100%`. 704 pub fn parse<'i, 't>( 705 context: &ParserContext, 706 input: &mut Parser<'i, 't>, 707 function: MathFunction, 708 allowed: AllowParse, 709 ) -> Result<Self, ParseError<'i>> { 710 input.parse_nested_block(|input| { 711 match function { 712 MathFunction::Calc => Self::parse_argument(context, input, allowed), 713 MathFunction::Clamp => { 714 let min = Self::parse_argument(context, input, allowed)?; 715 input.expect_comma()?; 716 let center = Self::parse_argument(context, input, allowed)?; 717 input.expect_comma()?; 718 let max = Self::parse_argument(context, input, allowed)?; 719 Ok(Self::Clamp { 720 min: Box::new(min), 721 center: Box::new(center), 722 max: Box::new(max), 723 }) 724 }, 725 MathFunction::Round => { 726 let strategy = input.try_parse(parse_rounding_strategy); 727 728 // <rounding-strategy> = nearest | up | down | to-zero 729 // https://drafts.csswg.org/css-values-4/#calc-syntax 730 fn parse_rounding_strategy<'i, 't>( 731 input: &mut Parser<'i, 't>, 732 ) -> Result<RoundingStrategy, ParseError<'i>> { 733 Ok(try_match_ident_ignore_ascii_case! { input, 734 "nearest" => RoundingStrategy::Nearest, 735 "up" => RoundingStrategy::Up, 736 "down" => RoundingStrategy::Down, 737 "to-zero" => RoundingStrategy::ToZero, 738 }) 739 } 740 741 if strategy.is_ok() { 742 input.expect_comma()?; 743 } 744 745 let value = Self::parse_argument(context, input, allowed)?; 746 747 // <step> defaults to the number 1 if not provided 748 // https://drafts.csswg.org/css-values-4/#funcdef-round 749 let step = input.try_parse(|input| { 750 input.expect_comma()?; 751 Self::parse_argument(context, input, allowed) 752 }); 753 754 let step = step.unwrap_or(Self::Leaf(Leaf::Number(1.0))); 755 756 Ok(Self::Round { 757 strategy: strategy.unwrap_or(RoundingStrategy::Nearest), 758 value: Box::new(value), 759 step: Box::new(step), 760 }) 761 }, 762 MathFunction::Mod | MathFunction::Rem => { 763 let dividend = Self::parse_argument(context, input, allowed)?; 764 input.expect_comma()?; 765 let divisor = Self::parse_argument(context, input, allowed)?; 766 767 let op = match function { 768 MathFunction::Mod => ModRemOp::Mod, 769 MathFunction::Rem => ModRemOp::Rem, 770 _ => unreachable!(), 771 }; 772 Ok(Self::ModRem { 773 dividend: Box::new(dividend), 774 divisor: Box::new(divisor), 775 op, 776 }) 777 }, 778 MathFunction::Min | MathFunction::Max => { 779 // TODO(emilio): The common case for parse_comma_separated 780 // is just one element, but for min / max is two, really... 781 // 782 // Consider adding an API to cssparser to specify the 783 // initial vector capacity? 784 let arguments = input.parse_comma_separated(|input| { 785 let result = Self::parse_argument(context, input, allowed)?; 786 Ok(result) 787 })?; 788 789 let op = match function { 790 MathFunction::Min => MinMaxOp::Min, 791 MathFunction::Max => MinMaxOp::Max, 792 _ => unreachable!(), 793 }; 794 795 Ok(Self::MinMax(arguments.into(), op)) 796 }, 797 MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => { 798 let a = Self::parse_angle_argument(context, input)?; 799 800 let number = match function { 801 MathFunction::Sin => a.sin(), 802 MathFunction::Cos => a.cos(), 803 MathFunction::Tan => a.tan(), 804 _ => unsafe { 805 debug_unreachable!("We just checked!"); 806 }, 807 }; 808 809 Ok(Self::Leaf(Leaf::Number(number))) 810 }, 811 MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => { 812 let a = Self::parse_number_argument(context, input)?; 813 814 let radians = match function { 815 MathFunction::Asin => a.asin(), 816 MathFunction::Acos => a.acos(), 817 MathFunction::Atan => a.atan(), 818 _ => unsafe { 819 debug_unreachable!("We just checked!"); 820 }, 821 }; 822 823 Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) 824 }, 825 MathFunction::Atan2 => { 826 let allow_all = allowed.new_including(CalcUnits::ALL); 827 let a = Self::parse_argument(context, input, allow_all)?; 828 input.expect_comma()?; 829 let b = Self::parse_argument(context, input, allow_all)?; 830 831 let radians = Self::try_resolve(input, || { 832 if let Ok(a) = a.to_number() { 833 let b = b.to_number()?; 834 return Ok(a.atan2(b)); 835 } 836 837 if let Ok(a) = a.to_percentage() { 838 let b = b.to_percentage()?; 839 return Ok(a.atan2(b)); 840 } 841 842 if let Ok(a) = a.to_time(None) { 843 let b = b.to_time(None)?; 844 return Ok(a.seconds().atan2(b.seconds())); 845 } 846 847 if let Ok(a) = a.to_angle() { 848 let b = b.to_angle()?; 849 return Ok(a.radians().atan2(b.radians())); 850 } 851 852 if let Ok(a) = a.to_resolution() { 853 let b = b.to_resolution()?; 854 return Ok(a.dppx().atan2(b.dppx())); 855 } 856 857 let a = a.into_length_or_percentage(AllowedNumericType::All)?; 858 let b = b.into_length_or_percentage(AllowedNumericType::All)?; 859 let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?; 860 861 Ok(a.atan2(b)) 862 })?; 863 864 Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) 865 }, 866 MathFunction::Pow => { 867 let a = Self::parse_number_argument(context, input)?; 868 input.expect_comma()?; 869 let b = Self::parse_number_argument(context, input)?; 870 871 let number = a.powf(b); 872 873 Ok(Self::Leaf(Leaf::Number(number))) 874 }, 875 MathFunction::Sqrt => { 876 let a = Self::parse_number_argument(context, input)?; 877 878 let number = a.sqrt(); 879 880 Ok(Self::Leaf(Leaf::Number(number))) 881 }, 882 MathFunction::Hypot => { 883 let arguments = input.parse_comma_separated(|input| { 884 let result = Self::parse_argument(context, input, allowed)?; 885 Ok(result) 886 })?; 887 888 Ok(Self::Hypot(arguments.into())) 889 }, 890 MathFunction::Log => { 891 let a = Self::parse_number_argument(context, input)?; 892 let b = input 893 .try_parse(|input| { 894 input.expect_comma()?; 895 Self::parse_number_argument(context, input) 896 }) 897 .ok(); 898 899 let number = match b { 900 Some(b) => a.log(b), 901 None => a.ln(), 902 }; 903 904 Ok(Self::Leaf(Leaf::Number(number))) 905 }, 906 MathFunction::Exp => { 907 let a = Self::parse_number_argument(context, input)?; 908 let number = a.exp(); 909 Ok(Self::Leaf(Leaf::Number(number))) 910 }, 911 MathFunction::Abs => { 912 let node = Self::parse_argument(context, input, allowed)?; 913 Ok(Self::Abs(Box::new(node))) 914 }, 915 MathFunction::Sign => { 916 // The sign of a percentage is dependent on the percentage basis, so if 917 // percentages aren't allowed (so there's no basis) we shouldn't allow them in 918 // sign(). The rest of the units are safe tho. 919 let node = Self::parse_argument( 920 context, 921 input, 922 allowed.new_including(CalcUnits::ALL - CalcUnits::PERCENTAGE), 923 )?; 924 Ok(Self::Sign(Box::new(node))) 925 }, 926 } 927 }) 928 } 929 930 fn parse_angle_argument<'i, 't>( 931 context: &ParserContext, 932 input: &mut Parser<'i, 't>, 933 ) -> Result<CSSFloat, ParseError<'i>> { 934 let argument = Self::parse_argument(context, input, AllowParse::new(CalcUnits::ANGLE))?; 935 argument 936 .to_number() 937 .or_else(|()| Ok(argument.to_angle()?.radians())) 938 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 939 } 940 941 fn parse_number_argument<'i, 't>( 942 context: &ParserContext, 943 input: &mut Parser<'i, 't>, 944 ) -> Result<CSSFloat, ParseError<'i>> { 945 Self::parse_argument(context, input, AllowParse::new(CalcUnits::empty()))? 946 .to_number() 947 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 948 } 949 950 fn parse_argument<'i, 't>( 951 context: &ParserContext, 952 input: &mut Parser<'i, 't>, 953 allowed: AllowParse, 954 ) -> Result<Self, ParseError<'i>> { 955 let mut sum = SmallVec::<[CalcNode; 1]>::new(); 956 let first = Self::parse_product(context, input, allowed)?; 957 sum.push(first); 958 loop { 959 let start = input.state(); 960 match input.next_including_whitespace() { 961 Ok(&Token::WhiteSpace(_)) => { 962 if input.is_exhausted() { 963 break; // allow trailing whitespace 964 } 965 match *input.next()? { 966 Token::Delim('+') => { 967 let rhs = Self::parse_product(context, input, allowed)?; 968 if sum.last_mut().unwrap().try_sum_in_place(&rhs).is_err() { 969 sum.push(rhs); 970 } 971 }, 972 Token::Delim('-') => { 973 let mut rhs = Self::parse_product(context, input, allowed)?; 974 rhs.negate(); 975 if sum.last_mut().unwrap().try_sum_in_place(&rhs).is_err() { 976 sum.push(rhs); 977 } 978 }, 979 _ => { 980 input.reset(&start); 981 break; 982 }, 983 } 984 }, 985 _ => { 986 input.reset(&start); 987 break; 988 }, 989 } 990 } 991 992 Ok(if sum.len() == 1 { 993 sum.drain(..).next().unwrap() 994 } else { 995 Self::Sum(sum.into_boxed_slice().into()) 996 }) 997 } 998 999 /// Parse a top-level `calc` expression, and all the products that may 1000 /// follow, and stop as soon as a non-product expression is found. 1001 /// 1002 /// This should parse correctly: 1003 /// 1004 /// * `2` 1005 /// * `2 * 2` 1006 /// * `2 * 2 + 2` (but will leave the `+ 2` unparsed). 1007 /// 1008 fn parse_product<'i, 't>( 1009 context: &ParserContext, 1010 input: &mut Parser<'i, 't>, 1011 allowed: AllowParse, 1012 ) -> Result<Self, ParseError<'i>> { 1013 let mut product = SmallVec::<[CalcNode; 1]>::new(); 1014 let first = Self::parse_one(context, input, allowed)?; 1015 product.push(first); 1016 1017 loop { 1018 let start = input.state(); 1019 match input.next() { 1020 Ok(&Token::Delim('*')) => { 1021 let mut rhs = Self::parse_one(context, input, allowed)?; 1022 1023 // We can unwrap here, becuase we start the function by adding a node to 1024 // the list. 1025 if !product.last_mut().unwrap().try_product_in_place(&mut rhs) { 1026 product.push(rhs); 1027 } 1028 }, 1029 Ok(&Token::Delim('/')) => { 1030 let rhs = Self::parse_one(context, input, allowed)?; 1031 1032 enum InPlaceDivisionResult { 1033 /// The right was merged into the left. 1034 Merged, 1035 /// The right is not a number or could not be resolved, so the left is 1036 /// unchanged. 1037 Unchanged, 1038 /// The right was resolved, but was not a number, so the calculation is 1039 /// invalid. 1040 Invalid, 1041 } 1042 1043 fn try_division_in_place( 1044 left: &mut CalcNode, 1045 right: &CalcNode, 1046 ) -> InPlaceDivisionResult { 1047 if let Ok(resolved) = right.resolve() { 1048 if let Some(number) = resolved.as_number() { 1049 if number != 1.0 && left.is_product_distributive() { 1050 if left.map(|l| l / number).is_err() { 1051 return InPlaceDivisionResult::Invalid; 1052 } 1053 return InPlaceDivisionResult::Merged; 1054 } 1055 } else { 1056 // Color components are valid denominators, but they can't resolve 1057 // at parse time. 1058 return if resolved.unit().contains(CalcUnits::COLOR_COMPONENT) { 1059 InPlaceDivisionResult::Unchanged 1060 } else { 1061 InPlaceDivisionResult::Invalid 1062 }; 1063 } 1064 } 1065 InPlaceDivisionResult::Unchanged 1066 } 1067 1068 // The right hand side of a division *must* be a number, so if we can 1069 // already resolve it, then merge it with the last node on the product list. 1070 // We can unwrap here, becuase we start the function by adding a node to 1071 // the list. 1072 match try_division_in_place(&mut product.last_mut().unwrap(), &rhs) { 1073 InPlaceDivisionResult::Merged => {}, 1074 InPlaceDivisionResult::Unchanged => { 1075 product.push(Self::Invert(Box::new(rhs))) 1076 }, 1077 InPlaceDivisionResult::Invalid => { 1078 return Err( 1079 input.new_custom_error(StyleParseErrorKind::UnspecifiedError) 1080 ) 1081 }, 1082 } 1083 }, 1084 _ => { 1085 input.reset(&start); 1086 break; 1087 }, 1088 } 1089 } 1090 1091 Ok(if product.len() == 1 { 1092 product.drain(..).next().unwrap() 1093 } else { 1094 Self::Product(product.into_boxed_slice().into()) 1095 }) 1096 } 1097 1098 fn try_resolve<'i, 't, F>( 1099 input: &Parser<'i, 't>, 1100 closure: F, 1101 ) -> Result<CSSFloat, ParseError<'i>> 1102 where 1103 F: FnOnce() -> Result<CSSFloat, ()>, 1104 { 1105 closure().map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1106 } 1107 1108 /// Tries to simplify this expression into a `<length>` or `<percentage>` 1109 /// value. 1110 pub fn into_length_or_percentage( 1111 mut self, 1112 clamping_mode: AllowedNumericType, 1113 ) -> Result<CalcLengthPercentage, ()> { 1114 self.simplify_and_sort(); 1115 1116 // Although we allow numbers inside CalcLengthPercentage, calculations that resolve to a 1117 // number result is still not allowed. 1118 let unit = self.unit()?; 1119 if !CalcUnits::LENGTH_PERCENTAGE.intersects(unit) { 1120 Err(()) 1121 } else { 1122 Ok(CalcLengthPercentage { 1123 clamping_mode, 1124 node: self, 1125 }) 1126 } 1127 } 1128 1129 /// Tries to simplify this expression into a `<time>` value. 1130 fn to_time(&self, clamping_mode: Option<AllowedNumericType>) -> Result<Time, ()> { 1131 let seconds = if let Leaf::Time(time) = self.resolve()? { 1132 time.seconds() 1133 } else { 1134 return Err(()); 1135 }; 1136 1137 Ok(Time::from_seconds_with_calc_clamping_mode( 1138 seconds, 1139 clamping_mode, 1140 )) 1141 } 1142 1143 /// Tries to simplify the expression into a `<resolution>` value. 1144 fn to_resolution(&self) -> Result<Resolution, ()> { 1145 let dppx = if let Leaf::Resolution(resolution) = self.resolve()? { 1146 resolution.dppx() 1147 } else { 1148 return Err(()); 1149 }; 1150 1151 Ok(Resolution::from_dppx_calc(dppx)) 1152 } 1153 1154 /// Tries to simplify this expression into an `Angle` value. 1155 fn to_angle(&self) -> Result<Angle, ()> { 1156 let degrees = if let Leaf::Angle(angle) = self.resolve()? { 1157 angle.degrees() 1158 } else { 1159 return Err(()); 1160 }; 1161 1162 let result = Angle::from_calc(degrees); 1163 Ok(result) 1164 } 1165 1166 /// Tries to simplify this expression into a `<number>` value. 1167 fn to_number(&self) -> Result<CSSFloat, ()> { 1168 let number = if let Leaf::Number(number) = self.resolve()? { 1169 number 1170 } else { 1171 return Err(()); 1172 }; 1173 1174 let result = number; 1175 1176 Ok(result) 1177 } 1178 1179 /// Tries to simplify this expression into a `<percentage>` value. 1180 fn to_percentage(&self) -> Result<CSSFloat, ()> { 1181 if let Leaf::Percentage(percentage) = self.resolve()? { 1182 Ok(percentage) 1183 } else { 1184 Err(()) 1185 } 1186 } 1187 1188 /// Given a function name, and the location from where the token came from, 1189 /// return a mathematical function corresponding to that name or an error. 1190 #[inline] 1191 pub fn math_function<'i>( 1192 _: &ParserContext, 1193 name: &CowRcStr<'i>, 1194 location: cssparser::SourceLocation, 1195 ) -> Result<MathFunction, ParseError<'i>> { 1196 let function = match MathFunction::from_ident(&*name) { 1197 Ok(f) => f, 1198 Err(()) => { 1199 return Err(location.new_unexpected_token_error(Token::Function(name.clone()))) 1200 }, 1201 }; 1202 1203 Ok(function) 1204 } 1205 1206 /// Convenience parsing function for `<length> | <percentage>`, and, optionally, `anchor()`. 1207 pub fn parse_length_or_percentage<'i, 't>( 1208 context: &ParserContext, 1209 input: &mut Parser<'i, 't>, 1210 clamping_mode: AllowedNumericType, 1211 function: MathFunction, 1212 allow_anchor: AllowAnchorPositioningFunctions, 1213 ) -> Result<CalcLengthPercentage, ParseError<'i>> { 1214 let allowed = if allow_anchor == AllowAnchorPositioningFunctions::No { 1215 AllowParse::new(CalcUnits::LENGTH_PERCENTAGE) 1216 } else { 1217 AllowParse { 1218 units: CalcUnits::LENGTH_PERCENTAGE, 1219 additional_functions: match allow_anchor { 1220 AllowAnchorPositioningFunctions::No => unreachable!(), 1221 AllowAnchorPositioningFunctions::AllowAnchorSize => { 1222 AdditionalFunctions::ANCHOR_SIZE 1223 }, 1224 AllowAnchorPositioningFunctions::AllowAnchorAndAnchorSize => { 1225 AdditionalFunctions::ANCHOR | AdditionalFunctions::ANCHOR_SIZE 1226 }, 1227 }, 1228 } 1229 }; 1230 Self::parse(context, input, function, allowed)? 1231 .into_length_or_percentage(clamping_mode) 1232 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1233 } 1234 1235 /// Convenience parsing function for percentages. 1236 pub fn parse_percentage<'i, 't>( 1237 context: &ParserContext, 1238 input: &mut Parser<'i, 't>, 1239 function: MathFunction, 1240 ) -> Result<CSSFloat, ParseError<'i>> { 1241 Self::parse( 1242 context, 1243 input, 1244 function, 1245 AllowParse::new(CalcUnits::PERCENTAGE), 1246 )? 1247 .to_percentage() 1248 .map(crate::values::normalize) 1249 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1250 } 1251 1252 /// Convenience parsing function for `<length>`. 1253 pub fn parse_length<'i, 't>( 1254 context: &ParserContext, 1255 input: &mut Parser<'i, 't>, 1256 clamping_mode: AllowedNumericType, 1257 function: MathFunction, 1258 ) -> Result<CalcLengthPercentage, ParseError<'i>> { 1259 Self::parse(context, input, function, AllowParse::new(CalcUnits::LENGTH))? 1260 .into_length_or_percentage(clamping_mode) 1261 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1262 } 1263 1264 /// Convenience parsing function for `<number>`. 1265 pub fn parse_number<'i, 't>( 1266 context: &ParserContext, 1267 input: &mut Parser<'i, 't>, 1268 function: MathFunction, 1269 ) -> Result<CSSFloat, ParseError<'i>> { 1270 Self::parse( 1271 context, 1272 input, 1273 function, 1274 AllowParse::new(CalcUnits::empty()), 1275 )? 1276 .to_number() 1277 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1278 } 1279 1280 /// Convenience parsing function for `<angle>`. 1281 pub fn parse_angle<'i, 't>( 1282 context: &ParserContext, 1283 input: &mut Parser<'i, 't>, 1284 function: MathFunction, 1285 ) -> Result<Angle, ParseError<'i>> { 1286 Self::parse(context, input, function, AllowParse::new(CalcUnits::ANGLE))? 1287 .to_angle() 1288 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1289 } 1290 1291 /// Convenience parsing function for `<time>`. 1292 pub fn parse_time<'i, 't>( 1293 context: &ParserContext, 1294 input: &mut Parser<'i, 't>, 1295 clamping_mode: AllowedNumericType, 1296 function: MathFunction, 1297 ) -> Result<Time, ParseError<'i>> { 1298 Self::parse(context, input, function, AllowParse::new(CalcUnits::TIME))? 1299 .to_time(Some(clamping_mode)) 1300 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1301 } 1302 1303 /// Convenience parsing function for `<resolution>`. 1304 pub fn parse_resolution<'i, 't>( 1305 context: &ParserContext, 1306 input: &mut Parser<'i, 't>, 1307 function: MathFunction, 1308 ) -> Result<Resolution, ParseError<'i>> { 1309 Self::parse( 1310 context, 1311 input, 1312 function, 1313 AllowParse::new(CalcUnits::RESOLUTION), 1314 )? 1315 .to_resolution() 1316 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1317 } 1318 }