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