mod.rs (32020B)
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 values. 6 //! 7 //! TODO(emilio): Enhance docs. 8 9 use super::computed::transform::DirectionVector; 10 use super::computed::{Context, ToComputedValue}; 11 use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks; 12 use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth}; 13 use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize}; 14 use super::generics::transform::IsParallelTo; 15 use super::generics::{self, GreaterThanOrEqualToOne, NonNegative}; 16 use super::{CSSFloat, CSSInteger}; 17 use crate::context::QuirksMode; 18 use crate::derives::*; 19 use crate::parser::{Parse, ParserContext}; 20 use crate::values::specified::calc::CalcNode; 21 use crate::values::{serialize_atom_identifier, serialize_number, AtomString}; 22 use crate::{Atom, Namespace, One, Prefix, Zero}; 23 use cssparser::{Parser, Token}; 24 use std::fmt::{self, Write}; 25 use std::ops::Add; 26 use style_traits::values::specified::AllowedNumericType; 27 use style_traits::{ 28 CssString, CssWriter, NumericValue, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss, 29 ToTyped, TypedValue, 30 }; 31 32 pub use self::align::{ContentDistribution, ItemPlacement, JustifyItems, SelfAlignment}; 33 pub use self::angle::{AllowUnitlessZeroAngle, Angle}; 34 pub use self::animation::{ 35 AnimationComposition, AnimationDirection, AnimationDuration, AnimationFillMode, 36 AnimationIterationCount, AnimationName, AnimationPlayState, AnimationTimeline, ScrollAxis, 37 TimelineName, TransitionBehavior, TransitionProperty, ViewTimelineInset, ViewTransitionClass, 38 ViewTransitionName, 39 }; 40 pub use self::background::{BackgroundRepeat, BackgroundSize}; 41 pub use self::basic_shape::FillRule; 42 pub use self::border::{ 43 BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, 44 BorderImageWidth, BorderRadius, BorderSideOffset, BorderSideWidth, BorderSpacing, BorderStyle, 45 LineWidth, 46 }; 47 pub use self::box_::{ 48 Appearance, BaselineSource, BreakBetween, BreakWithin, Clear, Contain, ContainIntrinsicSize, 49 ContainerName, ContainerType, ContentVisibility, Display, Float, LineClamp, Overflow, 50 OverflowAnchor, OverflowClipMargin, OverscrollBehavior, Perspective, PositionProperty, Resize, 51 ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, 52 ScrollbarGutter, TouchAction, VerticalAlign, WillChange, WillChangeBits, WritingModeProperty, 53 Zoom, 54 }; 55 pub use self::color::{ 56 Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust, 57 }; 58 pub use self::column::ColumnCount; 59 pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; 60 pub use self::easing::TimingFunction; 61 pub use self::effects::{BoxShadow, Filter, SimpleShadow}; 62 pub use self::flex::FlexBasis; 63 pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle}; 64 pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric}; 65 pub use self::font::{ 66 FontSize, FontSizeAdjust, FontSizeAdjustFactor, FontSizeKeyword, FontStretch, FontSynthesis, 67 FontSynthesisStyle, 68 }; 69 pub use self::font::{FontVariantAlternates, FontWeight}; 70 pub use self::font::{FontVariantEastAsian, FontVariationSettings, LineHeight}; 71 pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale}; 72 pub use self::image::{EndingShape as GradientEndingShape, Gradient, Image, ImageRendering}; 73 pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; 74 pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; 75 pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; 76 pub use self::length::{Margin, MaxSize, Size}; 77 pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant}; 78 pub use self::length::{ 79 NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto, 80 }; 81 #[cfg(feature = "gecko")] 82 pub use self::list::ListStyleType; 83 pub use self::list::Quotes; 84 pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate}; 85 pub use self::outline::OutlineStyle; 86 pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize}; 87 pub use self::percentage::{NonNegativePercentage, Percentage}; 88 pub use self::position::AnchorFunction; 89 pub use self::position::AnchorName; 90 pub use self::position::AnchorScope; 91 pub use self::position::AspectRatio; 92 pub use self::position::Inset; 93 pub use self::position::PositionAnchor; 94 pub use self::position::PositionTryFallbacks; 95 pub use self::position::PositionTryOrder; 96 pub use self::position::PositionVisibility; 97 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto}; 98 pub use self::position::{MasonryAutoFlow, MasonryItemOrder, MasonryPlacement}; 99 pub use self::position::{PositionArea, PositionAreaKeyword}; 100 pub use self::position::{PositionComponent, ZIndex}; 101 pub use self::ratio::Ratio; 102 pub use self::rect::NonNegativeLengthOrNumberRect; 103 pub use self::resolution::Resolution; 104 pub use self::svg::{DProperty, MozContextProperties}; 105 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint}; 106 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth, VectorEffect}; 107 pub use self::svg_path::SVGPathData; 108 pub use self::text::RubyPosition; 109 pub use self::text::{HyphenateCharacter, HyphenateLimitChars}; 110 pub use self::text::{InitialLetter, LetterSpacing, LineBreak, TextAlign, TextIndent}; 111 pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; 112 pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; 113 pub use self::text::{TextAlignLast, TextAutospace, TextUnderlinePosition}; 114 pub use self::text::{ 115 TextDecorationInset, TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform, 116 }; 117 pub use self::time::Time; 118 pub use self::transform::{Rotate, Scale, Transform}; 119 pub use self::transform::{TransformBox, TransformOrigin, TransformStyle, Translate}; 120 #[cfg(feature = "gecko")] 121 pub use self::ui::CursorImage; 122 pub use self::ui::{ 123 BoolInteger, Cursor, Inert, MozTheme, PointerEvents, ScrollbarColor, UserFocus, UserSelect, 124 }; 125 pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; 126 127 pub mod align; 128 pub mod angle; 129 pub mod animation; 130 pub mod background; 131 pub mod basic_shape; 132 pub mod border; 133 #[path = "box.rs"] 134 pub mod box_; 135 pub mod calc; 136 pub mod color; 137 pub mod column; 138 pub mod counters; 139 pub mod easing; 140 pub mod effects; 141 pub mod flex; 142 pub mod font; 143 pub mod grid; 144 pub mod image; 145 pub mod intersection_observer; 146 pub mod length; 147 pub mod list; 148 pub mod motion; 149 pub mod outline; 150 pub mod page; 151 pub mod percentage; 152 pub mod position; 153 pub mod ratio; 154 pub mod rect; 155 pub mod resolution; 156 pub mod source_size_list; 157 pub mod svg; 158 pub mod svg_path; 159 pub mod table; 160 pub mod text; 161 pub mod time; 162 pub mod transform; 163 pub mod ui; 164 pub mod url; 165 166 /// <angle> | <percentage> 167 /// https://drafts.csswg.org/css-values/#typedef-angle-percentage 168 #[allow(missing_docs)] 169 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] 170 pub enum AngleOrPercentage { 171 Percentage(Percentage), 172 Angle(Angle), 173 } 174 175 impl AngleOrPercentage { 176 fn parse_internal<'i, 't>( 177 context: &ParserContext, 178 input: &mut Parser<'i, 't>, 179 allow_unitless_zero: AllowUnitlessZeroAngle, 180 ) -> Result<Self, ParseError<'i>> { 181 if let Ok(per) = input.try_parse(|i| Percentage::parse(context, i)) { 182 return Ok(AngleOrPercentage::Percentage(per)); 183 } 184 185 Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle) 186 } 187 188 /// Allow unitless angles, used for conic-gradients as specified by the spec. 189 /// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle 190 pub fn parse_with_unitless<'i, 't>( 191 context: &ParserContext, 192 input: &mut Parser<'i, 't>, 193 ) -> Result<Self, ParseError<'i>> { 194 AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes) 195 } 196 } 197 198 impl Parse for AngleOrPercentage { 199 fn parse<'i, 't>( 200 context: &ParserContext, 201 input: &mut Parser<'i, 't>, 202 ) -> Result<Self, ParseError<'i>> { 203 AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No) 204 } 205 } 206 207 /// Parse a `<number>` value, with a given clamping mode. 208 fn parse_number_with_clamping_mode<'i, 't>( 209 context: &ParserContext, 210 input: &mut Parser<'i, 't>, 211 clamping_mode: AllowedNumericType, 212 ) -> Result<Number, ParseError<'i>> { 213 let location = input.current_source_location(); 214 match *input.next()? { 215 Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => { 216 Ok(Number { 217 value, 218 calc_clamping_mode: None, 219 }) 220 }, 221 Token::Function(ref name) => { 222 let function = CalcNode::math_function(context, name, location)?; 223 let value = CalcNode::parse_number(context, input, function)?; 224 Ok(Number { 225 value, 226 calc_clamping_mode: Some(clamping_mode), 227 }) 228 }, 229 ref t => Err(location.new_unexpected_token_error(t.clone())), 230 } 231 } 232 233 /// A CSS `<number>` specified value. 234 /// 235 /// https://drafts.csswg.org/css-values-3/#number-value 236 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialOrd, ToShmem)] 237 pub struct Number { 238 /// The numeric value itself. 239 value: CSSFloat, 240 /// If this number came from a calc() expression, this tells how clamping 241 /// should be done on the value. 242 calc_clamping_mode: Option<AllowedNumericType>, 243 } 244 245 impl Parse for Number { 246 fn parse<'i, 't>( 247 context: &ParserContext, 248 input: &mut Parser<'i, 't>, 249 ) -> Result<Self, ParseError<'i>> { 250 parse_number_with_clamping_mode(context, input, AllowedNumericType::All) 251 } 252 } 253 254 impl PartialEq<Number> for Number { 255 fn eq(&self, other: &Number) -> bool { 256 if self.calc_clamping_mode != other.calc_clamping_mode { 257 return false; 258 } 259 260 self.value == other.value || (self.value.is_nan() && other.value.is_nan()) 261 } 262 } 263 264 impl Number { 265 /// Returns a new number with the value `val`. 266 #[inline] 267 fn new_with_clamping_mode( 268 value: CSSFloat, 269 calc_clamping_mode: Option<AllowedNumericType>, 270 ) -> Self { 271 Self { 272 value, 273 calc_clamping_mode, 274 } 275 } 276 277 /// Returns this percentage as a number. 278 pub fn to_percentage(&self) -> Percentage { 279 Percentage::new_with_clamping_mode(self.value, self.calc_clamping_mode) 280 } 281 282 /// Returns a new number with the value `val`. 283 #[inline] 284 pub fn new(val: CSSFloat) -> Self { 285 Self::new_with_clamping_mode(val, None) 286 } 287 288 /// Returns whether this number came from a `calc()` expression. 289 #[inline] 290 pub fn was_calc(&self) -> bool { 291 self.calc_clamping_mode.is_some() 292 } 293 294 /// Returns the numeric value, clamped if needed. 295 #[inline] 296 pub fn get(&self) -> f32 { 297 crate::values::normalize( 298 self.calc_clamping_mode 299 .map_or(self.value, |mode| mode.clamp(self.value)), 300 ) 301 .min(f32::MAX) 302 .max(f32::MIN) 303 } 304 305 #[allow(missing_docs)] 306 pub fn parse_non_negative<'i, 't>( 307 context: &ParserContext, 308 input: &mut Parser<'i, 't>, 309 ) -> Result<Number, ParseError<'i>> { 310 parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative) 311 } 312 313 #[allow(missing_docs)] 314 pub fn parse_at_least_one<'i, 't>( 315 context: &ParserContext, 316 input: &mut Parser<'i, 't>, 317 ) -> Result<Number, ParseError<'i>> { 318 parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne) 319 } 320 321 /// Clamp to 1.0 if the value is over 1.0. 322 #[inline] 323 pub fn clamp_to_one(self) -> Self { 324 Number { 325 value: self.value.min(1.), 326 calc_clamping_mode: self.calc_clamping_mode, 327 } 328 } 329 } 330 331 impl ToComputedValue for Number { 332 type ComputedValue = CSSFloat; 333 334 #[inline] 335 fn to_computed_value(&self, _: &Context) -> CSSFloat { 336 self.get() 337 } 338 339 #[inline] 340 fn from_computed_value(computed: &CSSFloat) -> Self { 341 Number { 342 value: *computed, 343 calc_clamping_mode: None, 344 } 345 } 346 } 347 348 impl ToCss for Number { 349 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 350 where 351 W: Write, 352 { 353 serialize_number(self.value, self.calc_clamping_mode.is_some(), dest) 354 } 355 } 356 357 impl ToTyped for Number { 358 fn to_typed(&self) -> Option<TypedValue> { 359 let value = self.value; 360 let unit = CssString::from("number"); 361 Some(TypedValue::Numeric(NumericValue::Unit { value, unit })) 362 } 363 } 364 365 impl IsParallelTo for (Number, Number, Number) { 366 fn is_parallel_to(&self, vector: &DirectionVector) -> bool { 367 use euclid::approxeq::ApproxEq; 368 // If a and b is parallel, the angle between them is 0deg, so 369 // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. 370 let self_vector = DirectionVector::new(self.0.get(), self.1.get(), self.2.get()); 371 self_vector 372 .cross(*vector) 373 .square_length() 374 .approx_eq(&0.0f32) 375 } 376 } 377 378 impl SpecifiedValueInfo for Number {} 379 380 impl Add for Number { 381 type Output = Self; 382 383 fn add(self, other: Self) -> Self { 384 Self::new(self.get() + other.get()) 385 } 386 } 387 388 impl Zero for Number { 389 #[inline] 390 fn zero() -> Self { 391 Self::new(0.) 392 } 393 394 #[inline] 395 fn is_zero(&self) -> bool { 396 self.get() == 0. 397 } 398 } 399 400 impl From<Number> for f32 { 401 #[inline] 402 fn from(n: Number) -> Self { 403 n.get() 404 } 405 } 406 407 impl From<Number> for f64 { 408 #[inline] 409 fn from(n: Number) -> Self { 410 n.get() as f64 411 } 412 } 413 414 /// A Number which is >= 0.0. 415 pub type NonNegativeNumber = NonNegative<Number>; 416 417 impl Parse for NonNegativeNumber { 418 fn parse<'i, 't>( 419 context: &ParserContext, 420 input: &mut Parser<'i, 't>, 421 ) -> Result<Self, ParseError<'i>> { 422 parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative) 423 .map(NonNegative::<Number>) 424 } 425 } 426 427 impl One for NonNegativeNumber { 428 #[inline] 429 fn one() -> Self { 430 NonNegativeNumber::new(1.0) 431 } 432 433 #[inline] 434 fn is_one(&self) -> bool { 435 self.get() == 1.0 436 } 437 } 438 439 impl NonNegativeNumber { 440 /// Returns a new non-negative number with the value `val`. 441 pub fn new(val: CSSFloat) -> Self { 442 NonNegative::<Number>(Number::new(val.max(0.))) 443 } 444 445 /// Returns the numeric value. 446 #[inline] 447 pub fn get(&self) -> f32 { 448 self.0.get() 449 } 450 } 451 452 /// An Integer which is >= 0. 453 pub type NonNegativeInteger = NonNegative<Integer>; 454 455 impl Parse for NonNegativeInteger { 456 fn parse<'i, 't>( 457 context: &ParserContext, 458 input: &mut Parser<'i, 't>, 459 ) -> Result<Self, ParseError<'i>> { 460 Ok(NonNegative(Integer::parse_non_negative(context, input)?)) 461 } 462 } 463 464 /// A Number which is >= 1.0. 465 pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>; 466 467 impl Parse for GreaterThanOrEqualToOneNumber { 468 fn parse<'i, 't>( 469 context: &ParserContext, 470 input: &mut Parser<'i, 't>, 471 ) -> Result<Self, ParseError<'i>> { 472 parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne) 473 .map(GreaterThanOrEqualToOne::<Number>) 474 } 475 } 476 477 /// <number> | <percentage> 478 /// 479 /// Accepts only non-negative numbers. 480 #[allow(missing_docs)] 481 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] 482 pub enum NumberOrPercentage { 483 Percentage(Percentage), 484 Number(Number), 485 } 486 487 impl NumberOrPercentage { 488 fn parse_with_clamping_mode<'i, 't>( 489 context: &ParserContext, 490 input: &mut Parser<'i, 't>, 491 type_: AllowedNumericType, 492 ) -> Result<Self, ParseError<'i>> { 493 if let Ok(per) = 494 input.try_parse(|i| Percentage::parse_with_clamping_mode(context, i, type_)) 495 { 496 return Ok(NumberOrPercentage::Percentage(per)); 497 } 498 499 parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number) 500 } 501 502 /// Parse a non-negative number or percentage. 503 pub fn parse_non_negative<'i, 't>( 504 context: &ParserContext, 505 input: &mut Parser<'i, 't>, 506 ) -> Result<Self, ParseError<'i>> { 507 Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative) 508 } 509 510 /// Convert the number or the percentage to a number. 511 pub fn to_percentage(self) -> Percentage { 512 match self { 513 Self::Percentage(p) => p, 514 Self::Number(n) => n.to_percentage(), 515 } 516 } 517 518 /// Convert the number or the percentage to a number. 519 pub fn to_number(self) -> Number { 520 match self { 521 Self::Percentage(p) => p.to_number(), 522 Self::Number(n) => n, 523 } 524 } 525 } 526 527 impl Parse for NumberOrPercentage { 528 fn parse<'i, 't>( 529 context: &ParserContext, 530 input: &mut Parser<'i, 't>, 531 ) -> Result<Self, ParseError<'i>> { 532 Self::parse_with_clamping_mode(context, input, AllowedNumericType::All) 533 } 534 } 535 536 /// A non-negative <number> | <percentage>. 537 pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>; 538 539 impl NonNegativeNumberOrPercentage { 540 /// Returns the `100%` value. 541 #[inline] 542 pub fn hundred_percent() -> Self { 543 NonNegative(NumberOrPercentage::Percentage(Percentage::hundred())) 544 } 545 546 /// Return a particular number. 547 #[inline] 548 pub fn new_number(n: f32) -> Self { 549 NonNegative(NumberOrPercentage::Number(Number::new(n))) 550 } 551 } 552 553 impl Parse for NonNegativeNumberOrPercentage { 554 fn parse<'i, 't>( 555 context: &ParserContext, 556 input: &mut Parser<'i, 't>, 557 ) -> Result<Self, ParseError<'i>> { 558 Ok(NonNegative(NumberOrPercentage::parse_non_negative( 559 context, input, 560 )?)) 561 } 562 } 563 564 /// The value of Opacity is <alpha-value>, which is "<number> | <percentage>". 565 /// However, we serialize the specified value as number, so it's ok to store 566 /// the Opacity as Number. 567 #[derive( 568 Clone, 569 Copy, 570 Debug, 571 MallocSizeOf, 572 PartialEq, 573 PartialOrd, 574 SpecifiedValueInfo, 575 ToCss, 576 ToShmem, 577 ToTyped, 578 )] 579 pub struct Opacity(Number); 580 581 impl Parse for Opacity { 582 /// Opacity accepts <number> | <percentage>, so we parse it as NumberOrPercentage, 583 /// and then convert into an Number if it's a Percentage. 584 /// https://drafts.csswg.org/cssom/#serializing-css-values 585 fn parse<'i, 't>( 586 context: &ParserContext, 587 input: &mut Parser<'i, 't>, 588 ) -> Result<Self, ParseError<'i>> { 589 let number = NumberOrPercentage::parse(context, input)?.to_number(); 590 Ok(Opacity(number)) 591 } 592 } 593 594 impl ToComputedValue for Opacity { 595 type ComputedValue = CSSFloat; 596 597 #[inline] 598 fn to_computed_value(&self, context: &Context) -> CSSFloat { 599 let value = self.0.to_computed_value(context); 600 if context.for_smil_animation { 601 // SMIL expects to be able to interpolate between out-of-range 602 // opacity values. 603 value 604 } else { 605 value.min(1.0).max(0.0) 606 } 607 } 608 609 #[inline] 610 fn from_computed_value(computed: &CSSFloat) -> Self { 611 Opacity(Number::from_computed_value(computed)) 612 } 613 } 614 615 /// A specified `<integer>`, either a simple integer value or a calc expression. 616 /// Note that a calc expression may not actually be an integer; it will be rounded 617 /// at computed-value time. 618 /// 619 /// <https://drafts.csswg.org/css-values/#integers> 620 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem, ToTyped)] 621 pub enum Integer { 622 /// A literal integer value. 623 Literal(CSSInteger), 624 /// A calc expression, whose value will be rounded later if necessary. 625 Calc(CSSFloat), 626 } 627 628 impl Zero for Integer { 629 #[inline] 630 fn zero() -> Self { 631 Self::new(0) 632 } 633 634 #[inline] 635 fn is_zero(&self) -> bool { 636 *self == 0 637 } 638 } 639 640 impl One for Integer { 641 #[inline] 642 fn one() -> Self { 643 Self::new(1) 644 } 645 646 #[inline] 647 fn is_one(&self) -> bool { 648 *self == 1 649 } 650 } 651 652 impl PartialEq<i32> for Integer { 653 fn eq(&self, value: &i32) -> bool { 654 self.value() == *value 655 } 656 } 657 658 impl Integer { 659 /// Trivially constructs a new `Integer` value. 660 pub fn new(val: CSSInteger) -> Self { 661 Self::Literal(val) 662 } 663 664 /// Returns the (rounded) integer value associated with this value. 665 pub fn value(&self) -> CSSInteger { 666 match *self { 667 Self::Literal(i) => i, 668 Self::Calc(n) => (n + 0.5).floor() as CSSInteger, 669 } 670 } 671 672 /// Trivially constructs a new integer value from a `calc()` expression. 673 fn from_calc(val: CSSFloat) -> Self { 674 Self::Calc(val) 675 } 676 } 677 678 impl Parse for Integer { 679 fn parse<'i, 't>( 680 context: &ParserContext, 681 input: &mut Parser<'i, 't>, 682 ) -> Result<Self, ParseError<'i>> { 683 let location = input.current_source_location(); 684 match *input.next()? { 685 Token::Number { 686 int_value: Some(v), .. 687 } => Ok(Integer::new(v)), 688 Token::Function(ref name) => { 689 let function = CalcNode::math_function(context, name, location)?; 690 let result = CalcNode::parse_number(context, input, function)?; 691 Ok(Integer::from_calc(result)) 692 }, 693 ref t => Err(location.new_unexpected_token_error(t.clone())), 694 } 695 } 696 } 697 698 impl Integer { 699 /// Parse an integer value which is at least `min`. 700 pub fn parse_with_minimum<'i, 't>( 701 context: &ParserContext, 702 input: &mut Parser<'i, 't>, 703 min: i32, 704 ) -> Result<Integer, ParseError<'i>> { 705 let value = Integer::parse(context, input)?; 706 // FIXME(emilio): The spec asks us to avoid rejecting it at parse 707 // time except until computed value time. 708 // 709 // It's not totally clear it's worth it though, and no other browser 710 // does this. 711 if value.value() < min { 712 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 713 } 714 Ok(value) 715 } 716 717 /// Parse a non-negative integer. 718 pub fn parse_non_negative<'i, 't>( 719 context: &ParserContext, 720 input: &mut Parser<'i, 't>, 721 ) -> Result<Integer, ParseError<'i>> { 722 Integer::parse_with_minimum(context, input, 0) 723 } 724 725 /// Parse a positive integer (>= 1). 726 pub fn parse_positive<'i, 't>( 727 context: &ParserContext, 728 input: &mut Parser<'i, 't>, 729 ) -> Result<Integer, ParseError<'i>> { 730 Integer::parse_with_minimum(context, input, 1) 731 } 732 } 733 734 impl ToComputedValue for Integer { 735 type ComputedValue = i32; 736 737 #[inline] 738 fn to_computed_value(&self, _: &Context) -> i32 { 739 self.value() 740 } 741 742 #[inline] 743 fn from_computed_value(computed: &i32) -> Self { 744 Integer::new(*computed) 745 } 746 } 747 748 impl ToCss for Integer { 749 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 750 where 751 W: Write, 752 { 753 match *self { 754 Integer::Literal(i) => i.to_css(dest), 755 Integer::Calc(n) => { 756 dest.write_str("calc(")?; 757 n.to_css(dest)?; 758 dest.write_char(')') 759 }, 760 } 761 } 762 } 763 764 impl SpecifiedValueInfo for Integer {} 765 766 /// A wrapper of Integer, with value >= 1. 767 pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>; 768 769 impl Parse for PositiveInteger { 770 #[inline] 771 fn parse<'i, 't>( 772 context: &ParserContext, 773 input: &mut Parser<'i, 't>, 774 ) -> Result<Self, ParseError<'i>> { 775 Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne) 776 } 777 } 778 779 /// The specified value of a grid `<track-breadth>` 780 pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>; 781 782 /// The specified value of a grid `<track-size>` 783 pub type TrackSize = GenericTrackSize<LengthPercentage>; 784 785 /// The specified value of a grid `<track-size>+` 786 pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>; 787 788 /// The specified value of a grid `<track-list>` 789 /// (could also be `<auto-track-list>` or `<explicit-track-list>`) 790 pub type TrackList = GenericTrackList<LengthPercentage, Integer>; 791 792 /// The specified value of a `<grid-line>`. 793 pub type GridLine = GenericGridLine<Integer>; 794 795 /// `<grid-template-rows> | <grid-template-columns>` 796 pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>; 797 798 /// rect(...) 799 pub type ClipRect = generics::GenericClipRect<LengthOrAuto>; 800 801 impl Parse for ClipRect { 802 fn parse<'i, 't>( 803 context: &ParserContext, 804 input: &mut Parser<'i, 't>, 805 ) -> Result<Self, ParseError<'i>> { 806 Self::parse_quirky(context, input, AllowQuirks::No) 807 } 808 } 809 810 impl ClipRect { 811 /// Parses a rect(<top>, <left>, <bottom>, <right>), allowing quirks. 812 fn parse_quirky<'i, 't>( 813 context: &ParserContext, 814 input: &mut Parser<'i, 't>, 815 allow_quirks: AllowQuirks, 816 ) -> Result<Self, ParseError<'i>> { 817 input.expect_function_matching("rect")?; 818 819 fn parse_argument<'i, 't>( 820 context: &ParserContext, 821 input: &mut Parser<'i, 't>, 822 allow_quirks: AllowQuirks, 823 ) -> Result<LengthOrAuto, ParseError<'i>> { 824 LengthOrAuto::parse_quirky(context, input, allow_quirks) 825 } 826 827 input.parse_nested_block(|input| { 828 let top = parse_argument(context, input, allow_quirks)?; 829 let right; 830 let bottom; 831 let left; 832 833 if input.try_parse(|input| input.expect_comma()).is_ok() { 834 right = parse_argument(context, input, allow_quirks)?; 835 input.expect_comma()?; 836 bottom = parse_argument(context, input, allow_quirks)?; 837 input.expect_comma()?; 838 left = parse_argument(context, input, allow_quirks)?; 839 } else { 840 right = parse_argument(context, input, allow_quirks)?; 841 bottom = parse_argument(context, input, allow_quirks)?; 842 left = parse_argument(context, input, allow_quirks)?; 843 } 844 845 Ok(ClipRect { 846 top, 847 right, 848 bottom, 849 left, 850 }) 851 }) 852 } 853 } 854 855 /// rect(...) | auto 856 pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>; 857 858 impl ClipRectOrAuto { 859 /// Parses a ClipRect or Auto, allowing quirks. 860 pub fn parse_quirky<'i, 't>( 861 context: &ParserContext, 862 input: &mut Parser<'i, 't>, 863 allow_quirks: AllowQuirks, 864 ) -> Result<Self, ParseError<'i>> { 865 if let Ok(v) = input.try_parse(|i| ClipRect::parse_quirky(context, i, allow_quirks)) { 866 return Ok(generics::GenericClipRectOrAuto::Rect(v)); 867 } 868 input.expect_ident_matching("auto")?; 869 Ok(generics::GenericClipRectOrAuto::Auto) 870 } 871 } 872 873 /// Whether quirks are allowed in this context. 874 #[derive(Clone, Copy, PartialEq)] 875 pub enum AllowQuirks { 876 /// Quirks are not allowed. 877 No, 878 /// Quirks are allowed, in quirks mode. 879 Yes, 880 /// Quirks are always allowed, used for SVG lengths. 881 Always, 882 } 883 884 impl AllowQuirks { 885 /// Returns `true` if quirks are allowed in this context. 886 pub fn allowed(self, quirks_mode: QuirksMode) -> bool { 887 match self { 888 AllowQuirks::Always => true, 889 AllowQuirks::No => false, 890 AllowQuirks::Yes => quirks_mode == QuirksMode::Quirks, 891 } 892 } 893 } 894 895 /// An attr(...) rule 896 /// 897 /// `[namespace? `|`]? ident` 898 #[derive( 899 Clone, 900 Debug, 901 Eq, 902 MallocSizeOf, 903 PartialEq, 904 SpecifiedValueInfo, 905 ToComputedValue, 906 ToResolvedValue, 907 ToShmem, 908 )] 909 #[css(function)] 910 #[repr(C)] 911 pub struct Attr { 912 /// Optional namespace prefix. 913 pub namespace_prefix: Prefix, 914 /// Optional namespace URL. 915 pub namespace_url: Namespace, 916 /// Attribute name 917 pub attribute: Atom, 918 /// Fallback value 919 pub fallback: AtomString, 920 } 921 922 impl Parse for Attr { 923 fn parse<'i, 't>( 924 context: &ParserContext, 925 input: &mut Parser<'i, 't>, 926 ) -> Result<Attr, ParseError<'i>> { 927 input.expect_function_matching("attr")?; 928 input.parse_nested_block(|i| Attr::parse_function(context, i)) 929 } 930 } 931 932 /// Get the Namespace for a given prefix from the namespace map. 933 fn get_namespace_for_prefix(prefix: &Prefix, context: &ParserContext) -> Option<Namespace> { 934 context.namespaces.prefixes.get(prefix).cloned() 935 } 936 937 /// Try to parse a namespace and return it if parsed, or none if there was not one present 938 fn parse_namespace<'i, 't>( 939 context: &ParserContext, 940 input: &mut Parser<'i, 't>, 941 ) -> Result<(Prefix, Namespace), ParseError<'i>> { 942 let ns_prefix = match input.next()? { 943 Token::Ident(ref prefix) => Some(Prefix::from(prefix.as_ref())), 944 Token::Delim('|') => None, 945 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), 946 }; 947 948 if ns_prefix.is_some() && !matches!(*input.next_including_whitespace()?, Token::Delim('|')) { 949 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 950 } 951 952 if let Some(prefix) = ns_prefix { 953 let ns = match get_namespace_for_prefix(&prefix, context) { 954 Some(ns) => ns, 955 None => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), 956 }; 957 Ok((prefix, ns)) 958 } else { 959 Ok((Prefix::default(), Namespace::default())) 960 } 961 } 962 963 impl Attr { 964 /// Parse contents of attr() assuming we have already parsed `attr` and are 965 /// within a parse_nested_block() 966 pub fn parse_function<'i, 't>( 967 context: &ParserContext, 968 input: &mut Parser<'i, 't>, 969 ) -> Result<Attr, ParseError<'i>> { 970 // Syntax is `[namespace? '|']? ident [',' fallback]?` 971 let namespace = input 972 .try_parse(|input| parse_namespace(context, input)) 973 .ok(); 974 let namespace_is_some = namespace.is_some(); 975 let (namespace_prefix, namespace_url) = namespace.unwrap_or_default(); 976 977 // If there is a namespace, ensure no whitespace following '|' 978 let attribute = Atom::from(if namespace_is_some { 979 let location = input.current_source_location(); 980 match *input.next_including_whitespace()? { 981 Token::Ident(ref ident) => ident.as_ref(), 982 ref t => return Err(location.new_unexpected_token_error(t.clone())), 983 } 984 } else { 985 input.expect_ident()?.as_ref() 986 }); 987 988 // Fallback will always be a string value for now as we do not support 989 // attr() types yet. 990 let fallback = input 991 .try_parse(|input| -> Result<AtomString, ParseError<'i>> { 992 input.expect_comma()?; 993 Ok(input.expect_string()?.as_ref().into()) 994 }) 995 .unwrap_or_default(); 996 997 Ok(Attr { 998 namespace_prefix, 999 namespace_url, 1000 attribute, 1001 fallback, 1002 }) 1003 } 1004 } 1005 1006 impl ToCss for Attr { 1007 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 1008 where 1009 W: Write, 1010 { 1011 dest.write_str("attr(")?; 1012 if !self.namespace_prefix.is_empty() { 1013 serialize_atom_identifier(&self.namespace_prefix, dest)?; 1014 dest.write_char('|')?; 1015 } 1016 serialize_atom_identifier(&self.attribute, dest)?; 1017 1018 if !self.fallback.is_empty() { 1019 dest.write_str(", ")?; 1020 self.fallback.to_css(dest)?; 1021 } 1022 1023 dest.write_char(')') 1024 } 1025 }