diff options
Diffstat (limited to 'servo/components/style/values/specified/mod.rs')
-rw-r--r-- | servo/components/style/values/specified/mod.rs | 954 |
1 files changed, 954 insertions, 0 deletions
diff --git a/servo/components/style/values/specified/mod.rs b/servo/components/style/values/specified/mod.rs new file mode 100644 index 0000000000..f247b7f948 --- /dev/null +++ b/servo/components/style/values/specified/mod.rs @@ -0,0 +1,954 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Specified values. +//! +//! TODO(emilio): Enhance docs. + +use super::computed::transform::DirectionVector; +use super::computed::{Context, ToComputedValue}; +use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks; +use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth}; +use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize}; +use super::generics::transform::IsParallelTo; +use super::generics::{self, GreaterThanOrEqualToOne, NonNegative}; +use super::{CSSFloat, CSSInteger}; +use crate::context::QuirksMode; +use crate::parser::{Parse, ParserContext}; +use crate::values::serialize_atom_identifier; +use crate::values::specified::calc::CalcNode; +use crate::{Atom, Namespace, One, Prefix, Zero}; +use cssparser::{Parser, Token}; +use std::fmt::{self, Write}; +use std::ops::Add; +use style_traits::values::specified::AllowedNumericType; +use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; + +#[cfg(feature = "gecko")] +pub use self::align::{AlignContent, AlignItems, AlignSelf, AlignTracks, ContentDistribution}; +#[cfg(feature = "gecko")] +pub use self::align::{JustifyContent, JustifyItems, JustifySelf, JustifyTracks, SelfAlignment}; +pub use self::angle::{AllowUnitlessZeroAngle, Angle}; +pub use self::animation::{AnimationIterationCount, AnimationName, AnimationTimeline}; +pub use self::animation::{ScrollAxis, ScrollTimelineName, TransitionProperty, ViewTimelineInset}; +pub use self::background::{BackgroundRepeat, BackgroundSize}; +pub use self::basic_shape::FillRule; +pub use self::border::{ + BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, + BorderImageWidth, BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle, LineWidth, +}; +pub use self::box_::{ + Appearance, BreakBetween, BaselineSource, BreakWithin, Contain, ContainerName, ContainerType, + Clear, ContainIntrinsicSize, ContentVisibility, Display, Float, LineClamp, Overflow, + OverflowAnchor, OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter, + ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, + TouchAction, VerticalAlign, WillChange, +}; +pub use self::color::{ + Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust, +}; +pub use self::column::ColumnCount; +pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; +pub use self::easing::TimingFunction; +pub use self::effects::{BoxShadow, Filter, SimpleShadow}; +pub use self::flex::FlexBasis; +pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle}; +pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric}; +pub use self::font::{FontSize, FontSizeAdjust, FontSizeKeyword, FontStretch, FontSynthesis}; +pub use self::font::{FontVariantAlternates, FontWeight}; +pub use self::font::{FontVariantEastAsian, FontVariationSettings}; +pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale}; +pub use self::image::{EndingShape as GradientEndingShape, Gradient}; +pub use self::image::{Image, ImageRendering, MozImageRect}; +pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; +pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; +pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; +pub use self::length::{MaxSize, Size}; +pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant}; +pub use self::length::{ + NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto, +}; +#[cfg(feature = "gecko")] +pub use self::list::ListStyleType; +pub use self::list::Quotes; +pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate}; +pub use self::outline::OutlineStyle; +pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize}; +pub use self::percentage::{NonNegativePercentage, Percentage}; +pub use self::position::AspectRatio; +pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto}; +pub use self::position::{MasonryAutoFlow, MasonryItemOrder, MasonryPlacement}; +pub use self::position::{PositionComponent, ZIndex}; +pub use self::ratio::Ratio; +pub use self::rect::NonNegativeLengthOrNumberRect; +pub use self::resolution::Resolution; +pub use self::svg::{DProperty, MozContextProperties}; +pub use self::svg::{SVGLength, SVGOpacity, SVGPaint}; +pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; +pub use self::svg_path::SVGPathData; +pub use self::text::HyphenateCharacter; +pub use self::text::RubyPosition; +pub use self::text::TextAlignLast; +pub use self::text::TextUnderlinePosition; +pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight, TextAlign}; +pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; +pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; +pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform}; +pub use self::time::Time; +pub use self::transform::{Rotate, Scale, Transform}; +pub use self::transform::{TransformOrigin, TransformStyle, Translate}; +#[cfg(feature = "gecko")] +pub use self::ui::CursorImage; +pub use self::ui::{BoolInteger, Cursor, UserSelect}; +pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; + +#[cfg(feature = "gecko")] +pub mod align; +pub mod angle; +pub mod animation; +pub mod background; +pub mod basic_shape; +pub mod border; +#[path = "box.rs"] +pub mod box_; +pub mod calc; +pub mod color; +pub mod column; +pub mod counters; +pub mod easing; +pub mod effects; +pub mod flex; +pub mod font; +#[cfg(feature = "gecko")] +pub mod gecko; +pub mod grid; +pub mod image; +pub mod length; +pub mod list; +pub mod motion; +pub mod outline; +pub mod page; +pub mod percentage; +pub mod position; +pub mod ratio; +pub mod rect; +pub mod resolution; +pub mod source_size_list; +pub mod svg; +pub mod svg_path; +pub mod table; +pub mod text; +pub mod time; +pub mod transform; +pub mod ui; +pub mod url; + +/// <angle> | <percentage> +/// https://drafts.csswg.org/css-values/#typedef-angle-percentage +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub enum AngleOrPercentage { + Percentage(Percentage), + Angle(Angle), +} + +impl AngleOrPercentage { + fn parse_internal<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_unitless_zero: AllowUnitlessZeroAngle, + ) -> Result<Self, ParseError<'i>> { + if let Ok(per) = input.try_parse(|i| Percentage::parse(context, i)) { + return Ok(AngleOrPercentage::Percentage(per)); + } + + Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle) + } + + /// Allow unitless angles, used for conic-gradients as specified by the spec. + /// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle + pub fn parse_with_unitless<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes) + } +} + +impl Parse for AngleOrPercentage { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No) + } +} + +/// Parse a `<number>` value, with a given clamping mode. +fn parse_number_with_clamping_mode<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + clamping_mode: AllowedNumericType, +) -> Result<Number, ParseError<'i>> { + let location = input.current_source_location(); + match *input.next()? { + Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => { + Ok(Number { + value: value.min(f32::MAX).max(f32::MIN), + calc_clamping_mode: None, + }) + }, + Token::Function(ref name) => { + let function = CalcNode::math_function(context, name, location)?; + let result = CalcNode::parse_number(context, input, function)?; + Ok(Number { + value: result.min(f32::MAX).max(f32::MIN), + calc_clamping_mode: Some(clamping_mode), + }) + }, + ref t => Err(location.new_unexpected_token_error(t.clone())), + } +} + +/// A CSS `<number>` specified value. +/// +/// https://drafts.csswg.org/css-values-3/#number-value +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)] +pub struct Number { + /// The numeric value itself. + value: CSSFloat, + /// If this number came from a calc() expression, this tells how clamping + /// should be done on the value. + calc_clamping_mode: Option<AllowedNumericType>, +} + +impl Parse for Number { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + parse_number_with_clamping_mode(context, input, AllowedNumericType::All) + } +} + +impl Number { + /// Returns a new number with the value `val`. + fn new_with_clamping_mode( + value: CSSFloat, + calc_clamping_mode: Option<AllowedNumericType>, + ) -> Self { + Self { + value, + calc_clamping_mode, + } + } + + /// Returns this percentage as a number. + pub fn to_percentage(&self) -> Percentage { + Percentage::new_with_clamping_mode(self.value, self.calc_clamping_mode) + } + + /// Returns a new number with the value `val`. + pub fn new(val: CSSFloat) -> Self { + Self::new_with_clamping_mode(val, None) + } + + /// Returns whether this number came from a `calc()` expression. + #[inline] + pub fn was_calc(&self) -> bool { + self.calc_clamping_mode.is_some() + } + + /// Returns the numeric value, clamped if needed. + #[inline] + pub fn get(&self) -> f32 { + self.calc_clamping_mode + .map_or(self.value, |mode| mode.clamp(self.value)) + } + + #[allow(missing_docs)] + pub fn parse_non_negative<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Number, ParseError<'i>> { + parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative) + } + + #[allow(missing_docs)] + pub fn parse_at_least_one<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Number, ParseError<'i>> { + parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne) + } + + /// Clamp to 1.0 if the value is over 1.0. + #[inline] + pub fn clamp_to_one(self) -> Self { + Number { + value: self.value.min(1.), + calc_clamping_mode: self.calc_clamping_mode, + } + } +} + +impl ToComputedValue for Number { + type ComputedValue = CSSFloat; + + #[inline] + fn to_computed_value(&self, _: &Context) -> CSSFloat { + self.get() + } + + #[inline] + fn from_computed_value(computed: &CSSFloat) -> Self { + Number { + value: *computed, + calc_clamping_mode: None, + } + } +} + +impl ToCss for Number { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + if self.calc_clamping_mode.is_some() { + dest.write_str("calc(")?; + } + self.value.to_css(dest)?; + if self.calc_clamping_mode.is_some() { + dest.write_char(')')?; + } + Ok(()) + } +} + +impl IsParallelTo for (Number, Number, Number) { + fn is_parallel_to(&self, vector: &DirectionVector) -> bool { + use euclid::approxeq::ApproxEq; + // If a and b is parallel, the angle between them is 0deg, so + // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. + let self_vector = DirectionVector::new(self.0.get(), self.1.get(), self.2.get()); + self_vector + .cross(*vector) + .square_length() + .approx_eq(&0.0f32) + } +} + +impl SpecifiedValueInfo for Number {} + +impl Add for Number { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self::new(self.get() + other.get()) + } +} + +impl Zero for Number { + #[inline] + fn zero() -> Self { + Self::new(0.) + } + + #[inline] + fn is_zero(&self) -> bool { + self.get() == 0. + } +} + +impl From<Number> for f32 { + #[inline] + fn from(n: Number) -> Self { + n.get() + } +} + +impl From<Number> for f64 { + #[inline] + fn from(n: Number) -> Self { + n.get() as f64 + } +} + +/// A Number which is >= 0.0. +pub type NonNegativeNumber = NonNegative<Number>; + +impl Parse for NonNegativeNumber { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative) + .map(NonNegative::<Number>) + } +} + +impl One for NonNegativeNumber { + #[inline] + fn one() -> Self { + NonNegativeNumber::new(1.0) + } + + #[inline] + fn is_one(&self) -> bool { + self.get() == 1.0 + } +} + +impl NonNegativeNumber { + /// Returns a new non-negative number with the value `val`. + pub fn new(val: CSSFloat) -> Self { + NonNegative::<Number>(Number::new(val.max(0.))) + } + + /// Returns the numeric value. + #[inline] + pub fn get(&self) -> f32 { + self.0.get() + } +} + +/// An Integer which is >= 0. +pub type NonNegativeInteger = NonNegative<Integer>; + +impl Parse for NonNegativeInteger { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Ok(NonNegative(Integer::parse_non_negative(context, input)?)) + } +} + +/// A Number which is >= 1.0. +pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>; + +impl Parse for GreaterThanOrEqualToOneNumber { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne) + .map(GreaterThanOrEqualToOne::<Number>) + } +} + +/// <number> | <percentage> +/// +/// Accepts only non-negative numbers. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub enum NumberOrPercentage { + Percentage(Percentage), + Number(Number), +} + +impl NumberOrPercentage { + fn parse_with_clamping_mode<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + type_: AllowedNumericType, + ) -> Result<Self, ParseError<'i>> { + if let Ok(per) = + input.try_parse(|i| Percentage::parse_with_clamping_mode(context, i, type_)) + { + return Ok(NumberOrPercentage::Percentage(per)); + } + + parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number) + } + + /// Parse a non-negative number or percentage. + pub fn parse_non_negative<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative) + } + + /// Convert the number or the percentage to a number. + pub fn to_percentage(self) -> Percentage { + match self { + Self::Percentage(p) => p, + Self::Number(n) => n.to_percentage(), + } + } + + /// Convert the number or the percentage to a number. + pub fn to_number(self) -> Number { + match self { + Self::Percentage(p) => p.to_number(), + Self::Number(n) => n, + } + } +} + +impl Parse for NumberOrPercentage { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Self::parse_with_clamping_mode(context, input, AllowedNumericType::All) + } +} + +/// A non-negative <number> | <percentage>. +pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>; + +impl NonNegativeNumberOrPercentage { + /// Returns the `100%` value. + #[inline] + pub fn hundred_percent() -> Self { + NonNegative(NumberOrPercentage::Percentage(Percentage::hundred())) + } +} + +impl Parse for NonNegativeNumberOrPercentage { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Ok(NonNegative(NumberOrPercentage::parse_non_negative( + context, input, + )?)) + } +} + +/// The value of Opacity is <alpha-value>, which is "<number> | <percentage>". +/// However, we serialize the specified value as number, so it's ok to store +/// the Opacity as Number. +#[derive( + Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, SpecifiedValueInfo, ToCss, ToShmem, +)] +pub struct Opacity(Number); + +impl Parse for Opacity { + /// Opacity accepts <number> | <percentage>, so we parse it as NumberOrPercentage, + /// and then convert into an Number if it's a Percentage. + /// https://drafts.csswg.org/cssom/#serializing-css-values + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let number = NumberOrPercentage::parse(context, input)?.to_number(); + Ok(Opacity(number)) + } +} + +impl ToComputedValue for Opacity { + type ComputedValue = CSSFloat; + + #[inline] + fn to_computed_value(&self, context: &Context) -> CSSFloat { + let value = self.0.to_computed_value(context); + if context.for_smil_animation { + // SMIL expects to be able to interpolate between out-of-range + // opacity values. + value + } else { + value.min(1.0).max(0.0) + } + } + + #[inline] + fn from_computed_value(computed: &CSSFloat) -> Self { + Opacity(Number::from_computed_value(computed)) + } +} + +/// A specified `<integer>`, optionally coming from a `calc()` expression. +/// +/// <https://drafts.csswg.org/css-values/#integers> +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, ToShmem)] +pub struct Integer { + value: CSSInteger, + was_calc: bool, +} + +impl Zero for Integer { + #[inline] + fn zero() -> Self { + Self::new(0) + } + + #[inline] + fn is_zero(&self) -> bool { + self.value() == 0 + } +} + +impl One for Integer { + #[inline] + fn one() -> Self { + Self::new(1) + } + + #[inline] + fn is_one(&self) -> bool { + self.value() == 1 + } +} + +impl PartialEq<i32> for Integer { + fn eq(&self, value: &i32) -> bool { + self.value() == *value + } +} + +impl Integer { + /// Trivially constructs a new `Integer` value. + pub fn new(val: CSSInteger) -> Self { + Integer { + value: val, + was_calc: false, + } + } + + /// Returns the integer value associated with this value. + pub fn value(&self) -> CSSInteger { + self.value + } + + /// Trivially constructs a new integer value from a `calc()` expression. + fn from_calc(val: CSSInteger) -> Self { + Integer { + value: val, + was_calc: true, + } + } +} + +impl Parse for Integer { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let location = input.current_source_location(); + match *input.next()? { + Token::Number { + int_value: Some(v), .. + } => Ok(Integer::new(v)), + Token::Function(ref name) => { + let function = CalcNode::math_function(context, name, location)?; + let result = CalcNode::parse_integer(context, input, function)?; + Ok(Integer::from_calc(result)) + }, + ref t => Err(location.new_unexpected_token_error(t.clone())), + } + } +} + +impl Integer { + /// Parse an integer value which is at least `min`. + pub fn parse_with_minimum<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + min: i32, + ) -> Result<Integer, ParseError<'i>> { + let value = Integer::parse(context, input)?; + // FIXME(emilio): The spec asks us to avoid rejecting it at parse + // time except until computed value time. + // + // It's not totally clear it's worth it though, and no other browser + // does this. + if value.value() < min { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + Ok(value) + } + + /// Parse a non-negative integer. + pub fn parse_non_negative<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Integer, ParseError<'i>> { + Integer::parse_with_minimum(context, input, 0) + } + + /// Parse a positive integer (>= 1). + pub fn parse_positive<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Integer, ParseError<'i>> { + Integer::parse_with_minimum(context, input, 1) + } +} + +impl ToComputedValue for Integer { + type ComputedValue = i32; + + #[inline] + fn to_computed_value(&self, _: &Context) -> i32 { + self.value + } + + #[inline] + fn from_computed_value(computed: &i32) -> Self { + Integer::new(*computed) + } +} + +impl ToCss for Integer { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + if self.was_calc { + dest.write_str("calc(")?; + } + self.value.to_css(dest)?; + if self.was_calc { + dest.write_char(')')?; + } + Ok(()) + } +} + +impl SpecifiedValueInfo for Integer {} + +/// A wrapper of Integer, with value >= 1. +pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>; + +impl Parse for PositiveInteger { + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne) + } +} + +/// The specified value of a grid `<track-breadth>` +pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>; + +/// The specified value of a grid `<track-size>` +pub type TrackSize = GenericTrackSize<LengthPercentage>; + +/// The specified value of a grid `<track-size>+` +pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>; + +/// The specified value of a grid `<track-list>` +/// (could also be `<auto-track-list>` or `<explicit-track-list>`) +pub type TrackList = GenericTrackList<LengthPercentage, Integer>; + +/// The specified value of a `<grid-line>`. +pub type GridLine = GenericGridLine<Integer>; + +/// `<grid-template-rows> | <grid-template-columns>` +pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>; + +/// rect(...) +pub type ClipRect = generics::GenericClipRect<LengthOrAuto>; + +impl Parse for ClipRect { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Self::parse_quirky(context, input, AllowQuirks::No) + } +} + +impl ClipRect { + /// Parses a rect(<top>, <left>, <bottom>, <right>), allowing quirks. + fn parse_quirky<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_quirks: AllowQuirks, + ) -> Result<Self, ParseError<'i>> { + input.expect_function_matching("rect")?; + + fn parse_argument<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_quirks: AllowQuirks, + ) -> Result<LengthOrAuto, ParseError<'i>> { + LengthOrAuto::parse_quirky(context, input, allow_quirks) + } + + input.parse_nested_block(|input| { + let top = parse_argument(context, input, allow_quirks)?; + let right; + let bottom; + let left; + + if input.try_parse(|input| input.expect_comma()).is_ok() { + right = parse_argument(context, input, allow_quirks)?; + input.expect_comma()?; + bottom = parse_argument(context, input, allow_quirks)?; + input.expect_comma()?; + left = parse_argument(context, input, allow_quirks)?; + } else { + right = parse_argument(context, input, allow_quirks)?; + bottom = parse_argument(context, input, allow_quirks)?; + left = parse_argument(context, input, allow_quirks)?; + } + + Ok(ClipRect { + top, + right, + bottom, + left, + }) + }) + } +} + +/// rect(...) | auto +pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>; + +impl ClipRectOrAuto { + /// Parses a ClipRect or Auto, allowing quirks. + pub fn parse_quirky<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_quirks: AllowQuirks, + ) -> Result<Self, ParseError<'i>> { + if let Ok(v) = input.try_parse(|i| ClipRect::parse_quirky(context, i, allow_quirks)) { + return Ok(generics::GenericClipRectOrAuto::Rect(v)); + } + input.expect_ident_matching("auto")?; + Ok(generics::GenericClipRectOrAuto::Auto) + } +} + +/// Whether quirks are allowed in this context. +#[derive(Clone, Copy, PartialEq)] +pub enum AllowQuirks { + /// Quirks are not allowed. + No, + /// Quirks are allowed, in quirks mode. + Yes, + /// Quirks are always allowed, used for SVG lengths. + Always, +} + +impl AllowQuirks { + /// Returns `true` if quirks are allowed in this context. + pub fn allowed(self, quirks_mode: QuirksMode) -> bool { + match self { + AllowQuirks::Always => true, + AllowQuirks::No => false, + AllowQuirks::Yes => quirks_mode == QuirksMode::Quirks, + } + } +} + +/// An attr(...) rule +/// +/// `[namespace? `|`]? ident` +#[derive( + Clone, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[css(function)] +#[repr(C)] +pub struct Attr { + /// Optional namespace prefix. + pub namespace_prefix: Prefix, + /// Optional namespace URL. + pub namespace_url: Namespace, + /// Attribute name + pub attribute: Atom, +} + +impl Parse for Attr { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Attr, ParseError<'i>> { + input.expect_function_matching("attr")?; + input.parse_nested_block(|i| Attr::parse_function(context, i)) + } +} + +/// Get the Namespace for a given prefix from the namespace map. +fn get_namespace_for_prefix(prefix: &Prefix, context: &ParserContext) -> Option<Namespace> { + context.namespaces.prefixes.get(prefix).cloned() +} + +impl Attr { + /// Parse contents of attr() assuming we have already parsed `attr` and are + /// within a parse_nested_block() + pub fn parse_function<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Attr, ParseError<'i>> { + // Syntax is `[namespace? `|`]? ident` + // no spaces allowed + let first = input.try_parse(|i| i.expect_ident_cloned()).ok(); + if let Ok(token) = input.try_parse(|i| i.next_including_whitespace().map(|t| t.clone())) { + match token { + Token::Delim('|') => { + let location = input.current_source_location(); + // must be followed by an ident + let second_token = match *input.next_including_whitespace()? { + Token::Ident(ref second) => second, + ref t => return Err(location.new_unexpected_token_error(t.clone())), + }; + + let (namespace_prefix, namespace_url) = if let Some(ns) = first { + let prefix = Prefix::from(ns.as_ref()); + let ns = match get_namespace_for_prefix(&prefix, context) { + Some(ns) => ns, + None => { + return Err(location + .new_custom_error(StyleParseErrorKind::UnspecifiedError)); + }, + }; + (prefix, ns) + } else { + (Prefix::default(), Namespace::default()) + }; + return Ok(Attr { + namespace_prefix, + namespace_url, + attribute: Atom::from(second_token.as_ref()), + }); + }, + // In the case of attr(foobar ) we don't want to error out + // because of the trailing whitespace. + Token::WhiteSpace(..) => {}, + ref t => return Err(input.new_unexpected_token_error(t.clone())), + } + } + + if let Some(first) = first { + Ok(Attr { + namespace_prefix: Prefix::default(), + namespace_url: Namespace::default(), + attribute: Atom::from(first.as_ref()), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } +} + +impl ToCss for Attr { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str("attr(")?; + if !self.namespace_prefix.is_empty() { + serialize_atom_identifier(&self.namespace_prefix, dest)?; + dest.write_char('|')?; + } + serialize_atom_identifier(&self.attribute, dest)?; + dest.write_char(')') + } +} |