Skip to main content

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