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,
557    Copy,
558    Debug,
559    MallocSizeOf,
560    PartialEq,
561    PartialOrd,
562    SpecifiedValueInfo,
563    ToCss,
564    ToShmem,
565    ToTyped,
566)]
567pub struct Opacity(Number);
568
569impl Parse for Opacity {
570    /// Opacity accepts <number> | <percentage>, so we parse it as NumberOrPercentage,
571    /// and then convert into an Number if it's a Percentage.
572    /// https://drafts.csswg.org/cssom/#serializing-css-values
573    fn parse<'i, 't>(
574        context: &ParserContext,
575        input: &mut Parser<'i, 't>,
576    ) -> Result<Self, ParseError<'i>> {
577        let number = NumberOrPercentage::parse(context, input)?.to_number();
578        Ok(Opacity(number))
579    }
580}
581
582impl ToComputedValue for Opacity {
583    type ComputedValue = CSSFloat;
584
585    #[inline]
586    fn to_computed_value(&self, context: &Context) -> CSSFloat {
587        let value = self.0.to_computed_value(context);
588        if context.for_smil_animation {
589            // SMIL expects to be able to interpolate between out-of-range
590            // opacity values.
591            value
592        } else {
593            value.min(1.0).max(0.0)
594        }
595    }
596
597    #[inline]
598    fn from_computed_value(computed: &CSSFloat) -> Self {
599        Opacity(Number::from_computed_value(computed))
600    }
601}
602
603/// A specified `<integer>`, either a simple integer value or a calc expression.
604/// Note that a calc expression may not actually be an integer; it will be rounded
605/// at computed-value time.
606///
607/// <https://drafts.csswg.org/css-values/#integers>
608#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem, ToTyped)]
609pub enum Integer {
610    /// A literal integer value.
611    Literal(CSSInteger),
612    /// A calc expression, whose value will be rounded later if necessary.
613    Calc(CSSFloat),
614}
615
616impl Zero for Integer {
617    #[inline]
618    fn zero() -> Self {
619        Self::new(0)
620    }
621
622    #[inline]
623    fn is_zero(&self) -> bool {
624        *self == 0
625    }
626}
627
628impl One for Integer {
629    #[inline]
630    fn one() -> Self {
631        Self::new(1)
632    }
633
634    #[inline]
635    fn is_one(&self) -> bool {
636        *self == 1
637    }
638}
639
640impl PartialEq<i32> for Integer {
641    fn eq(&self, value: &i32) -> bool {
642        self.value() == *value
643    }
644}
645
646impl Integer {
647    /// Trivially constructs a new `Integer` value.
648    pub fn new(val: CSSInteger) -> Self {
649        Self::Literal(val)
650    }
651
652    /// Returns the (rounded) integer value associated with this value.
653    pub fn value(&self) -> CSSInteger {
654        match *self {
655            Self::Literal(i) => i,
656            Self::Calc(n) => (n + 0.5).floor() as CSSInteger,
657        }
658    }
659
660    /// Trivially constructs a new integer value from a `calc()` expression.
661    fn from_calc(val: CSSFloat) -> Self {
662        Self::Calc(val)
663    }
664}
665
666impl Parse for Integer {
667    fn parse<'i, 't>(
668        context: &ParserContext,
669        input: &mut Parser<'i, 't>,
670    ) -> Result<Self, ParseError<'i>> {
671        let location = input.current_source_location();
672        match *input.next()? {
673            Token::Number {
674                int_value: Some(v), ..
675            } => Ok(Integer::new(v)),
676            Token::Function(ref name) => {
677                let function = CalcNode::math_function(context, name, location)?;
678                let result = CalcNode::parse_number(context, input, function)?;
679                Ok(Integer::from_calc(result))
680            },
681            ref t => Err(location.new_unexpected_token_error(t.clone())),
682        }
683    }
684}
685
686impl Integer {
687    /// Parse an integer value which is at least `min`.
688    pub fn parse_with_minimum<'i, 't>(
689        context: &ParserContext,
690        input: &mut Parser<'i, 't>,
691        min: i32,
692    ) -> Result<Integer, ParseError<'i>> {
693        let value = Integer::parse(context, input)?;
694        // FIXME(emilio): The spec asks us to avoid rejecting it at parse
695        // time except until computed value time.
696        //
697        // It's not totally clear it's worth it though, and no other browser
698        // does this.
699        if value.value() < min {
700            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
701        }
702        Ok(value)
703    }
704
705    /// Parse a non-negative integer.
706    pub fn parse_non_negative<'i, 't>(
707        context: &ParserContext,
708        input: &mut Parser<'i, 't>,
709    ) -> Result<Integer, ParseError<'i>> {
710        Integer::parse_with_minimum(context, input, 0)
711    }
712
713    /// Parse a positive integer (>= 1).
714    pub fn parse_positive<'i, 't>(
715        context: &ParserContext,
716        input: &mut Parser<'i, 't>,
717    ) -> Result<Integer, ParseError<'i>> {
718        Integer::parse_with_minimum(context, input, 1)
719    }
720}
721
722impl ToComputedValue for Integer {
723    type ComputedValue = i32;
724
725    #[inline]
726    fn to_computed_value(&self, _: &Context) -> i32 {
727        self.value()
728    }
729
730    #[inline]
731    fn from_computed_value(computed: &i32) -> Self {
732        Integer::new(*computed)
733    }
734}
735
736impl ToCss for Integer {
737    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
738    where
739        W: Write,
740    {
741        match *self {
742            Integer::Literal(i) => i.to_css(dest),
743            Integer::Calc(n) => {
744                dest.write_str("calc(")?;
745                n.to_css(dest)?;
746                dest.write_char(')')
747            },
748        }
749    }
750}
751
752impl SpecifiedValueInfo for Integer {}
753
754/// A wrapper of Integer, with value >= 1.
755pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>;
756
757impl Parse for PositiveInteger {
758    #[inline]
759    fn parse<'i, 't>(
760        context: &ParserContext,
761        input: &mut Parser<'i, 't>,
762    ) -> Result<Self, ParseError<'i>> {
763        Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne)
764    }
765}
766
767/// The specified value of a grid `<track-breadth>`
768pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
769
770/// The specified value of a grid `<track-size>`
771pub type TrackSize = GenericTrackSize<LengthPercentage>;
772
773/// The specified value of a grid `<track-size>+`
774pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>;
775
776/// The specified value of a grid `<track-list>`
777/// (could also be `<auto-track-list>` or `<explicit-track-list>`)
778pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
779
780/// The specified value of a `<grid-line>`.
781pub type GridLine = GenericGridLine<Integer>;
782
783/// `<grid-template-rows> | <grid-template-columns>`
784pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
785
786/// rect(...)
787pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
788
789impl Parse for ClipRect {
790    fn parse<'i, 't>(
791        context: &ParserContext,
792        input: &mut Parser<'i, 't>,
793    ) -> Result<Self, ParseError<'i>> {
794        Self::parse_quirky(context, input, AllowQuirks::No)
795    }
796}
797
798impl ClipRect {
799    /// Parses a rect(<top>, <left>, <bottom>, <right>), allowing quirks.
800    fn parse_quirky<'i, 't>(
801        context: &ParserContext,
802        input: &mut Parser<'i, 't>,
803        allow_quirks: AllowQuirks,
804    ) -> Result<Self, ParseError<'i>> {
805        input.expect_function_matching("rect")?;
806
807        fn parse_argument<'i, 't>(
808            context: &ParserContext,
809            input: &mut Parser<'i, 't>,
810            allow_quirks: AllowQuirks,
811        ) -> Result<LengthOrAuto, ParseError<'i>> {
812            LengthOrAuto::parse_quirky(context, input, allow_quirks)
813        }
814
815        input.parse_nested_block(|input| {
816            let top = parse_argument(context, input, allow_quirks)?;
817            let right;
818            let bottom;
819            let left;
820
821            if input.try_parse(|input| input.expect_comma()).is_ok() {
822                right = parse_argument(context, input, allow_quirks)?;
823                input.expect_comma()?;
824                bottom = parse_argument(context, input, allow_quirks)?;
825                input.expect_comma()?;
826                left = parse_argument(context, input, allow_quirks)?;
827            } else {
828                right = parse_argument(context, input, allow_quirks)?;
829                bottom = parse_argument(context, input, allow_quirks)?;
830                left = parse_argument(context, input, allow_quirks)?;
831            }
832
833            Ok(ClipRect {
834                top,
835                right,
836                bottom,
837                left,
838            })
839        })
840    }
841}
842
843/// rect(...) | auto
844pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
845
846impl ClipRectOrAuto {
847    /// Parses a ClipRect or Auto, allowing quirks.
848    pub fn parse_quirky<'i, 't>(
849        context: &ParserContext,
850        input: &mut Parser<'i, 't>,
851        allow_quirks: AllowQuirks,
852    ) -> Result<Self, ParseError<'i>> {
853        if let Ok(v) = input.try_parse(|i| ClipRect::parse_quirky(context, i, allow_quirks)) {
854            return Ok(generics::GenericClipRectOrAuto::Rect(v));
855        }
856        input.expect_ident_matching("auto")?;
857        Ok(generics::GenericClipRectOrAuto::Auto)
858    }
859}
860
861/// Whether quirks are allowed in this context.
862#[derive(Clone, Copy, PartialEq)]
863pub enum AllowQuirks {
864    /// Quirks are not allowed.
865    No,
866    /// Quirks are allowed, in quirks mode.
867    Yes,
868    /// Quirks are always allowed, used for SVG lengths.
869    Always,
870}
871
872impl AllowQuirks {
873    /// Returns `true` if quirks are allowed in this context.
874    pub fn allowed(self, quirks_mode: QuirksMode) -> bool {
875        match self {
876            AllowQuirks::Always => true,
877            AllowQuirks::No => false,
878            AllowQuirks::Yes => quirks_mode == QuirksMode::Quirks,
879        }
880    }
881}
882
883/// An attr(...) rule
884///
885/// `[namespace? `|`]? ident`
886#[derive(
887    Clone,
888    Debug,
889    Eq,
890    MallocSizeOf,
891    PartialEq,
892    SpecifiedValueInfo,
893    ToComputedValue,
894    ToResolvedValue,
895    ToShmem,
896)]
897#[css(function)]
898#[repr(C)]
899pub struct Attr {
900    /// Optional namespace prefix.
901    pub namespace_prefix: Prefix,
902    /// Optional namespace URL.
903    pub namespace_url: Namespace,
904    /// Attribute name
905    pub attribute: Atom,
906    /// Fallback value
907    pub fallback: AtomString,
908}
909
910impl Parse for Attr {
911    fn parse<'i, 't>(
912        context: &ParserContext,
913        input: &mut Parser<'i, 't>,
914    ) -> Result<Attr, ParseError<'i>> {
915        input.expect_function_matching("attr")?;
916        input.parse_nested_block(|i| Attr::parse_function(context, i))
917    }
918}
919
920/// Get the Namespace for a given prefix from the namespace map.
921fn get_namespace_for_prefix(prefix: &Prefix, context: &ParserContext) -> Option<Namespace> {
922    context.namespaces.prefixes.get(prefix).cloned()
923}
924
925/// Try to parse a namespace and return it if parsed, or none if there was not one present
926fn parse_namespace<'i, 't>(
927    context: &ParserContext,
928    input: &mut Parser<'i, 't>,
929) -> Result<(Prefix, Namespace), ParseError<'i>> {
930    let ns_prefix = match input.next()? {
931        Token::Ident(ref prefix) => Some(Prefix::from(prefix.as_ref())),
932        Token::Delim('|') => None,
933        _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
934    };
935
936    if ns_prefix.is_some() && !matches!(*input.next_including_whitespace()?, Token::Delim('|')) {
937        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
938    }
939
940    if let Some(prefix) = ns_prefix {
941        let ns = match get_namespace_for_prefix(&prefix, context) {
942            Some(ns) => ns,
943            None => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
944        };
945        Ok((prefix, ns))
946    } else {
947        Ok((Prefix::default(), Namespace::default()))
948    }
949}
950
951impl Attr {
952    /// Parse contents of attr() assuming we have already parsed `attr` and are
953    /// within a parse_nested_block()
954    pub fn parse_function<'i, 't>(
955        context: &ParserContext,
956        input: &mut Parser<'i, 't>,
957    ) -> Result<Attr, ParseError<'i>> {
958        // Syntax is `[namespace? '|']? ident [',' fallback]?`
959        let namespace = input
960            .try_parse(|input| parse_namespace(context, input))
961            .ok();
962        let namespace_is_some = namespace.is_some();
963        let (namespace_prefix, namespace_url) = namespace.unwrap_or_default();
964
965        // If there is a namespace, ensure no whitespace following '|'
966        let attribute = Atom::from(if namespace_is_some {
967            let location = input.current_source_location();
968            match *input.next_including_whitespace()? {
969                Token::Ident(ref ident) => ident.as_ref(),
970                ref t => return Err(location.new_unexpected_token_error(t.clone())),
971            }
972        } else {
973            input.expect_ident()?.as_ref()
974        });
975
976        // Fallback will always be a string value for now as we do not support
977        // attr() types yet.
978        let fallback = input
979            .try_parse(|input| -> Result<AtomString, ParseError<'i>> {
980                input.expect_comma()?;
981                Ok(input.expect_string()?.as_ref().into())
982            })
983            .unwrap_or_default();
984
985        Ok(Attr {
986            namespace_prefix,
987            namespace_url,
988            attribute,
989            fallback,
990        })
991    }
992}
993
994impl ToCss for Attr {
995    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
996    where
997        W: Write,
998    {
999        dest.write_str("attr(")?;
1000        if !self.namespace_prefix.is_empty() {
1001            serialize_atom_identifier(&self.namespace_prefix, dest)?;
1002            dest.write_char('|')?;
1003        }
1004        serialize_atom_identifier(&self.attribute, dest)?;
1005
1006        if !self.fallback.is_empty() {
1007            dest.write_str(", ")?;
1008            self.fallback.to_css(dest)?;
1009        }
1010
1011        dest.write_char(')')
1012    }
1013}