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