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