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