style/values/generics/
length.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//! Generic types for CSS values related to length.
6
7use crate::parser::{Parse, ParserContext};
8use crate::values::generics::box_::PositionProperty;
9use crate::values::generics::Optional;
10use crate::values::DashedIdent;
11use crate::Zero;
12use cssparser::Parser;
13use std::fmt::Write;
14use style_traits::ParseError;
15use style_traits::StyleParseErrorKind;
16use style_traits::ToCss;
17use style_traits::{CssWriter, SpecifiedValueInfo};
18
19/// A `<length-percentage> | auto` value.
20#[allow(missing_docs)]
21#[derive(
22    Animate,
23    Clone,
24    ComputeSquaredDistance,
25    Copy,
26    Debug,
27    MallocSizeOf,
28    PartialEq,
29    SpecifiedValueInfo,
30    ToAnimatedValue,
31    ToAnimatedZero,
32    ToComputedValue,
33    ToCss,
34    ToResolvedValue,
35    ToShmem,
36)]
37#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
38#[repr(C, u8)]
39pub enum GenericLengthPercentageOrAuto<LengthPercent> {
40    LengthPercentage(LengthPercent),
41    Auto,
42}
43
44pub use self::GenericLengthPercentageOrAuto as LengthPercentageOrAuto;
45
46impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage> {
47    /// `auto` value.
48    #[inline]
49    pub fn auto() -> Self {
50        LengthPercentageOrAuto::Auto
51    }
52
53    /// Whether this is the `auto` value.
54    #[inline]
55    pub fn is_auto(&self) -> bool {
56        matches!(*self, LengthPercentageOrAuto::Auto)
57    }
58
59    /// A helper function to parse this with quirks or not and so forth.
60    pub fn parse_with<'i, 't>(
61        context: &ParserContext,
62        input: &mut Parser<'i, 't>,
63        parser: impl FnOnce(
64            &ParserContext,
65            &mut Parser<'i, 't>,
66        ) -> Result<LengthPercentage, ParseError<'i>>,
67    ) -> Result<Self, ParseError<'i>> {
68        if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
69            return Ok(LengthPercentageOrAuto::Auto);
70        }
71
72        Ok(LengthPercentageOrAuto::LengthPercentage(parser(
73            context, input,
74        )?))
75    }
76}
77
78impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage>
79where
80    LengthPercentage: Clone,
81{
82    /// Resolves `auto` values by calling `f`.
83    #[inline]
84    pub fn auto_is(&self, f: impl FnOnce() -> LengthPercentage) -> LengthPercentage {
85        match self {
86            LengthPercentageOrAuto::LengthPercentage(length) => length.clone(),
87            LengthPercentageOrAuto::Auto => f(),
88        }
89    }
90
91    /// Returns the non-`auto` value, if any.
92    #[inline]
93    pub fn non_auto(&self) -> Option<LengthPercentage> {
94        match self {
95            LengthPercentageOrAuto::LengthPercentage(length) => Some(length.clone()),
96            LengthPercentageOrAuto::Auto => None,
97        }
98    }
99
100    /// Maps the length of this value.
101    pub fn map<T>(&self, f: impl FnOnce(LengthPercentage) -> T) -> LengthPercentageOrAuto<T> {
102        match self {
103            LengthPercentageOrAuto::LengthPercentage(l) => {
104                LengthPercentageOrAuto::LengthPercentage(f(l.clone()))
105            },
106            LengthPercentageOrAuto::Auto => LengthPercentageOrAuto::Auto,
107        }
108    }
109}
110
111impl<LengthPercentage: Zero> Zero for LengthPercentageOrAuto<LengthPercentage> {
112    fn zero() -> Self {
113        LengthPercentageOrAuto::LengthPercentage(Zero::zero())
114    }
115
116    fn is_zero(&self) -> bool {
117        match *self {
118            LengthPercentageOrAuto::LengthPercentage(ref l) => l.is_zero(),
119            LengthPercentageOrAuto::Auto => false,
120        }
121    }
122}
123
124impl<LengthPercentage: Parse> Parse for LengthPercentageOrAuto<LengthPercentage> {
125    fn parse<'i, 't>(
126        context: &ParserContext,
127        input: &mut Parser<'i, 't>,
128    ) -> Result<Self, ParseError<'i>> {
129        Self::parse_with(context, input, LengthPercentage::parse)
130    }
131}
132
133/// A generic value for the `width`, `height`, `min-width`, or `min-height` property.
134///
135/// Unlike `max-width` or `max-height` properties, a Size can be `auto`,
136/// and cannot be `none`.
137///
138/// Note that it only accepts non-negative values.
139#[allow(missing_docs)]
140#[derive(
141    Animate,
142    ComputeSquaredDistance,
143    Clone,
144    Debug,
145    MallocSizeOf,
146    PartialEq,
147    ToAnimatedValue,
148    ToAnimatedZero,
149    ToComputedValue,
150    ToCss,
151    ToResolvedValue,
152    ToShmem,
153)]
154#[repr(C, u8)]
155pub enum GenericSize<LengthPercent> {
156    LengthPercentage(LengthPercent),
157    Auto,
158    #[animation(error)]
159    MaxContent,
160    #[animation(error)]
161    MinContent,
162    #[animation(error)]
163    FitContent,
164    #[cfg(feature = "gecko")]
165    #[animation(error)]
166    MozAvailable,
167    #[cfg(feature = "gecko")]
168    #[animation(error)]
169    WebkitFillAvailable,
170    #[animation(error)]
171    Stretch,
172    #[animation(error)]
173    #[css(function = "fit-content")]
174    FitContentFunction(LengthPercent),
175    AnchorSizeFunction(
176        #[animation(field_bound)]
177        #[distance(field_bound)]
178        Box<GenericAnchorSizeFunction<LengthPercent>>
179    ),
180    AnchorContainingCalcFunction(LengthPercent),
181}
182
183impl<LengthPercent> SpecifiedValueInfo for GenericSize<LengthPercent>
184where
185LengthPercent: SpecifiedValueInfo
186{
187    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
188        LengthPercent::collect_completion_keywords(f);
189        f(&["auto", "stretch", "fit-content"]);
190        if cfg!(feature = "gecko") {
191            f(&["max-content", "min-content", "-moz-available", "-webkit-fill-available"]);
192        }
193        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
194            f(&["anchor-size"]);
195        }
196    }
197}
198
199pub use self::GenericSize as Size;
200
201impl<LengthPercentage> Size<LengthPercentage> {
202    /// `auto` value.
203    #[inline]
204    pub fn auto() -> Self {
205        Size::Auto
206    }
207
208    /// Returns whether we're the auto value.
209    #[inline]
210    pub fn is_auto(&self) -> bool {
211        matches!(*self, Size::Auto)
212    }
213}
214
215/// A generic value for the `max-width` or `max-height` property.
216#[allow(missing_docs)]
217#[derive(
218    Animate,
219    Clone,
220    ComputeSquaredDistance,
221    Debug,
222    MallocSizeOf,
223    PartialEq,
224    ToAnimatedValue,
225    ToAnimatedZero,
226    ToComputedValue,
227    ToCss,
228    ToResolvedValue,
229    ToShmem,
230)]
231#[repr(C, u8)]
232pub enum GenericMaxSize<LengthPercent> {
233    LengthPercentage(LengthPercent),
234    None,
235    #[animation(error)]
236    MaxContent,
237    #[animation(error)]
238    MinContent,
239    #[animation(error)]
240    FitContent,
241    #[cfg(feature = "gecko")]
242    #[animation(error)]
243    MozAvailable,
244    #[cfg(feature = "gecko")]
245    #[animation(error)]
246    WebkitFillAvailable,
247    #[animation(error)]
248    Stretch,
249    #[animation(error)]
250    #[css(function = "fit-content")]
251    FitContentFunction(LengthPercent),
252    AnchorSizeFunction(
253        #[animation(field_bound)]
254        #[distance(field_bound)]
255        Box<GenericAnchorSizeFunction<LengthPercent>>
256    ),
257    AnchorContainingCalcFunction(LengthPercent),
258}
259
260impl<LP> SpecifiedValueInfo for GenericMaxSize<LP>
261where
262    LP: SpecifiedValueInfo
263{
264    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
265        LP::collect_completion_keywords(f);
266        f(&["none", "stretch", "fit-content"]);
267        if cfg!(feature = "gecko") {
268            f(&["max-content", "min-content", "-moz-available", "-webkit-fill-available"]);
269        }
270        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
271            f(&["anchor-size"]);
272        }
273    }
274}
275
276pub use self::GenericMaxSize as MaxSize;
277
278impl<LengthPercentage> MaxSize<LengthPercentage> {
279    /// `none` value.
280    #[inline]
281    pub fn none() -> Self {
282        MaxSize::None
283    }
284}
285
286/// A generic `<length>` | `<number>` value for the `tab-size` property.
287#[derive(
288    Animate,
289    Clone,
290    ComputeSquaredDistance,
291    Copy,
292    Debug,
293    MallocSizeOf,
294    Parse,
295    PartialEq,
296    SpecifiedValueInfo,
297    ToAnimatedValue,
298    ToAnimatedZero,
299    ToComputedValue,
300    ToCss,
301    ToResolvedValue,
302    ToShmem,
303)]
304#[repr(C, u8)]
305pub enum GenericLengthOrNumber<L, N> {
306    /// A number.
307    ///
308    /// NOTE: Numbers need to be before lengths, in order to parse them
309    /// first, since `0` should be a number, not the `0px` length.
310    Number(N),
311    /// A length.
312    Length(L),
313}
314
315pub use self::GenericLengthOrNumber as LengthOrNumber;
316
317impl<L, N: Zero> Zero for LengthOrNumber<L, N> {
318    fn zero() -> Self {
319        LengthOrNumber::Number(Zero::zero())
320    }
321
322    fn is_zero(&self) -> bool {
323        match *self {
324            LengthOrNumber::Number(ref n) => n.is_zero(),
325            LengthOrNumber::Length(..) => false,
326        }
327    }
328}
329
330/// A generic `<length-percentage>` | normal` value.
331#[derive(
332    Animate,
333    Clone,
334    ComputeSquaredDistance,
335    Copy,
336    Debug,
337    MallocSizeOf,
338    Parse,
339    PartialEq,
340    SpecifiedValueInfo,
341    ToAnimatedValue,
342    ToAnimatedZero,
343    ToComputedValue,
344    ToCss,
345    ToResolvedValue,
346    ToShmem,
347)]
348#[repr(C, u8)]
349#[allow(missing_docs)]
350pub enum GenericLengthPercentageOrNormal<LengthPercent> {
351    LengthPercentage(LengthPercent),
352    Normal,
353}
354
355pub use self::GenericLengthPercentageOrNormal as LengthPercentageOrNormal;
356
357impl<LengthPercent> LengthPercentageOrNormal<LengthPercent> {
358    /// Returns the normal value.
359    #[inline]
360    pub fn normal() -> Self {
361        LengthPercentageOrNormal::Normal
362    }
363}
364
365/// Anchor size function used by sizing, margin and inset properties.
366/// This resolves to the size of the anchor at computed time.
367///
368/// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor-size
369#[derive(
370    Animate,
371    Clone,
372    ComputeSquaredDistance,
373    Debug,
374    MallocSizeOf,
375    PartialEq,
376    SpecifiedValueInfo,
377    ToShmem,
378    ToAnimatedValue,
379    ToAnimatedZero,
380    ToComputedValue,
381    ToResolvedValue,
382    Serialize,
383    Deserialize,
384)]
385#[repr(C)]
386pub struct GenericAnchorSizeFunction<LengthPercentage> {
387    /// Anchor name of the element to anchor to.
388    /// If omitted (i.e. empty), selects the implicit anchor element.
389    #[animation(constant)]
390    pub target_element: DashedIdent,
391    /// Size of the positioned element, expressed in that of the anchor element.
392    /// If omitted, defaults to the axis of the property the function is used in.
393    pub size: AnchorSizeKeyword,
394    /// Value to use in case the anchor function is invalid.
395    pub fallback: Optional<LengthPercentage>,
396}
397
398impl<LengthPercentage> ToCss for GenericAnchorSizeFunction<LengthPercentage>
399where
400    LengthPercentage: ToCss,
401{
402    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> std::fmt::Result
403    where
404        W: Write,
405    {
406        dest.write_str("anchor-size(")?;
407        let mut previous_entry_printed = false;
408        if !self.target_element.is_empty() {
409            previous_entry_printed = true;
410            self.target_element.to_css(dest)?;
411        }
412        if self.size != AnchorSizeKeyword::None {
413            if previous_entry_printed {
414                dest.write_str(" ")?;
415            }
416            previous_entry_printed = true;
417            self.size.to_css(dest)?;
418        }
419        if let Some(f) = self.fallback.as_ref() {
420            if previous_entry_printed {
421                dest.write_str(", ")?;
422            }
423            f.to_css(dest)?;
424        }
425        dest.write_str(")")
426    }
427}
428
429impl<LengthPercentage> Parse for GenericAnchorSizeFunction<LengthPercentage>
430where
431    LengthPercentage: Parse,
432{
433    fn parse<'i, 't>(
434        context: &ParserContext,
435        input: &mut Parser<'i, 't>,
436    ) -> Result<Self, ParseError<'i>> {
437        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
438            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
439        }
440        input.expect_function_matching("anchor-size")?;
441        Self::parse_inner(
442            context,
443            input,
444            |i| LengthPercentage::parse(context, i)
445        )
446    }
447}
448
449/// Result of resolving an anchor function.
450pub enum AnchorResolutionResult<'a, LengthPercentage> {
451    /// Function resolved to a valid anchor.
452    Resolved(LengthPercentage),
453    /// Referenced anchor is invalid, but fallback is used.
454    Fallback(&'a LengthPercentage),
455    /// Referenced anchor is invalid.
456    Invalid,
457}
458
459impl<'a, LengthPercentage> AnchorResolutionResult<'a, LengthPercentage> {
460    /// Return result for an invalid anchor function, depending on if it has any fallback.
461    pub fn new_anchor_invalid(fallback: Option<&'a LengthPercentage>) -> Self {
462        if let Some(fb) = fallback {
463            return Self::Fallback(fb);
464        }
465        Self::Invalid
466    }
467}
468
469impl<LengthPercentage> GenericAnchorSizeFunction<LengthPercentage>
470{
471    /// Parse the inner part of `anchor-size()`, after the parser has consumed "anchor-size(".
472    pub fn parse_inner<'i, 't, F>(
473        context: &ParserContext,
474        input: &mut Parser<'i, 't>,
475        f: F,
476    ) -> Result<Self, ParseError<'i>>
477    where
478        F: FnOnce(&mut Parser<'i, '_>) -> Result<LengthPercentage, ParseError<'i>>,
479    {
480        input.parse_nested_block(|i| {
481            let mut target_element = i
482                .try_parse(|i| DashedIdent::parse(context, i))
483                .unwrap_or(DashedIdent::empty());
484            let size = i.try_parse(AnchorSizeKeyword::parse).unwrap_or(AnchorSizeKeyword::None);
485            if target_element.is_empty() {
486                target_element = i
487                    .try_parse(|i| DashedIdent::parse(context, i))
488                    .unwrap_or(DashedIdent::empty());
489            }
490            let previous_parsed = !target_element.is_empty() || size != AnchorSizeKeyword::None;
491            let fallback = i
492                .try_parse(|i| {
493                    if previous_parsed {
494                        i.expect_comma()?;
495                    }
496                    f(i)
497                })
498                .ok();
499            Ok(GenericAnchorSizeFunction {
500                target_element,
501                size: size.into(),
502                fallback: fallback.into(),
503            })
504        })
505    }
506
507    /// Resolve the anchor size function. On failure, return reference to fallback, if exists.
508    pub fn resolve<'a>(
509        &'a self,
510        position_property: PositionProperty,
511    ) -> AnchorResolutionResult<'a, LengthPercentage> {
512        if !position_property.is_absolutely_positioned() {
513            return AnchorResolutionResult::new_anchor_invalid(self.fallback.as_ref());
514        }
515
516        // TODO(dshin): Do the actual anchor resolution here.
517        AnchorResolutionResult::new_anchor_invalid(self.fallback.as_ref())
518    }
519}
520
521/// Keyword values for the anchor size function.
522#[derive(
523    Animate,
524    Clone,
525    ComputeSquaredDistance,
526    Copy,
527    Debug,
528    MallocSizeOf,
529    PartialEq,
530    Parse,
531    SpecifiedValueInfo,
532    ToCss,
533    ToShmem,
534    ToAnimatedValue,
535    ToAnimatedZero,
536    ToComputedValue,
537    ToResolvedValue,
538    Serialize,
539    Deserialize,
540)]
541#[repr(u8)]
542pub enum AnchorSizeKeyword {
543    /// Magic value for nothing.
544    #[css(skip)]
545    None,
546    /// Width of the anchor element.
547    Width,
548    /// Height of the anchor element.
549    Height,
550    /// Block size of the anchor element.
551    Block,
552    /// Inline size of the anchor element.
553    Inline,
554    /// Same as `Block`, resolved against the positioned element's writing mode.
555    SelfBlock,
556    /// Same as `Inline`, resolved against the positioned element's writing mode.
557    SelfInline,
558}
559
560/// Specified type for `margin` properties, which allows
561/// the use of the `anchor-size()` function.
562#[derive(
563    Animate,
564    Clone,
565    ComputeSquaredDistance,
566    Debug,
567    MallocSizeOf,
568    PartialEq,
569    ToCss,
570    ToShmem,
571    ToAnimatedValue,
572    ToAnimatedZero,
573    ToComputedValue,
574    ToResolvedValue,
575)]
576#[repr(C)]
577pub enum GenericMargin<LP> {
578    /// A `<length-percentage>` value.
579    LengthPercentage(LP),
580    /// An `auto` value.
581    Auto,
582    /// Margin size defined by the anchor element.
583    ///
584    /// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor-size
585    AnchorSizeFunction(
586        #[animation(field_bound)]
587        #[distance(field_bound)]
588        Box<GenericAnchorSizeFunction<LP>>,
589    ),
590    /// A `<length-percentage>` value, guaranteed to contain `calc()`,
591    /// which then is guaranteed to contain `anchor()` or `anchor-size()`.
592    AnchorContainingCalcFunction(LP),
593}
594
595#[cfg(feature = "servo")]
596impl<LP> GenericMargin<LP> {
597    /// Return true if it is 'auto'.
598    #[inline]
599    pub fn is_auto(&self) -> bool {
600        matches!(self, Self::Auto)
601    }
602}
603
604#[cfg(feature = "servo")]
605impl GenericMargin<crate::values::computed::LengthPercentage> {
606    /// Returns true if the computed value is absolute 0 or 0%.
607    #[inline]
608    pub fn is_definitely_zero(&self) -> bool {
609        match self {
610            Self::LengthPercentage(lp) => lp.is_definitely_zero(),
611            _ => false,
612        }
613    }
614}
615
616impl<LP> SpecifiedValueInfo for GenericMargin<LP>
617where
618    LP: SpecifiedValueInfo,
619{
620    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
621        LP::collect_completion_keywords(f);
622        f(&["auto"]);
623        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
624            f(&["anchor-size"]);
625        }
626    }
627}
628
629impl<LP> Zero for GenericMargin<LP>
630where
631    LP: Zero,
632{
633    fn is_zero(&self) -> bool {
634        match self {
635            Self::LengthPercentage(l) => l.is_zero(),
636            Self::Auto | Self::AnchorSizeFunction(_) | Self::AnchorContainingCalcFunction(_) => {
637                false
638            },
639        }
640    }
641
642    fn zero() -> Self {
643        Self::LengthPercentage(LP::zero())
644    }
645}