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::{Context, ToComputedValue};
10use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks;
11use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
12use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize};
13use super::generics::{self, NonNegative};
14use super::CSSFloat;
15use crate::context::QuirksMode;
16use crate::derives::*;
17use crate::parser::{Parse, ParserContext};
18use crate::typed_om::NumericBaseType;
19use crate::values::computed;
20use crate::values::specified::calc::PercentageContext;
21use crate::values::specified::number::parse_number_with_clamping_mode;
22use crate::{Namespace, Prefix};
23use cssparser::{Parser, Token};
24use rustc_hash::FxHashMap;
25use style_traits::values::specified::AllowedNumericType;
26use style_traits::{ParseError, StyleParseErrorKind};
27
28pub use self::align::{ContentDistribution, ItemPlacement, JustifyItems, SelfAlignment};
29pub use self::angle::{AllowUnitlessZeroAngle, Angle, NoCalcAngle};
30pub use self::animation::{
31    AnimationComposition, AnimationDirection, AnimationDuration, AnimationFillMode,
32    AnimationIterationCount, AnimationName, AnimationPlayState, AnimationRangeEnd,
33    AnimationRangeStart, AnimationTimeline, ScrollAxis, TimelineName, TransitionBehavior,
34    TransitionProperty, ViewTimelineInset, ViewTransitionClass, ViewTransitionName,
35};
36pub use self::background::{BackgroundClip, BackgroundRepeat, BackgroundSize};
37pub use self::basic_shape::FillRule;
38pub use self::border::{
39    BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice,
40    BorderImageWidth, BorderRadius, BorderSideOffset, BorderSideWidth, BorderSpacing, BorderStyle,
41    LineWidth,
42};
43pub use self::box_::{
44    AlignmentBaseline, Appearance, BaselineShift, BaselineSource, BreakBetween, BreakWithin, Clear,
45    Contain, ContainIntrinsicSize, ContainerName, ContainerType, ContentVisibility, Display,
46    DominantBaseline, Float, LineClamp, MarginTrim, Overflow, OverflowAnchor, OverflowClipMargin,
47    OverscrollBehavior, Perspective, PositionProperty, Resize, ScrollSnapAlign, ScrollSnapAxis,
48    ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, ScrollbarInset,
49    TouchAction, WillChange, WillChangeBits, WritingModeProperty, Zoom,
50};
51pub use self::calc::{CalcLengthPercentage, CalcNumeric};
52pub use self::color::{
53    Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust,
54};
55pub use self::column::ColumnCount;
56pub use self::corner_shape::{CornerShape, CornerShapeRect, SuperellipseArg};
57pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet};
58pub use self::easing::TimingFunction;
59pub use self::effects::{BoxShadow, Filter, SimpleShadow};
60pub use self::flex::FlexBasis;
61pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle};
62pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric};
63pub use self::font::{
64    FontSize, FontSizeAdjust, FontSizeAdjustFactor, FontSizeKeyword, FontSynthesis,
65    FontSynthesisStyle, FontWidth,
66};
67pub use self::font::{FontVariantAlternates, FontWeight};
68pub use self::font::{FontVariantEastAsian, FontVariationSettings, LineHeight};
69pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale};
70pub use self::image::{
71    EndingShape as GradientEndingShape, Gradient, Image, ImageDecoding, ImageRendering,
72};
73pub use self::length::{Length, LengthOrNumber, LengthUnit, NonNegativeLengthOrNumber};
74pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
75pub use self::length::{Margin, MaxSize, Size};
76pub use self::length::{NoCalcLength, ViewportVariant};
77pub use self::length::{
78    NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto,
79};
80pub use self::list::ListStyleType;
81pub use self::list::Quotes;
82pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate};
83pub use self::number::{
84    GreaterThanOrEqualToOneNumber, Integer, NoCalcNumber, NonNegativeInteger, NonNegativeNumber,
85    Number, PositiveInteger,
86};
87pub use self::outline::OutlineStyle;
88pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize};
89pub use self::param::LinkParameters;
90pub use self::percentage::{NoCalcPercentage, NonNegativePercentage, Percentage};
91pub use self::position::{
92    AnchorFunction, AnchorName, AnchorNameIdent, AspectRatio, FlexWrap, GridAutoFlow,
93    GridTemplateAreas, Inset, MasonryAutoFlow, MasonryItemOrder, MasonryPlacement, Position,
94    PositionAnchor, PositionAnchorKeyword, PositionArea, PositionAreaKeyword, PositionComponent,
95    PositionOrAuto, PositionTryFallbacks, PositionTryOrder, PositionVisibility, ScopedName, ZIndex,
96};
97pub use self::ratio::Ratio;
98pub use self::rect::NonNegativeLengthOrNumberRect;
99pub use self::resolution::{NoCalcResolution, Resolution};
100pub use self::svg::{DProperty, MozContextProperties};
101pub use self::svg::{SVGLength, SVGOpacity, SVGPaint};
102pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth, VectorEffect};
103pub use self::svg_path::SVGPathData;
104pub use self::text::RubyPosition;
105pub use self::text::{HyphenateCharacter, HyphenateLimitChars};
106pub use self::text::{InitialLetter, LetterSpacing, LineBreak, TextAlign, TextIndent};
107pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak};
108pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
109pub use self::text::{TextAlignLast, TextAutospace, TextUnderlinePosition};
110pub use self::text::{TextBoxEdge, TextBoxTrim};
111pub use self::text::{
112    TextDecorationInset, TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform,
113};
114pub use self::time::{NoCalcTime, Time};
115pub use self::transform::{Rotate, Scale, Transform};
116pub use self::transform::{TransformBox, TransformOrigin, TransformStyle, Translate};
117pub use self::tree_counting::TreeCountingFunction;
118#[cfg(feature = "gecko")]
119pub use self::ui::CursorImage;
120pub use self::ui::{
121    BoolInteger, Cursor, Inert, MozTheme, PointerEvents, ScrollbarColor, UserFocus, UserSelect,
122};
123pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
124
125pub mod align;
126pub mod angle;
127pub mod animation;
128pub mod background;
129pub mod basic_shape;
130pub mod border;
131#[path = "box.rs"]
132pub mod box_;
133pub mod calc;
134pub mod color;
135pub mod column;
136pub mod corner_shape;
137pub mod counters;
138pub mod easing;
139pub mod effects;
140pub mod flex;
141pub mod font;
142pub mod frequency;
143pub mod grid;
144pub mod image;
145pub mod intersection_observer;
146pub mod length;
147pub mod list;
148pub mod motion;
149pub mod number;
150pub mod outline;
151pub mod page;
152pub mod param;
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 tree_counting;
166pub mod ui;
167pub mod url;
168
169/// <angle> | <percentage>
170/// https://drafts.csswg.org/css-values/#typedef-angle-percentage
171#[allow(missing_docs)]
172#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
173pub enum AngleOrPercentage {
174    Percentage(Percentage),
175    Angle(Angle),
176}
177
178impl AngleOrPercentage {
179    fn parse_internal(
180        context: &ParserContext,
181        input: &mut Parser,
182        allow_unitless_zero: AllowUnitlessZeroAngle,
183    ) -> Result<Self, ParseError> {
184        if let Ok(per) = input.try_parse(|i| Percentage::parse(context, i)) {
185            return Ok(AngleOrPercentage::Percentage(per));
186        }
187
188        Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle)
189    }
190
191    /// Allow unitless angles, used for conic-gradients as specified by the spec.
192    /// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle
193    pub fn parse_with_unitless(
194        context: &ParserContext,
195        input: &mut Parser,
196    ) -> Result<Self, ParseError> {
197        AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes)
198    }
199}
200
201impl Parse for AngleOrPercentage {
202    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
203        AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No)
204    }
205}
206
207/// <number> | <percentage>
208///
209/// Accepts only non-negative numbers.
210///
211/// TODO(Bug 2040559) - Convert this into a NumericUnion, instead of an enum over
212/// Number and Percentage. Both types are also NumericUnions of unitless floats.
213#[allow(missing_docs)]
214#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
215pub enum NumberOrPercentage {
216    Percentage(Percentage),
217    Number(Number),
218}
219
220impl NumberOrPercentage {
221    fn parse_with_clamping_mode(
222        context: &ParserContext,
223        input: &mut Parser,
224        type_: AllowedNumericType,
225    ) -> Result<Self, ParseError> {
226        if let Ok(per) =
227            input.try_parse(|i| Percentage::parse_with_clamping_mode(context, i, type_))
228        {
229            return Ok(NumberOrPercentage::Percentage(per));
230        }
231
232        parse_number_with_clamping_mode(
233            context,
234            input,
235            type_,
236            PercentageContext::allowed_with_hint(NumericBaseType::Percent),
237        )
238        .map(NumberOrPercentage::Number)
239    }
240
241    /// Parse a non-negative number or percentage.
242    pub fn parse_non_negative(
243        context: &ParserContext,
244        input: &mut Parser,
245    ) -> Result<Self, ParseError> {
246        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
247    }
248
249    /// Convert the number or the percentage to a number.
250    pub fn to_percentage(self) -> Option<Percentage> {
251        match self {
252            Self::Percentage(p) => Some(p),
253            Self::Number(n) => n.to_percentage(),
254        }
255    }
256
257    /// Convert the number or the percentage to a number.
258    pub fn to_number(&self) -> Option<Number> {
259        match self {
260            Self::Percentage(p) => p.to_number(),
261            Self::Number(n) => Some(n.clone()),
262        }
263    }
264
265    /// Gets a reference to the underlying percentage, or None if this is a number
266    pub fn as_percentage(&self) -> Option<&Percentage> {
267        match self {
268            NumberOrPercentage::Percentage(percentage) => Some(percentage),
269            _ => None,
270        }
271    }
272
273    /// If this is a non-calc percentage, replaces it with the equivalent
274    /// number; otherwise, returns the original value.
275    pub fn into_simplified_number(self) -> NumberOrPercentage {
276        match self.as_percentage().and_then(|p| p.get()) {
277            Some(p) => NumberOrPercentage::Number(Number::new(p)),
278            None => self,
279        }
280    }
281
282    /// Attempts to resolve this number or percentage to a computed value.
283    pub fn to_computed_value_without_context(&self) -> Result<computed::NumberOrPercentage, ()> {
284        Ok(match self {
285            NumberOrPercentage::Percentage(percentage) => computed::NumberOrPercentage::Percentage(
286                computed::Percentage(percentage.resolve().ok_or(())?),
287            ),
288            NumberOrPercentage::Number(number) => {
289                computed::NumberOrPercentage::Number(number.resolve().ok_or(())?)
290            },
291        })
292    }
293}
294
295impl Parse for NumberOrPercentage {
296    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
297        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
298    }
299}
300
301/// A non-negative <number> | <percentage>.
302pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>;
303
304impl NonNegativeNumberOrPercentage {
305    /// Returns the `100%` value.
306    #[inline]
307    pub fn hundred_percent() -> Self {
308        NonNegative(NumberOrPercentage::Percentage(Percentage::hundred()))
309    }
310
311    /// Return a particular number.
312    #[inline]
313    pub fn new_number(n: f32) -> Self {
314        NonNegative(NumberOrPercentage::Number(Number::new(n)))
315    }
316}
317
318impl Parse for NonNegativeNumberOrPercentage {
319    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
320        Ok(NonNegative(NumberOrPercentage::parse_non_negative(
321            context, input,
322        )?))
323    }
324}
325
326/// A specified CSS `opacity`
327#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
328pub struct Opacity(NumberOrPercentage);
329
330impl Parse for Opacity {
331    /// Opacity accepts <number> | <percentage>, so we parse it as NumberOrPercentage,
332    /// and then convert into an Number if it's a non-calc Percentage.
333    /// https://drafts.csswg.org/css-color-4/#serializing-opacity-values
334    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
335        Ok(Opacity(
336            NumberOrPercentage::parse(context, input)?.into_simplified_number(),
337        ))
338    }
339}
340
341impl ToComputedValue for Opacity {
342    type ComputedValue = CSSFloat;
343
344    #[inline]
345    fn to_computed_value(&self, context: &Context) -> CSSFloat {
346        let value = self.0.to_computed_value(context).value();
347        if context.for_animation {
348            // Type <number> and <percentage> should be able to interpolate
349            // out-of-range opacity values which benefits additive animation
350            value
351        } else {
352            value.min(1.0).max(0.0)
353        }
354    }
355
356    #[inline]
357    fn from_computed_value(computed: &CSSFloat) -> Self {
358        Opacity(NumberOrPercentage::Number(Number::from_computed_value(
359            computed,
360        )))
361    }
362}
363
364/// The specified value of a grid `<track-breadth>`
365pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
366
367/// The specified value of a grid `<track-size>`
368pub type TrackSize = GenericTrackSize<LengthPercentage>;
369
370/// The specified value of a grid `<track-size>+`
371pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>;
372
373/// The specified value of a grid `<track-list>`
374/// (could also be `<auto-track-list>` or `<explicit-track-list>`)
375pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
376
377/// The specified value of a `<grid-line>`.
378pub type GridLine = GenericGridLine<Integer>;
379
380/// `<grid-template-rows> | <grid-template-columns>`
381pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
382
383/// rect(...)
384pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
385
386impl Parse for ClipRect {
387    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
388        Self::parse_quirky(context, input, AllowQuirks::No)
389    }
390}
391
392impl ClipRect {
393    /// Parses a rect(<top>, <left>, <bottom>, <right>), allowing quirks.
394    fn parse_quirky(
395        context: &ParserContext,
396        input: &mut Parser,
397        allow_quirks: AllowQuirks,
398    ) -> Result<Self, ParseError> {
399        input.expect_function_matching("rect")?;
400
401        fn parse_argument(
402            context: &ParserContext,
403            input: &mut Parser,
404            allow_quirks: AllowQuirks,
405        ) -> Result<LengthOrAuto, ParseError> {
406            LengthOrAuto::parse_quirky(context, input, allow_quirks)
407        }
408
409        input.parse_nested_block(|input| {
410            let top = parse_argument(context, input, allow_quirks)?;
411            let right;
412            let bottom;
413            let left;
414
415            if input.try_parse(|input| input.expect_comma()).is_ok() {
416                right = parse_argument(context, input, allow_quirks)?;
417                input.expect_comma()?;
418                bottom = parse_argument(context, input, allow_quirks)?;
419                input.expect_comma()?;
420                left = parse_argument(context, input, allow_quirks)?;
421            } else {
422                right = parse_argument(context, input, allow_quirks)?;
423                bottom = parse_argument(context, input, allow_quirks)?;
424                left = parse_argument(context, input, allow_quirks)?;
425            }
426
427            Ok(ClipRect {
428                top,
429                right,
430                bottom,
431                left,
432            })
433        })
434    }
435}
436
437/// rect(...) | auto
438pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
439
440impl ClipRectOrAuto {
441    /// Parses a ClipRect or Auto, allowing quirks.
442    pub fn parse_quirky(
443        context: &ParserContext,
444        input: &mut Parser,
445        allow_quirks: AllowQuirks,
446    ) -> Result<Self, ParseError> {
447        if let Ok(v) = input.try_parse(|i| ClipRect::parse_quirky(context, i, allow_quirks)) {
448            return Ok(generics::GenericClipRectOrAuto::Rect(v));
449        }
450        input.expect_ident_matching("auto")?;
451        Ok(generics::GenericClipRectOrAuto::Auto)
452    }
453}
454
455/// Whether quirks are allowed in this context.
456#[derive(Clone, Copy, PartialEq)]
457pub enum AllowQuirks {
458    /// Quirks are not allowed.
459    No,
460    /// Quirks are allowed, in quirks mode.
461    Yes,
462    /// Quirks are always allowed, used for SVG lengths.
463    Always,
464}
465
466impl AllowQuirks {
467    /// Returns `true` if quirks are allowed in this context.
468    pub fn allowed(self, quirks_mode: QuirksMode) -> bool {
469        match self {
470            AllowQuirks::Always => true,
471            AllowQuirks::No => false,
472            AllowQuirks::Yes => quirks_mode == QuirksMode::Quirks,
473        }
474    }
475}
476
477#[derive(Clone, Debug, PartialEq, MallocSizeOf, ToShmem)]
478/// A namespace wrapper to distinguish between valid variants
479pub enum ParsedNamespace {
480    /// Unregistered namespace
481    Unknown,
482    /// Registered namespace
483    Known(Namespace),
484}
485
486impl ParsedNamespace {
487    /// Parse a namespace prefix and resolve it to the correct
488    /// namespace URI.
489    pub fn parse(
490        namespaces: &FxHashMap<Prefix, Namespace>,
491        input: &mut Parser,
492    ) -> Result<Self, ParseError> {
493        // We don't need to keep the prefix because different
494        // prefixes can resolve to the same id. Additionally,
495        // we also don't need it for serialization as substitution
496        // functions serialize from the direct css declaration.
497        parse_namespace(namespaces, input).map(|(_prefix, namespace)| namespace)
498    }
499}
500
501impl Default for ParsedNamespace {
502    fn default() -> Self {
503        Self::Known(Namespace::default())
504    }
505}
506
507/// Try to parse a namespace and return it if parsed, or none if there was not one present
508pub fn parse_namespace(
509    namespaces: &FxHashMap<Prefix, Namespace>,
510    input: &mut Parser,
511) -> Result<(Prefix, ParsedNamespace), ParseError> {
512    let ns_prefix = match input.next()? {
513        Token::Ident(prefix) => Some(Prefix::from(prefix.as_ref())),
514        Token::Delim('|') => None,
515        _ => return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
516    };
517
518    if ns_prefix.is_some() && !matches!(*input.next_including_whitespace()?, Token::Delim('|')) {
519        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
520    }
521
522    if let Some(prefix) = ns_prefix {
523        let ns = match namespaces.get(&prefix).cloned() {
524            Some(ns) => ParsedNamespace::Known(ns),
525            None => ParsedNamespace::Unknown,
526        };
527        Ok((prefix, ns))
528    } else {
529        Ok((Prefix::default(), ParsedNamespace::default()))
530    }
531}