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(Box<GenericAnchorSizeFunction<Self>>),
176    AnchorContainingCalcFunction(LengthPercent),
177}
178
179impl<LengthPercent> SpecifiedValueInfo for GenericSize<LengthPercent>
180where
181    LengthPercent: SpecifiedValueInfo,
182{
183    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
184        LengthPercent::collect_completion_keywords(f);
185        f(&["auto", "stretch", "fit-content"]);
186        if cfg!(feature = "gecko") {
187            f(&[
188                "max-content",
189                "min-content",
190                "-moz-available",
191                "-webkit-fill-available",
192            ]);
193        }
194        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
195            f(&["anchor-size"]);
196        }
197    }
198}
199
200pub use self::GenericSize as Size;
201
202impl<LengthPercentage> Size<LengthPercentage> {
203    /// `auto` value.
204    #[inline]
205    pub fn auto() -> Self {
206        Size::Auto
207    }
208
209    /// Returns whether we're the auto value.
210    #[inline]
211    pub fn is_auto(&self) -> bool {
212        matches!(*self, Size::Auto)
213    }
214}
215
216/// A generic value for the `max-width` or `max-height` property.
217#[allow(missing_docs)]
218#[derive(
219    Animate,
220    Clone,
221    ComputeSquaredDistance,
222    Debug,
223    MallocSizeOf,
224    PartialEq,
225    ToAnimatedValue,
226    ToAnimatedZero,
227    ToComputedValue,
228    ToCss,
229    ToResolvedValue,
230    ToShmem,
231)]
232#[repr(C, u8)]
233pub enum GenericMaxSize<LengthPercent> {
234    LengthPercentage(LengthPercent),
235    None,
236    #[animation(error)]
237    MaxContent,
238    #[animation(error)]
239    MinContent,
240    #[animation(error)]
241    FitContent,
242    #[cfg(feature = "gecko")]
243    #[animation(error)]
244    MozAvailable,
245    #[cfg(feature = "gecko")]
246    #[animation(error)]
247    WebkitFillAvailable,
248    #[animation(error)]
249    Stretch,
250    #[animation(error)]
251    #[css(function = "fit-content")]
252    FitContentFunction(LengthPercent),
253    AnchorSizeFunction(Box<GenericAnchorSizeFunction<Self>>),
254    AnchorContainingCalcFunction(LengthPercent),
255}
256
257impl<LP> SpecifiedValueInfo for GenericMaxSize<LP>
258where
259    LP: SpecifiedValueInfo,
260{
261    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
262        LP::collect_completion_keywords(f);
263        f(&["none", "stretch", "fit-content"]);
264        if cfg!(feature = "gecko") {
265            f(&[
266                "max-content",
267                "min-content",
268                "-moz-available",
269                "-webkit-fill-available",
270            ]);
271        }
272        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
273            f(&["anchor-size"]);
274        }
275    }
276}
277
278pub use self::GenericMaxSize as MaxSize;
279
280impl<LengthPercentage> MaxSize<LengthPercentage> {
281    /// `none` value.
282    #[inline]
283    pub fn none() -> Self {
284        MaxSize::None
285    }
286}
287
288/// A generic `<length>` | `<number>` value for the `tab-size` property.
289#[derive(
290    Animate,
291    Clone,
292    ComputeSquaredDistance,
293    Copy,
294    Debug,
295    MallocSizeOf,
296    Parse,
297    PartialEq,
298    SpecifiedValueInfo,
299    ToAnimatedValue,
300    ToAnimatedZero,
301    ToComputedValue,
302    ToCss,
303    ToResolvedValue,
304    ToShmem,
305)]
306#[repr(C, u8)]
307pub enum GenericLengthOrNumber<L, N> {
308    /// A number.
309    ///
310    /// NOTE: Numbers need to be before lengths, in order to parse them
311    /// first, since `0` should be a number, not the `0px` length.
312    Number(N),
313    /// A length.
314    Length(L),
315}
316
317pub use self::GenericLengthOrNumber as LengthOrNumber;
318
319impl<L, N: Zero> Zero for LengthOrNumber<L, N> {
320    fn zero() -> Self {
321        LengthOrNumber::Number(Zero::zero())
322    }
323
324    fn is_zero(&self) -> bool {
325        match *self {
326            LengthOrNumber::Number(ref n) => n.is_zero(),
327            LengthOrNumber::Length(..) => false,
328        }
329    }
330}
331
332/// A generic `<length-percentage>` | normal` value.
333#[derive(
334    Animate,
335    Clone,
336    ComputeSquaredDistance,
337    Copy,
338    Debug,
339    MallocSizeOf,
340    Parse,
341    PartialEq,
342    SpecifiedValueInfo,
343    ToAnimatedValue,
344    ToAnimatedZero,
345    ToComputedValue,
346    ToCss,
347    ToResolvedValue,
348    ToShmem,
349)]
350#[repr(C, u8)]
351#[allow(missing_docs)]
352pub enum GenericLengthPercentageOrNormal<LengthPercent> {
353    LengthPercentage(LengthPercent),
354    Normal,
355}
356
357pub use self::GenericLengthPercentageOrNormal as LengthPercentageOrNormal;
358
359impl<LengthPercent> LengthPercentageOrNormal<LengthPercent> {
360    /// Returns the normal value.
361    #[inline]
362    pub fn normal() -> Self {
363        LengthPercentageOrNormal::Normal
364    }
365}
366
367/// Anchor size function used by sizing, margin and inset properties.
368/// This resolves to the size of the anchor at computed time.
369///
370/// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor-size
371#[derive(
372    Animate,
373    Clone,
374    ComputeSquaredDistance,
375    Debug,
376    MallocSizeOf,
377    PartialEq,
378    SpecifiedValueInfo,
379    ToShmem,
380    ToAnimatedValue,
381    ToAnimatedZero,
382    ToComputedValue,
383    ToResolvedValue,
384    Serialize,
385    Deserialize,
386)]
387#[repr(C)]
388pub struct GenericAnchorSizeFunction<Fallback> {
389    /// Anchor name of the element to anchor to.
390    /// If omitted (i.e. empty), selects the implicit anchor element.
391    #[animation(constant)]
392    pub target_element: DashedIdent,
393    /// Size of the positioned element, expressed in that of the anchor element.
394    /// If omitted, defaults to the axis of the property the function is used in.
395    pub size: AnchorSizeKeyword,
396    /// Value to use in case the anchor function is invalid.
397    pub fallback: Optional<Fallback>,
398}
399
400impl<Fallback> ToCss for GenericAnchorSizeFunction<Fallback>
401where
402    Fallback: ToCss,
403{
404    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> std::fmt::Result
405    where
406        W: Write,
407    {
408        dest.write_str("anchor-size(")?;
409        let mut previous_entry_printed = false;
410        if !self.target_element.is_empty() {
411            previous_entry_printed = true;
412            self.target_element.to_css(dest)?;
413        }
414        if self.size != AnchorSizeKeyword::None {
415            if previous_entry_printed {
416                dest.write_str(" ")?;
417            }
418            previous_entry_printed = true;
419            self.size.to_css(dest)?;
420        }
421        if let Some(f) = self.fallback.as_ref() {
422            if previous_entry_printed {
423                dest.write_str(", ")?;
424            }
425            f.to_css(dest)?;
426        }
427        dest.write_str(")")
428    }
429}
430
431impl<Fallback> Parse for GenericAnchorSizeFunction<Fallback>
432where
433    Fallback: Parse,
434{
435    fn parse<'i, 't>(
436        context: &ParserContext,
437        input: &mut Parser<'i, 't>,
438    ) -> Result<Self, ParseError<'i>> {
439        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
440            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
441        }
442        input.expect_function_matching("anchor-size")?;
443        Self::parse_inner(context, input, |i| Fallback::parse(context, i))
444    }
445}
446impl<Fallback> GenericAnchorSizeFunction<Fallback> {
447    /// Is the anchor-size use valid for given property?
448    pub fn valid_for(&self, position_property: PositionProperty) -> bool {
449        position_property.is_absolutely_positioned()
450    }
451}
452
453/// Result of resolving an anchor function.
454pub enum AnchorResolutionResult<'a, LengthPercentage> {
455    /// Function resolved to a valid anchor.
456    Resolved(LengthPercentage),
457    /// Referenced anchor is invalid, but fallback is used.
458    Fallback(&'a LengthPercentage),
459    /// Referenced anchor is invalid.
460    Invalid,
461}
462
463impl<'a, LengthPercentage> AnchorResolutionResult<'a, LengthPercentage> {
464    /// Return result for an invalid anchor function, depending on if it has any fallback.
465    pub fn new_anchor_invalid(fallback: Option<&'a LengthPercentage>) -> Self {
466        if let Some(fb) = fallback {
467            return Self::Fallback(fb);
468        }
469        Self::Invalid
470    }
471}
472
473impl<LengthPercentage> GenericAnchorSizeFunction<LengthPercentage> {
474    /// Parse the inner part of `anchor-size()`, after the parser has consumed "anchor-size(".
475    pub fn parse_inner<'i, 't, F>(
476        context: &ParserContext,
477        input: &mut Parser<'i, 't>,
478        f: F,
479    ) -> Result<Self, ParseError<'i>>
480    where
481        F: FnOnce(&mut Parser<'i, '_>) -> Result<LengthPercentage, ParseError<'i>>,
482    {
483        input.parse_nested_block(|i| {
484            let mut target_element = i
485                .try_parse(|i| DashedIdent::parse(context, i))
486                .unwrap_or(DashedIdent::empty());
487            let size = i
488                .try_parse(AnchorSizeKeyword::parse)
489                .unwrap_or(AnchorSizeKeyword::None);
490            if target_element.is_empty() {
491                target_element = i
492                    .try_parse(|i| DashedIdent::parse(context, i))
493                    .unwrap_or(DashedIdent::empty());
494            }
495            let previous_parsed = !target_element.is_empty() || size != AnchorSizeKeyword::None;
496            let fallback = i
497                .try_parse(|i| {
498                    if previous_parsed {
499                        i.expect_comma()?;
500                    }
501                    f(i)
502                })
503                .ok();
504            Ok(GenericAnchorSizeFunction {
505                target_element,
506                size: size.into(),
507                fallback: fallback.into(),
508            })
509        })
510    }
511}
512
513/// Keyword values for the anchor size function.
514#[derive(
515    Animate,
516    Clone,
517    ComputeSquaredDistance,
518    Copy,
519    Debug,
520    MallocSizeOf,
521    PartialEq,
522    Parse,
523    SpecifiedValueInfo,
524    ToCss,
525    ToShmem,
526    ToAnimatedValue,
527    ToAnimatedZero,
528    ToComputedValue,
529    ToResolvedValue,
530    Serialize,
531    Deserialize,
532)]
533#[repr(u8)]
534pub enum AnchorSizeKeyword {
535    /// Magic value for nothing.
536    #[css(skip)]
537    None,
538    /// Width of the anchor element.
539    Width,
540    /// Height of the anchor element.
541    Height,
542    /// Block size of the anchor element.
543    Block,
544    /// Inline size of the anchor element.
545    Inline,
546    /// Same as `Block`, resolved against the positioned element's writing mode.
547    SelfBlock,
548    /// Same as `Inline`, resolved against the positioned element's writing mode.
549    SelfInline,
550}
551
552/// Specified type for `margin` properties, which allows
553/// the use of the `anchor-size()` function.
554#[derive(
555    Animate,
556    Clone,
557    ComputeSquaredDistance,
558    Debug,
559    MallocSizeOf,
560    PartialEq,
561    ToCss,
562    ToShmem,
563    ToAnimatedValue,
564    ToAnimatedZero,
565    ToComputedValue,
566    ToResolvedValue,
567)]
568#[repr(C)]
569pub enum GenericMargin<LP> {
570    /// A `<length-percentage>` value.
571    LengthPercentage(LP),
572    /// An `auto` value.
573    Auto,
574    /// Margin size defined by the anchor element.
575    ///
576    /// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor-size
577    AnchorSizeFunction(Box<GenericAnchorSizeFunction<Self>>),
578    /// A `<length-percentage>` value, guaranteed to contain `calc()`,
579    /// which then is guaranteed to contain `anchor()` or `anchor-size()`.
580    AnchorContainingCalcFunction(LP),
581}
582
583#[cfg(feature = "servo")]
584impl<LP> GenericMargin<LP> {
585    /// Return true if it is 'auto'.
586    #[inline]
587    pub fn is_auto(&self) -> bool {
588        matches!(self, Self::Auto)
589    }
590}
591
592#[cfg(feature = "servo")]
593impl GenericMargin<crate::values::computed::LengthPercentage> {
594    /// Returns true if the computed value is absolute 0 or 0%.
595    #[inline]
596    pub fn is_definitely_zero(&self) -> bool {
597        match self {
598            Self::LengthPercentage(lp) => lp.is_definitely_zero(),
599            _ => false,
600        }
601    }
602}
603
604impl<LP> SpecifiedValueInfo for GenericMargin<LP>
605where
606    LP: SpecifiedValueInfo,
607{
608    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
609        LP::collect_completion_keywords(f);
610        f(&["auto"]);
611        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
612            f(&["anchor-size"]);
613        }
614    }
615}
616
617impl<LP> Zero for GenericMargin<LP>
618where
619    LP: Zero,
620{
621    fn is_zero(&self) -> bool {
622        match self {
623            Self::LengthPercentage(l) => l.is_zero(),
624            Self::Auto | Self::AnchorSizeFunction(_) | Self::AnchorContainingCalcFunction(_) => {
625                false
626            },
627        }
628    }
629
630    fn zero() -> Self {
631        Self::LengthPercentage(LP::zero())
632    }
633}