Skip to main content

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