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