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