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