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