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    Deserialize,
33    MallocSizeOf,
34    PartialEq,
35    Serialize,
36    SpecifiedValueInfo,
37    ToAnimatedValue,
38    ToAnimatedZero,
39    ToComputedValue,
40    ToCss,
41    ToResolvedValue,
42    ToShmem,
43    ToTyped,
44)]
45#[repr(C, u8)]
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    ToTyped,
398)]
399#[repr(C)]
400#[typed(todo_derive_fields)]
401pub struct GenericAnchorSizeFunction<Fallback> {
402    /// Anchor name of the element to anchor to.
403    /// If omitted (i.e. empty), selects the implicit anchor element.
404    #[animation(constant)]
405    pub target_element: TreeScoped<DashedIdent>,
406    /// Size of the positioned element, expressed in that of the anchor element.
407    /// If omitted, defaults to the axis of the property the function is used in.
408    pub size: AnchorSizeKeyword,
409    /// Value to use in case the anchor function is invalid.
410    pub fallback: Optional<Fallback>,
411}
412
413impl<Fallback: TryTacticAdjustment> TryTacticAdjustment for GenericAnchorSizeFunction<Fallback> {
414    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
415        self.size.try_tactic_adjustment(old_side, new_side);
416        if let Some(fallback) = self.fallback.as_mut() {
417            fallback.try_tactic_adjustment(old_side, new_side);
418        }
419    }
420}
421
422impl<Fallback> ToCss for GenericAnchorSizeFunction<Fallback>
423where
424    Fallback: ToCss,
425{
426    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> std::fmt::Result
427    where
428        W: Write,
429    {
430        dest.write_str("anchor-size(")?;
431        let mut previous_entry_printed = false;
432        if !self.target_element.value.0.is_empty() {
433            previous_entry_printed = true;
434            self.target_element.to_css(dest)?;
435        }
436        if self.size != AnchorSizeKeyword::None {
437            if previous_entry_printed {
438                dest.write_str(" ")?;
439            }
440            previous_entry_printed = true;
441            self.size.to_css(dest)?;
442        }
443        if let Some(f) = self.fallback.as_ref() {
444            if previous_entry_printed {
445                dest.write_str(", ")?;
446            }
447            f.to_css(dest)?;
448        }
449        dest.write_str(")")
450    }
451}
452
453impl<Fallback> Parse for GenericAnchorSizeFunction<Fallback>
454where
455    Fallback: Parse,
456{
457    fn parse<'i, 't>(
458        context: &ParserContext,
459        input: &mut Parser<'i, 't>,
460    ) -> Result<Self, ParseError<'i>> {
461        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
462            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
463        }
464        input.expect_function_matching("anchor-size")?;
465        Self::parse_inner(context, input, |i| Fallback::parse(context, i))
466    }
467}
468impl<Fallback> GenericAnchorSizeFunction<Fallback> {
469    /// Is the anchor-size use valid for given property?
470    pub fn valid_for(&self, position_property: PositionProperty) -> bool {
471        position_property.is_absolutely_positioned()
472    }
473}
474
475/// Result of resolving an anchor function.
476pub enum AnchorResolutionResult<'a, LengthPercentage> {
477    /// Function resolved to a valid anchor.
478    Resolved(LengthPercentage),
479    /// Referenced anchor is invalid, but fallback is used.
480    Fallback(&'a LengthPercentage),
481    /// Referenced anchor is invalid.
482    Invalid,
483}
484
485impl<'a, LengthPercentage> AnchorResolutionResult<'a, LengthPercentage> {
486    /// Return result for an invalid anchor function, depending on if it has any fallback.
487    pub fn new_anchor_invalid(fallback: Option<&'a LengthPercentage>) -> Self {
488        if let Some(fb) = fallback {
489            return Self::Fallback(fb);
490        }
491        Self::Invalid
492    }
493}
494
495impl<LengthPercentage> GenericAnchorSizeFunction<LengthPercentage> {
496    /// Parse the inner part of `anchor-size()`, after the parser has consumed "anchor-size(".
497    pub fn parse_inner<'i, 't, F>(
498        context: &ParserContext,
499        input: &mut Parser<'i, 't>,
500        f: F,
501    ) -> Result<Self, ParseError<'i>>
502    where
503        F: FnOnce(&mut Parser<'i, '_>) -> Result<LengthPercentage, ParseError<'i>>,
504    {
505        input.parse_nested_block(|i| {
506            let mut target_element = i
507                .try_parse(|i| DashedIdent::parse(context, i))
508                .unwrap_or(DashedIdent::empty());
509            let size = i
510                .try_parse(AnchorSizeKeyword::parse)
511                .unwrap_or(AnchorSizeKeyword::None);
512            if target_element.is_empty() {
513                target_element = i
514                    .try_parse(|i| DashedIdent::parse(context, i))
515                    .unwrap_or(DashedIdent::empty());
516            }
517            let previous_parsed = !target_element.is_empty() || size != AnchorSizeKeyword::None;
518            let fallback = i
519                .try_parse(|i| {
520                    if previous_parsed {
521                        i.expect_comma()?;
522                    }
523                    f(i)
524                })
525                .ok();
526            Ok(GenericAnchorSizeFunction {
527                target_element: TreeScoped::with_default_level(target_element),
528                size: size.into(),
529                fallback: fallback.into(),
530            })
531        })
532    }
533}
534
535/// Keyword values for the anchor size function.
536#[derive(
537    Animate,
538    Clone,
539    ComputeSquaredDistance,
540    Copy,
541    Debug,
542    MallocSizeOf,
543    PartialEq,
544    Parse,
545    SpecifiedValueInfo,
546    ToCss,
547    ToShmem,
548    ToAnimatedValue,
549    ToAnimatedZero,
550    ToComputedValue,
551    ToResolvedValue,
552    Serialize,
553    Deserialize,
554)]
555#[repr(u8)]
556pub enum AnchorSizeKeyword {
557    /// Magic value for nothing.
558    #[css(skip)]
559    None,
560    /// Width of the anchor element.
561    Width,
562    /// Height of the anchor element.
563    Height,
564    /// Block size of the anchor element.
565    Block,
566    /// Inline size of the anchor element.
567    Inline,
568    /// Same as `Block`, resolved against the positioned element's writing mode.
569    SelfBlock,
570    /// Same as `Inline`, resolved against the positioned element's writing mode.
571    SelfInline,
572}
573
574impl TryTacticAdjustment for AnchorSizeKeyword {
575    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
576        if old_side.parallel_to(new_side) {
577            return;
578        }
579        *self = match *self {
580            Self::None => Self::None,
581            Self::Width => Self::Height,
582            Self::Height => Self::Width,
583            Self::Block => Self::Inline,
584            Self::Inline => Self::Block,
585            Self::SelfBlock => Self::SelfInline,
586            Self::SelfInline => Self::SelfBlock,
587        }
588    }
589}
590
591/// Specified type for `margin` properties, which allows
592/// the use of the `anchor-size()` function.
593#[derive(
594    Animate,
595    Clone,
596    ComputeSquaredDistance,
597    Debug,
598    MallocSizeOf,
599    PartialEq,
600    ToCss,
601    ToShmem,
602    ToAnimatedValue,
603    ToAnimatedZero,
604    ToComputedValue,
605    ToResolvedValue,
606    ToTyped,
607)]
608#[repr(C)]
609pub enum GenericMargin<LP> {
610    /// A `<length-percentage>` value.
611    LengthPercentage(LP),
612    /// An `auto` value.
613    Auto,
614    /// Margin size defined by the anchor element.
615    ///
616    /// https://drafts.csswg.org/css-anchor-position-1/#funcdef-anchor-size
617    AnchorSizeFunction(Box<GenericAnchorSizeFunction<Self>>),
618    /// A `<length-percentage>` value, guaranteed to contain `calc()`,
619    /// which then is guaranteed to contain `anchor()` or `anchor-size()`.
620    AnchorContainingCalcFunction(LP),
621}
622
623#[cfg(feature = "servo")]
624impl<LP> GenericMargin<LP> {
625    /// Return true if it is 'auto'.
626    #[inline]
627    pub fn is_auto(&self) -> bool {
628        matches!(self, Self::Auto)
629    }
630}
631
632impl<LP> SpecifiedValueInfo for GenericMargin<LP>
633where
634    LP: SpecifiedValueInfo,
635{
636    fn collect_completion_keywords(f: style_traits::KeywordsCollectFn) {
637        LP::collect_completion_keywords(f);
638        f(&["auto"]);
639        if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
640            f(&["anchor-size"]);
641        }
642    }
643}
644
645impl<LP> Zero for GenericMargin<LP>
646where
647    LP: Zero,
648{
649    fn is_zero(&self) -> bool {
650        match self {
651            Self::LengthPercentage(l) => l.is_zero(),
652            Self::Auto | Self::AnchorSizeFunction(_) | Self::AnchorContainingCalcFunction(_) => {
653                false
654            },
655        }
656    }
657
658    fn zero() -> Self {
659        Self::LengthPercentage(LP::zero())
660    }
661}