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    ToTyped,
37)]
38#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
39#[repr(C, u8)]
40pub enum GenericLengthPercentageOrAuto<LengthPercent> {
41    LengthPercentage(LengthPercent),
42    Auto,
43}
44
45pub use self::GenericLengthPercentageOrAuto as LengthPercentageOrAuto;
46
47impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage> {
48    /// `auto` value.
49    #[inline]
50    pub fn auto() -> Self {
51        LengthPercentageOrAuto::Auto
52    }
53
54    /// Whether this is the `auto` value.
55    #[inline]
56    pub fn is_auto(&self) -> bool {
57        matches!(*self, LengthPercentageOrAuto::Auto)
58    }
59
60    /// A helper function to parse this with quirks or not and so forth.
61    pub fn parse_with<'i, 't>(
62        context: &ParserContext,
63        input: &mut Parser<'i, 't>,
64        parser: impl FnOnce(
65            &ParserContext,
66            &mut Parser<'i, 't>,
67        ) -> Result<LengthPercentage, ParseError<'i>>,
68    ) -> Result<Self, ParseError<'i>> {
69        if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
70            return Ok(LengthPercentageOrAuto::Auto);
71        }
72
73        Ok(LengthPercentageOrAuto::LengthPercentage(parser(
74            context, input,
75        )?))
76    }
77}
78
79impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage>
80where
81    LengthPercentage: Clone,
82{
83    /// Resolves `auto` values by calling `f`.
84    #[inline]
85    pub fn auto_is(&self, f: impl FnOnce() -> LengthPercentage) -> LengthPercentage {
86        match self {
87            LengthPercentageOrAuto::LengthPercentage(length) => length.clone(),
88            LengthPercentageOrAuto::Auto => f(),
89        }
90    }
91
92    /// Returns the non-`auto` value, if any.
93    #[inline]
94    pub fn non_auto(&self) -> Option<LengthPercentage> {
95        match self {
96            LengthPercentageOrAuto::LengthPercentage(length) => Some(length.clone()),
97            LengthPercentageOrAuto::Auto => None,
98        }
99    }
100
101    /// Maps the length of this value.
102    pub fn map<T>(&self, f: impl FnOnce(LengthPercentage) -> T) -> LengthPercentageOrAuto<T> {
103        match self {
104            LengthPercentageOrAuto::LengthPercentage(l) => {
105                LengthPercentageOrAuto::LengthPercentage(f(l.clone()))
106            },
107            LengthPercentageOrAuto::Auto => LengthPercentageOrAuto::Auto,
108        }
109    }
110}
111
112impl<LengthPercentage: Zero> Zero for LengthPercentageOrAuto<LengthPercentage> {
113    fn zero() -> Self {
114        LengthPercentageOrAuto::LengthPercentage(Zero::zero())
115    }
116
117    fn is_zero(&self) -> bool {
118        match *self {
119            LengthPercentageOrAuto::LengthPercentage(ref l) => l.is_zero(),
120            LengthPercentageOrAuto::Auto => false,
121        }
122    }
123}
124
125impl<LengthPercentage: Parse> Parse for LengthPercentageOrAuto<LengthPercentage> {
126    fn parse<'i, 't>(
127        context: &ParserContext,
128        input: &mut Parser<'i, 't>,
129    ) -> Result<Self, ParseError<'i>> {
130        Self::parse_with(context, input, LengthPercentage::parse)
131    }
132}
133
134/// A generic value for the `width`, `height`, `min-width`, or `min-height` property.
135///
136/// Unlike `max-width` or `max-height` properties, a Size can be `auto`,
137/// and cannot be `none`.
138///
139/// Note that it only accepts non-negative values.
140#[allow(missing_docs)]
141#[derive(
142    Animate,
143    ComputeSquaredDistance,
144    Clone,
145    Debug,
146    MallocSizeOf,
147    PartialEq,
148    ToAnimatedValue,
149    ToAnimatedZero,
150    ToComputedValue,
151    ToCss,
152    ToResolvedValue,
153    ToShmem,
154    ToTyped,
155)]
156#[repr(C, u8)]
157pub enum GenericSize<LengthPercent> {
158    LengthPercentage(LengthPercent),
159    Auto,
160    #[animation(error)]
161    MaxContent,
162    #[animation(error)]
163    MinContent,
164    #[animation(error)]
165    FitContent,
166    #[cfg(feature = "gecko")]
167    #[animation(error)]
168    MozAvailable,
169    #[animation(error)]
170    WebkitFillAvailable,
171    #[animation(error)]
172    Stretch,
173    #[animation(error)]
174    #[css(function = "fit-content")]
175    FitContentFunction(LengthPercent),
176    AnchorSizeFunction(Box<GenericAnchorSizeFunction<Self>>),
177    AnchorContainingCalcFunction(LengthPercent),
178}
179
180impl<LengthPercent> SpecifiedValueInfo for GenericSize<LengthPercent>
181where
182    LengthPercent: SpecifiedValueInfo,
183{
184    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
185        LengthPercent::collect_completion_keywords(f);
186        f(&["auto", "stretch", "fit-content", "max-content", "min-content"]);
187        if cfg!(feature = "gecko") {
188            f(&["-moz-available"]);
189        }
190        if static_prefs::pref!("layout.css.webkit-fill-available.enabled") {
191            f(&["-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    ToTyped,
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    #[animation(error)]
246    WebkitFillAvailable,
247    #[animation(error)]
248    Stretch,
249    #[animation(error)]
250    #[css(function = "fit-content")]
251    FitContentFunction(LengthPercent),
252    AnchorSizeFunction(Box<GenericAnchorSizeFunction<Self>>),
253    AnchorContainingCalcFunction(LengthPercent),
254}
255
256impl<LP> SpecifiedValueInfo for GenericMaxSize<LP>
257where
258    LP: SpecifiedValueInfo,
259{
260    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
261        LP::collect_completion_keywords(f);
262        f(&["none", "stretch", "fit-content", "max-content", "min-content"]);
263        if cfg!(feature = "gecko") {
264            f(&["-moz-available"]);
265        }
266        if static_prefs::pref!("layout.css.webkit-fill-available.enabled") {
267            f(&["-webkit-fill-available"]);
268        }
269        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
270            f(&["anchor-size"]);
271        }
272    }
273}
274
275pub use self::GenericMaxSize as MaxSize;
276
277impl<LengthPercentage> MaxSize<LengthPercentage> {
278    /// `none` value.
279    #[inline]
280    pub fn none() -> Self {
281        MaxSize::None
282    }
283}
284
285/// A generic `<length>` | `<number>` value for the `tab-size` property.
286#[derive(
287    Animate,
288    Clone,
289    ComputeSquaredDistance,
290    Copy,
291    Debug,
292    MallocSizeOf,
293    Parse,
294    PartialEq,
295    SpecifiedValueInfo,
296    ToAnimatedValue,
297    ToAnimatedZero,
298    ToComputedValue,
299    ToCss,
300    ToResolvedValue,
301    ToShmem,
302    ToTyped,
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    ToTyped,
348)]
349#[repr(C, u8)]
350#[allow(missing_docs)]
351pub enum GenericLengthPercentageOrNormal<LengthPercent> {
352    LengthPercentage(LengthPercent),
353    Normal,
354}
355
356pub use self::GenericLengthPercentageOrNormal as LengthPercentageOrNormal;
357
358impl<LengthPercent> LengthPercentageOrNormal<LengthPercent> {
359    /// Returns the normal value.
360    #[inline]
361    pub fn normal() -> Self {
362        LengthPercentageOrNormal::Normal
363    }
364}
365
366/// Anchor size function used by sizing, margin and inset properties.
367/// This resolves to the size of the anchor at computed time.
368///
369/// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor-size
370#[derive(
371    Animate,
372    Clone,
373    ComputeSquaredDistance,
374    Debug,
375    MallocSizeOf,
376    PartialEq,
377    SpecifiedValueInfo,
378    ToShmem,
379    ToAnimatedValue,
380    ToAnimatedZero,
381    ToComputedValue,
382    ToResolvedValue,
383    Serialize,
384    Deserialize,
385)]
386#[repr(C)]
387pub struct GenericAnchorSizeFunction<Fallback> {
388    /// Anchor name of the element to anchor to.
389    /// If omitted (i.e. empty), selects the implicit anchor element.
390    #[animation(constant)]
391    pub target_element: DashedIdent,
392    /// Size of the positioned element, expressed in that of the anchor element.
393    /// If omitted, defaults to the axis of the property the function is used in.
394    pub size: AnchorSizeKeyword,
395    /// Value to use in case the anchor function is invalid.
396    pub fallback: Optional<Fallback>,
397}
398
399impl<Fallback> ToCss for GenericAnchorSizeFunction<Fallback>
400where
401    Fallback: ToCss,
402{
403    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> std::fmt::Result
404    where
405        W: Write,
406    {
407        dest.write_str("anchor-size(")?;
408        let mut previous_entry_printed = false;
409        if !self.target_element.is_empty() {
410            previous_entry_printed = true;
411            self.target_element.to_css(dest)?;
412        }
413        if self.size != AnchorSizeKeyword::None {
414            if previous_entry_printed {
415                dest.write_str(" ")?;
416            }
417            previous_entry_printed = true;
418            self.size.to_css(dest)?;
419        }
420        if let Some(f) = self.fallback.as_ref() {
421            if previous_entry_printed {
422                dest.write_str(", ")?;
423            }
424            f.to_css(dest)?;
425        }
426        dest.write_str(")")
427    }
428}
429
430impl<Fallback> Parse for GenericAnchorSizeFunction<Fallback>
431where
432    Fallback: Parse,
433{
434    fn parse<'i, 't>(
435        context: &ParserContext,
436        input: &mut Parser<'i, 't>,
437    ) -> Result<Self, ParseError<'i>> {
438        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
439            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
440        }
441        input.expect_function_matching("anchor-size")?;
442        Self::parse_inner(context, input, |i| Fallback::parse(context, i))
443    }
444}
445impl<Fallback> GenericAnchorSizeFunction<Fallback> {
446    /// Is the anchor-size use valid for given property?
447    pub fn valid_for(&self, position_property: PositionProperty) -> bool {
448        position_property.is_absolutely_positioned()
449    }
450}
451
452/// Result of resolving an anchor function.
453pub enum AnchorResolutionResult<'a, LengthPercentage> {
454    /// Function resolved to a valid anchor.
455    Resolved(LengthPercentage),
456    /// Referenced anchor is invalid, but fallback is used.
457    Fallback(&'a LengthPercentage),
458    /// Referenced anchor is invalid.
459    Invalid,
460}
461
462impl<'a, LengthPercentage> AnchorResolutionResult<'a, LengthPercentage> {
463    /// Return result for an invalid anchor function, depending on if it has any fallback.
464    pub fn new_anchor_invalid(fallback: Option<&'a LengthPercentage>) -> Self {
465        if let Some(fb) = fallback {
466            return Self::Fallback(fb);
467        }
468        Self::Invalid
469    }
470}
471
472impl<LengthPercentage> GenericAnchorSizeFunction<LengthPercentage> {
473    /// Parse the inner part of `anchor-size()`, after the parser has consumed "anchor-size(".
474    pub fn parse_inner<'i, 't, F>(
475        context: &ParserContext,
476        input: &mut Parser<'i, 't>,
477        f: F,
478    ) -> Result<Self, ParseError<'i>>
479    where
480        F: FnOnce(&mut Parser<'i, '_>) -> Result<LengthPercentage, ParseError<'i>>,
481    {
482        input.parse_nested_block(|i| {
483            let mut target_element = i
484                .try_parse(|i| DashedIdent::parse(context, i))
485                .unwrap_or(DashedIdent::empty());
486            let size = i
487                .try_parse(AnchorSizeKeyword::parse)
488                .unwrap_or(AnchorSizeKeyword::None);
489            if target_element.is_empty() {
490                target_element = i
491                    .try_parse(|i| DashedIdent::parse(context, i))
492                    .unwrap_or(DashedIdent::empty());
493            }
494            let previous_parsed = !target_element.is_empty() || size != AnchorSizeKeyword::None;
495            let fallback = i
496                .try_parse(|i| {
497                    if previous_parsed {
498                        i.expect_comma()?;
499                    }
500                    f(i)
501                })
502                .ok();
503            Ok(GenericAnchorSizeFunction {
504                target_element,
505                size: size.into(),
506                fallback: fallback.into(),
507            })
508        })
509    }
510}
511
512/// Keyword values for the anchor size function.
513#[derive(
514    Animate,
515    Clone,
516    ComputeSquaredDistance,
517    Copy,
518    Debug,
519    MallocSizeOf,
520    PartialEq,
521    Parse,
522    SpecifiedValueInfo,
523    ToCss,
524    ToShmem,
525    ToAnimatedValue,
526    ToAnimatedZero,
527    ToComputedValue,
528    ToResolvedValue,
529    Serialize,
530    Deserialize,
531)]
532#[repr(u8)]
533pub enum AnchorSizeKeyword {
534    /// Magic value for nothing.
535    #[css(skip)]
536    None,
537    /// Width of the anchor element.
538    Width,
539    /// Height of the anchor element.
540    Height,
541    /// Block size of the anchor element.
542    Block,
543    /// Inline size of the anchor element.
544    Inline,
545    /// Same as `Block`, resolved against the positioned element's writing mode.
546    SelfBlock,
547    /// Same as `Inline`, resolved against the positioned element's writing mode.
548    SelfInline,
549}
550
551/// Specified type for `margin` properties, which allows
552/// the use of the `anchor-size()` function.
553#[derive(
554    Animate,
555    Clone,
556    ComputeSquaredDistance,
557    Debug,
558    MallocSizeOf,
559    PartialEq,
560    ToCss,
561    ToShmem,
562    ToAnimatedValue,
563    ToAnimatedZero,
564    ToComputedValue,
565    ToResolvedValue,
566    ToTyped,
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}