style/values/specified/
mod.rs

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
9use super::computed::transform::DirectionVector;
10use super::computed::{Context, ToComputedValue};
11use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks;
12use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
13use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize};
14use super::generics::transform::IsParallelTo;
15use super::generics::{self, GreaterThanOrEqualToOne, NonNegative};
16use super::{CSSFloat, CSSInteger};
17use crate::context::QuirksMode;
18use crate::derives::*;
19use crate::parser::{Parse, ParserContext};
20use crate::values::specified::calc::CalcNode;
21use crate::values::{serialize_atom_identifier, serialize_number, AtomString};
22use crate::{Atom, Namespace, One, Prefix, Zero};
23use cssparser::{Parser, Token};
24use std::fmt::{self, Write};
25use std::ops::Add;
26use style_traits::values::specified::AllowedNumericType;
27use style_traits::{
28    CssString, CssWriter, NumericValue, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
29    ToTyped, TypedValue,
30};
31
32pub use self::align::{ContentDistribution, ItemPlacement, JustifyItems, SelfAlignment};
33pub use self::angle::{AllowUnitlessZeroAngle, Angle};
34pub use self::animation::{
35    AnimationComposition, AnimationDirection, AnimationDuration, AnimationFillMode,
36    AnimationIterationCount, AnimationName, AnimationPlayState, AnimationTimeline, ScrollAxis,
37    TimelineName, TransitionBehavior, TransitionProperty, ViewTimelineInset, ViewTransitionClass,
38    ViewTransitionName,
39};
40pub use self::background::{BackgroundRepeat, BackgroundSize};
41pub use self::basic_shape::FillRule;
42pub use self::border::{
43    BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice,
44    BorderImageWidth, BorderRadius, BorderSideOffset, BorderSideWidth, BorderSpacing, BorderStyle,
45    LineWidth,
46};
47pub 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};
55pub use self::color::{
56    Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust,
57};
58pub use self::column::ColumnCount;
59pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet};
60pub use self::easing::TimingFunction;
61pub use self::effects::{BoxShadow, Filter, SimpleShadow};
62pub use self::flex::FlexBasis;
63pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle};
64pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric};
65pub use self::font::{
66    FontSize, FontSizeAdjust, FontSizeAdjustFactor, FontSizeKeyword, FontStretch, FontSynthesis,
67    FontSynthesisStyle,
68};
69pub use self::font::{FontVariantAlternates, FontWeight};
70pub use self::font::{FontVariantEastAsian, FontVariationSettings, LineHeight};
71pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale};
72pub use self::image::{EndingShape as GradientEndingShape, Gradient, Image, ImageRendering};
73pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth};
74pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
75pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
76pub use self::length::{Margin, MaxSize, Size};
77pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant};
78pub use self::length::{
79    NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto,
80};
81#[cfg(feature = "gecko")]
82pub use self::list::ListStyleType;
83pub use self::list::Quotes;
84pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate};
85pub use self::outline::OutlineStyle;
86pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize};
87pub use self::percentage::{NonNegativePercentage, Percentage};
88pub use self::position::AnchorFunction;
89pub use self::position::AnchorName;
90pub use self::position::AnchorScope;
91pub use self::position::AspectRatio;
92pub use self::position::Inset;
93pub use self::position::PositionAnchor;
94pub use self::position::PositionTryFallbacks;
95pub use self::position::PositionTryOrder;
96pub use self::position::PositionVisibility;
97pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto};
98pub use self::position::{MasonryAutoFlow, MasonryItemOrder, MasonryPlacement};
99pub use self::position::{PositionArea, PositionAreaKeyword};
100pub use self::position::{PositionComponent, ZIndex};
101pub use self::ratio::Ratio;
102pub use self::rect::NonNegativeLengthOrNumberRect;
103pub use self::resolution::Resolution;
104pub use self::svg::{DProperty, MozContextProperties};
105pub use self::svg::{SVGLength, SVGOpacity, SVGPaint};
106pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth, VectorEffect};
107pub use self::svg_path::SVGPathData;
108pub use self::text::RubyPosition;
109pub use self::text::{HyphenateCharacter, HyphenateLimitChars};
110pub use self::text::{InitialLetter, LetterSpacing, LineBreak, TextAlign, TextIndent};
111pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak};
112pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
113pub use self::text::{TextAlignLast, TextAutospace, TextUnderlinePosition};
114pub use self::text::{
115    TextDecorationInset, TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform,
116};
117pub use self::time::Time;
118pub use self::transform::{Rotate, Scale, Transform};
119pub use self::transform::{TransformBox, TransformOrigin, TransformStyle, Translate};
120#[cfg(feature = "gecko")]
121pub use self::ui::CursorImage;
122pub use self::ui::{
123    BoolInteger, Cursor, Inert, MozTheme, PointerEvents, ScrollbarColor, UserFocus, UserSelect,
124};
125pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
126
127pub mod align;
128pub mod angle;
129pub mod animation;
130pub mod background;
131pub mod basic_shape;
132pub mod border;
133#[path = "box.rs"]
134pub mod box_;
135pub mod calc;
136pub mod color;
137pub mod column;
138pub mod counters;
139pub mod easing;
140pub mod effects;
141pub mod flex;
142pub mod font;
143pub mod grid;
144pub mod image;
145pub mod intersection_observer;
146pub mod length;
147pub mod list;
148pub mod motion;
149pub mod outline;
150pub mod page;
151pub mod percentage;
152pub mod position;
153pub mod ratio;
154pub mod rect;
155pub mod resolution;
156pub mod source_size_list;
157pub mod svg;
158pub mod svg_path;
159pub mod table;
160pub mod text;
161pub mod time;
162pub mod transform;
163pub mod ui;
164pub 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)]
170pub enum AngleOrPercentage {
171    Percentage(Percentage),
172    Angle(Angle),
173}
174
175impl 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
198impl 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.
208fn 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)]
237pub 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
245impl 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
254impl 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
264impl 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
331impl 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
348impl 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
357impl 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
365impl 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
378impl SpecifiedValueInfo for Number {}
379
380impl 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
388impl 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
400impl From<Number> for f32 {
401    #[inline]
402    fn from(n: Number) -> Self {
403        n.get()
404    }
405}
406
407impl 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.
415pub type NonNegativeNumber = NonNegative<Number>;
416
417impl 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
427impl 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
439impl 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.
453pub type NonNegativeInteger = NonNegative<Integer>;
454
455impl 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.
465pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>;
466
467impl 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)]
482pub enum NumberOrPercentage {
483    Percentage(Percentage),
484    Number(Number),
485}
486
487impl 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
527impl 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>.
537pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>;
538
539impl 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
553impl 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)]
579pub struct Opacity(Number);
580
581impl 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
594impl 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)]
621pub 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
628impl 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
640impl 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
652impl PartialEq<i32> for Integer {
653    fn eq(&self, value: &i32) -> bool {
654        self.value() == *value
655    }
656}
657
658impl 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
678impl 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
698impl 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
734impl 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
748impl 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
764impl SpecifiedValueInfo for Integer {}
765
766/// A wrapper of Integer, with value >= 1.
767pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>;
768
769impl 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>`
780pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
781
782/// The specified value of a grid `<track-size>`
783pub type TrackSize = GenericTrackSize<LengthPercentage>;
784
785/// The specified value of a grid `<track-size>+`
786pub 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>`)
790pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
791
792/// The specified value of a `<grid-line>`.
793pub type GridLine = GenericGridLine<Integer>;
794
795/// `<grid-template-rows> | <grid-template-columns>`
796pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
797
798/// rect(...)
799pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
800
801impl 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
810impl 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
856pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
857
858impl 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)]
875pub 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
884impl 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)]
911pub 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
922impl 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.
933fn 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
938fn 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
963impl 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
1006impl 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}