Skip to main content

style/values/specified/
position.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//! CSS handling for the specified value of
6//! [`position`][position]s
7//!
8//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
9
10use crate::derives::*;
11use crate::logical_geometry::{LogicalAxis, LogicalSide, PhysicalSide, WritingMode};
12use crate::parser::{Parse, ParserContext};
13use crate::selector_map::PrecomputedHashMap;
14use crate::str::HTML_SPACE_CHARACTERS;
15use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
16use crate::values::computed::{Context, Percentage, ToComputedValue};
17use crate::values::generics::length::GenericAnchorSizeFunction;
18use crate::values::generics::position::PositionComponent as GenericPositionComponent;
19use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
20use crate::values::generics::position::ZIndex as GenericZIndex;
21use crate::values::generics::position::{AspectRatio as GenericAspectRatio, GenericAnchorSide};
22use crate::values::generics::position::{GenericAnchorFunction, GenericInset, TreeScoped};
23use crate::values::generics::position::{IsTreeScoped, Position as GenericPosition};
24use crate::values::specified;
25use crate::values::specified::align::AlignFlags;
26use crate::values::specified::percentage::NoCalcPercentage;
27use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
28use crate::values::{AtomIdent, DashedIdent};
29use crate::Atom;
30use cssparser::{match_ignore_ascii_case, Parser};
31use num_traits::FromPrimitive;
32use selectors::parser::SelectorParseErrorKind;
33use servo_arc::Arc;
34use smallvec::{smallvec, SmallVec};
35use std::collections::hash_map::Entry;
36use std::fmt::{self, Write};
37use style_traits::arc_slice::ArcSlice;
38use style_traits::values::specified::AllowedNumericType;
39use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
40use thin_vec::ThinVec;
41
42/// The specified value of a CSS `<position>`
43pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
44
45/// The specified value of an `auto | <position>`.
46pub type PositionOrAuto = GenericPositionOrAuto<Position>;
47
48/// The specified value of a horizontal position.
49pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
50
51/// The specified value of a vertical position.
52pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;
53
54/// The specified value of a component of a CSS `<position>`.
55#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
56#[typed(todo_derive_fields)]
57pub enum PositionComponent<S> {
58    /// `center`
59    Center,
60    /// `<length-percentage>`
61    Length(LengthPercentage),
62    /// `<side> <length-percentage>?`
63    Side(S, Option<LengthPercentage>),
64}
65
66/// A keyword for the X direction.
67#[derive(
68    Clone,
69    Copy,
70    Debug,
71    Eq,
72    Hash,
73    MallocSizeOf,
74    Parse,
75    PartialEq,
76    SpecifiedValueInfo,
77    ToComputedValue,
78    ToCss,
79    ToResolvedValue,
80    ToShmem,
81)]
82#[allow(missing_docs)]
83#[repr(u8)]
84pub enum HorizontalPositionKeyword {
85    Left,
86    Right,
87}
88
89/// A keyword for the Y direction.
90#[derive(
91    Clone,
92    Copy,
93    Debug,
94    Eq,
95    Hash,
96    MallocSizeOf,
97    Parse,
98    PartialEq,
99    SpecifiedValueInfo,
100    ToComputedValue,
101    ToCss,
102    ToResolvedValue,
103    ToShmem,
104)]
105#[allow(missing_docs)]
106#[repr(u8)]
107pub enum VerticalPositionKeyword {
108    Top,
109    Bottom,
110}
111
112impl Parse for Position {
113    fn parse<'i, 't>(
114        context: &ParserContext,
115        input: &mut Parser<'i, 't>,
116    ) -> Result<Self, ParseError<'i>> {
117        let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?;
118        if position.is_three_value_syntax() {
119            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
120        }
121        Ok(position)
122    }
123}
124
125impl Position {
126    /// Parses a `<bg-position>`, with quirks.
127    pub fn parse_three_value_quirky<'i, 't>(
128        context: &ParserContext,
129        input: &mut Parser<'i, 't>,
130        allow_quirks: AllowQuirks,
131    ) -> Result<Self, ParseError<'i>> {
132        match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
133            Ok(x_pos @ PositionComponent::Center) => {
134                if let Ok(y_pos) =
135                    input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
136                {
137                    return Ok(Self::new(x_pos, y_pos));
138                }
139                let x_pos = input
140                    .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
141                    .unwrap_or(x_pos);
142                let y_pos = PositionComponent::Center;
143                return Ok(Self::new(x_pos, y_pos));
144            },
145            Ok(PositionComponent::Side(x_keyword, lp)) => {
146                if input
147                    .try_parse(|i| i.expect_ident_matching("center"))
148                    .is_ok()
149                {
150                    let x_pos = PositionComponent::Side(x_keyword, lp);
151                    let y_pos = PositionComponent::Center;
152                    return Ok(Self::new(x_pos, y_pos));
153                }
154                if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
155                    let y_lp = input
156                        .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
157                        .ok();
158                    let x_pos = PositionComponent::Side(x_keyword, lp);
159                    let y_pos = PositionComponent::Side(y_keyword, y_lp);
160                    return Ok(Self::new(x_pos, y_pos));
161                }
162                let x_pos = PositionComponent::Side(x_keyword, None);
163                let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length);
164                return Ok(Self::new(x_pos, y_pos));
165            },
166            Ok(x_pos @ PositionComponent::Length(_)) => {
167                if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
168                    let y_pos = PositionComponent::Side(y_keyword, None);
169                    return Ok(Self::new(x_pos, y_pos));
170                }
171                if let Ok(y_lp) =
172                    input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
173                {
174                    let y_pos = PositionComponent::Length(y_lp);
175                    return Ok(Self::new(x_pos, y_pos));
176                }
177                let y_pos = PositionComponent::Center;
178                let _ = input.try_parse(|i| i.expect_ident_matching("center"));
179                return Ok(Self::new(x_pos, y_pos));
180            },
181            Err(_) => {},
182        }
183        let y_keyword = VerticalPositionKeyword::parse(input)?;
184        let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| {
185            let y_lp = i
186                .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
187                .ok();
188            if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
189                let x_lp = i
190                    .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
191                    .ok();
192                let x_pos = PositionComponent::Side(x_keyword, x_lp);
193                return Ok((y_lp, x_pos));
194            };
195            i.expect_ident_matching("center")?;
196            let x_pos = PositionComponent::Center;
197            Ok((y_lp, x_pos))
198        });
199        if let Ok((y_lp, x_pos)) = lp_and_x_pos {
200            let y_pos = PositionComponent::Side(y_keyword, y_lp);
201            return Ok(Self::new(x_pos, y_pos));
202        }
203        let x_pos = PositionComponent::Center;
204        let y_pos = PositionComponent::Side(y_keyword, None);
205        Ok(Self::new(x_pos, y_pos))
206    }
207
208    /// `center center`
209    #[inline]
210    pub fn center() -> Self {
211        Self::new(PositionComponent::Center, PositionComponent::Center)
212    }
213
214    /// Returns true if this uses a 3 value syntax.
215    #[inline]
216    fn is_three_value_syntax(&self) -> bool {
217        self.horizontal.component_count() != self.vertical.component_count()
218    }
219}
220
221impl ToCss for Position {
222    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
223    where
224        W: Write,
225    {
226        match (&self.horizontal, &self.vertical) {
227            (
228                x_pos @ &PositionComponent::Side(_, Some(_)),
229                &PositionComponent::Length(ref y_lp),
230            ) => {
231                x_pos.to_css(dest)?;
232                dest.write_str(" top ")?;
233                y_lp.to_css(dest)
234            },
235            (
236                &PositionComponent::Length(ref x_lp),
237                y_pos @ &PositionComponent::Side(_, Some(_)),
238            ) => {
239                dest.write_str("left ")?;
240                x_lp.to_css(dest)?;
241                dest.write_char(' ')?;
242                y_pos.to_css(dest)
243            },
244            (x_pos, y_pos) => {
245                x_pos.to_css(dest)?;
246                dest.write_char(' ')?;
247                y_pos.to_css(dest)
248            },
249        }
250    }
251}
252
253impl<S: Parse> Parse for PositionComponent<S> {
254    fn parse<'i, 't>(
255        context: &ParserContext,
256        input: &mut Parser<'i, 't>,
257    ) -> Result<Self, ParseError<'i>> {
258        Self::parse_quirky(context, input, AllowQuirks::No)
259    }
260}
261
262impl<S: Parse> PositionComponent<S> {
263    /// Parses a component of a CSS position, with quirks.
264    pub fn parse_quirky<'i, 't>(
265        context: &ParserContext,
266        input: &mut Parser<'i, 't>,
267        allow_quirks: AllowQuirks,
268    ) -> Result<Self, ParseError<'i>> {
269        if input
270            .try_parse(|i| i.expect_ident_matching("center"))
271            .is_ok()
272        {
273            return Ok(PositionComponent::Center);
274        }
275        if let Ok(lp) =
276            input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
277        {
278            return Ok(PositionComponent::Length(lp));
279        }
280        let keyword = S::parse(context, input)?;
281        let lp = input
282            .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
283            .ok();
284        Ok(PositionComponent::Side(keyword, lp))
285    }
286}
287
288impl<S> GenericPositionComponent for PositionComponent<S> {
289    fn is_center(&self) -> bool {
290        match *self {
291            PositionComponent::Center => true,
292            PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.get() == 0.5,
293            // 50% from any side is still the center.
294            PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => {
295                per.get() == 0.5
296            },
297            _ => false,
298        }
299    }
300}
301
302impl<S> PositionComponent<S> {
303    /// `0%`
304    pub fn zero() -> Self {
305        PositionComponent::Length(LengthPercentage::Percentage(NoCalcPercentage::zero()))
306    }
307
308    /// Returns the count of this component.
309    fn component_count(&self) -> usize {
310        match *self {
311            PositionComponent::Length(..) | PositionComponent::Center => 1,
312            PositionComponent::Side(_, ref lp) => {
313                if lp.is_some() {
314                    2
315                } else {
316                    1
317                }
318            },
319        }
320    }
321}
322
323impl<S: Side> ToComputedValue for PositionComponent<S> {
324    type ComputedValue = ComputedLengthPercentage;
325
326    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
327        match *self {
328            PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)),
329            PositionComponent::Side(ref keyword, None) => {
330                let p = Percentage(if keyword.is_start() { 0. } else { 1. });
331                ComputedLengthPercentage::new_percent(p)
332            },
333            PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
334                let length = length.to_computed_value(context);
335                // We represent `<end-side> <length>` as `calc(100% - <length>)`.
336                ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All)
337            },
338            PositionComponent::Side(_, Some(ref length))
339            | PositionComponent::Length(ref length) => length.to_computed_value(context),
340        }
341    }
342
343    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
344        PositionComponent::Length(ToComputedValue::from_computed_value(computed))
345    }
346}
347
348impl<S: Side> PositionComponent<S> {
349    /// The initial specified value of a position component, i.e. the start side.
350    pub fn initial_specified_value() -> Self {
351        PositionComponent::Side(S::start(), None)
352    }
353}
354
355/// https://drafts.csswg.org/css-anchor-position-1/#propdef-anchor-name
356#[derive(
357    Animate,
358    Clone,
359    Debug,
360    MallocSizeOf,
361    PartialEq,
362    SpecifiedValueInfo,
363    ToComputedValue,
364    ToCss,
365    ToResolvedValue,
366    ToShmem,
367    ToTyped,
368)]
369#[css(comma)]
370#[repr(transparent)]
371#[typed(todo_derive_fields)]
372pub struct AnchorNameIdent(
373    #[css(iterable, if_empty = "none")]
374    #[ignore_malloc_size_of = "Arc"]
375    #[animation(constant)]
376    pub crate::ArcSlice<DashedIdent>,
377);
378
379impl AnchorNameIdent {
380    /// Return the `none` value.
381    pub fn none() -> Self {
382        Self(Default::default())
383    }
384}
385
386impl Parse for AnchorNameIdent {
387    fn parse<'i, 't>(
388        context: &ParserContext,
389        input: &mut Parser<'i, 't>,
390    ) -> Result<Self, ParseError<'i>> {
391        let location = input.current_source_location();
392        let first = input.expect_ident()?;
393        if first.eq_ignore_ascii_case("none") {
394            return Ok(Self::none());
395        }
396        // The common case is probably just to have a single anchor name, so
397        // space for four on the stack should be plenty.
398        let mut idents: SmallVec<[DashedIdent; 4]> =
399            smallvec![DashedIdent::from_ident(location, first,)?];
400        while input.try_parse(|input| input.expect_comma()).is_ok() {
401            idents.push(DashedIdent::parse(context, input)?);
402        }
403        Ok(AnchorNameIdent(ArcSlice::from_iter(idents.drain(..))))
404    }
405}
406
407impl IsTreeScoped for AnchorNameIdent {
408    fn is_tree_scoped(&self) -> bool {
409        !self.0.is_empty()
410    }
411}
412
413/// https://drafts.csswg.org/css-anchor-position-1/#propdef-anchor-name
414pub type AnchorName = TreeScoped<AnchorNameIdent>;
415
416impl AnchorName {
417    /// Return the `none` value.
418    pub fn none() -> Self {
419        Self::with_default_level(AnchorNameIdent::none())
420    }
421}
422
423/// List of scoped names, or none.
424#[derive(
425    Clone,
426    Debug,
427    MallocSizeOf,
428    PartialEq,
429    SpecifiedValueInfo,
430    ToComputedValue,
431    ToCss,
432    ToResolvedValue,
433    ToShmem,
434    ToTyped,
435)]
436#[repr(transparent)]
437#[css(comma)]
438#[typed(todo_derive_fields)]
439pub struct ScopedNameList(
440    /// `none | all | <dashed-ident>#`
441    #[css(iterable, if_empty = "none")]
442    #[ignore_malloc_size_of = "Arc"]
443    crate::ArcSlice<AtomIdent>,
444);
445
446impl ScopedNameList {
447    /// Return the `none` value.
448    pub fn none() -> Self {
449        Self(crate::ArcSlice::default())
450    }
451
452    /// Whether we're the `none` value.
453    pub fn is_none(&self) -> bool {
454        self.0.is_empty()
455    }
456
457    /// Return the `all` value.
458    pub fn all() -> Self {
459        static ALL: std::sync::LazyLock<ScopedNameList> = std::sync::LazyLock::new(|| {
460            ScopedNameList(crate::ArcSlice::from_iter_leaked(std::iter::once(
461                AtomIdent::new(atom!("all")),
462            )))
463        });
464        ALL.clone()
465    }
466}
467
468impl Parse for ScopedNameList {
469    fn parse<'i, 't>(
470        context: &ParserContext,
471        input: &mut Parser<'i, 't>,
472    ) -> Result<Self, ParseError<'i>> {
473        let location = input.current_source_location();
474        let first = input.expect_ident()?;
475        if first.eq_ignore_ascii_case("none") {
476            return Ok(Self::none());
477        }
478        if first.eq_ignore_ascii_case("all") {
479            return Ok(Self::all());
480        }
481        // Authors using more than a handful of anchored elements is likely
482        // uncommon, so we only pre-allocate for 8 on the stack here.
483        let mut idents = SmallVec::<[AtomIdent; 8]>::new();
484        idents.push(AtomIdent::new(DashedIdent::from_ident(location, first)?.0));
485        while input.try_parse(|input| input.expect_comma()).is_ok() {
486            idents.push(AtomIdent::new(DashedIdent::parse(context, input)?.0));
487        }
488        Ok(Self(ArcSlice::from_iter(idents.drain(..))))
489    }
490}
491
492impl IsTreeScoped for ScopedNameList {
493    fn is_tree_scoped(&self) -> bool {
494        !self.is_none()
495    }
496}
497
498/// A scoped name type, such as:
499/// * https://drafts.csswg.org/css-anchor-position-1/#propdef-scope
500pub type ScopedName = TreeScoped<ScopedNameList>;
501
502impl ScopedName {
503    /// Return the `none` value.
504    pub fn none() -> Self {
505        Self::with_default_level(ScopedNameList::none())
506    }
507
508    /// Returns true if no scoped name is specified.
509    pub fn is_none(&self) -> bool {
510        self.value.is_none()
511    }
512}
513
514/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-anchor
515#[derive(
516    Clone,
517    Debug,
518    MallocSizeOf,
519    Parse,
520    PartialEq,
521    SpecifiedValueInfo,
522    ToComputedValue,
523    ToCss,
524    ToResolvedValue,
525    ToShmem,
526    ToTyped,
527)]
528#[repr(u8)]
529#[typed(todo_derive_fields)]
530pub enum PositionAnchorKeyword {
531    /// `normal`
532    Normal,
533    /// `none`
534    None,
535    /// `auto`
536    Auto,
537    /// `<dashed-ident>`
538    Ident(DashedIdent),
539}
540
541impl IsTreeScoped for PositionAnchorKeyword {
542    fn is_tree_scoped(&self) -> bool {
543        match *self {
544            Self::Normal | Self::None | Self::Auto => false,
545            Self::Ident(_) => true,
546        }
547    }
548}
549
550/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-anchor
551pub type PositionAnchor = TreeScoped<PositionAnchorKeyword>;
552
553impl PositionAnchor {
554    /// Return the `normal` value.
555    pub fn normal() -> Self {
556        Self::with_default_level(PositionAnchorKeyword::Normal)
557    }
558}
559
560#[derive(
561    Clone,
562    Copy,
563    Debug,
564    Eq,
565    MallocSizeOf,
566    Parse,
567    PartialEq,
568    Serialize,
569    SpecifiedValueInfo,
570    ToComputedValue,
571    ToCss,
572    ToResolvedValue,
573    ToShmem,
574)]
575#[repr(u8)]
576/// How to swap values for the automatically-generated position tactic.
577pub enum PositionTryFallbacksTryTacticKeyword {
578    /// Swap the values in the block axis.
579    FlipBlock,
580    /// Swap the values in the inline axis.
581    FlipInline,
582    /// Swap the values in the start properties.
583    FlipStart,
584    /// Swap the values in the X axis.
585    FlipX,
586    /// Swap the values in the Y axis.
587    FlipY,
588}
589
590#[derive(
591    Clone,
592    Debug,
593    Default,
594    Eq,
595    MallocSizeOf,
596    PartialEq,
597    SpecifiedValueInfo,
598    ToComputedValue,
599    ToCss,
600    ToResolvedValue,
601    ToShmem,
602)]
603#[repr(transparent)]
604/// Changes for the automatically-generated position option.
605/// Note that this is order-dependent - e.g. `flip-start flip-inline` != `flip-inline flip-start`.
606///
607/// https://drafts.csswg.org/css-anchor-position-1/#typedef-position-try-fallbacks-try-tactic
608pub struct PositionTryFallbacksTryTactic(
609    #[css(iterable)] pub ThinVec<PositionTryFallbacksTryTacticKeyword>,
610);
611
612impl Parse for PositionTryFallbacksTryTactic {
613    fn parse<'i, 't>(
614        _context: &ParserContext,
615        input: &mut Parser<'i, 't>,
616    ) -> Result<Self, ParseError<'i>> {
617        let mut result = ThinVec::with_capacity(5);
618        // Collect up to 5 keywords, disallowing duplicates.
619        for _ in 0..5 {
620            if let Ok(kw) = input.try_parse(PositionTryFallbacksTryTacticKeyword::parse) {
621                if result.contains(&kw) {
622                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
623                }
624                result.push(kw);
625            } else {
626                break;
627            }
628        }
629        if result.is_empty() {
630            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
631        }
632        Ok(Self(result))
633    }
634}
635
636impl PositionTryFallbacksTryTactic {
637    /// Returns whether there's any tactic.
638    #[inline]
639    pub fn is_empty(&self) -> bool {
640        self.0.is_empty()
641    }
642
643    /// Iterates over the fallbacks in order.
644    #[inline]
645    pub fn iter(&self) -> impl Iterator<Item = &PositionTryFallbacksTryTacticKeyword> {
646        self.0.iter()
647    }
648}
649
650#[derive(
651    Clone,
652    Debug,
653    MallocSizeOf,
654    PartialEq,
655    SpecifiedValueInfo,
656    ToComputedValue,
657    ToCss,
658    ToResolvedValue,
659    ToShmem,
660)]
661#[repr(C)]
662/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-try-fallbacks
663/// <dashed-ident> || <try-tactic>
664pub struct DashedIdentAndOrTryTactic {
665    /// `<dashed-ident>`
666    pub ident: DashedIdent,
667    /// `<try-tactic>`
668    pub try_tactic: PositionTryFallbacksTryTactic,
669}
670
671impl Parse for DashedIdentAndOrTryTactic {
672    fn parse<'i, 't>(
673        context: &ParserContext,
674        input: &mut Parser<'i, 't>,
675    ) -> Result<Self, ParseError<'i>> {
676        let mut result = Self {
677            ident: DashedIdent::empty(),
678            try_tactic: PositionTryFallbacksTryTactic::default(),
679        };
680
681        loop {
682            if result.ident.is_empty() {
683                if let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i)) {
684                    result.ident = ident;
685                    continue;
686                }
687            }
688            if result.try_tactic.is_empty() {
689                if let Ok(try_tactic) =
690                    input.try_parse(|i| PositionTryFallbacksTryTactic::parse(context, i))
691                {
692                    result.try_tactic = try_tactic;
693                    continue;
694                }
695            }
696            break;
697        }
698
699        if result.ident.is_empty() && result.try_tactic.is_empty() {
700            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
701        }
702        return Ok(result);
703    }
704}
705
706#[derive(
707    Clone,
708    Debug,
709    MallocSizeOf,
710    Parse,
711    PartialEq,
712    SpecifiedValueInfo,
713    ToComputedValue,
714    ToCss,
715    ToResolvedValue,
716    ToShmem,
717)]
718#[repr(u8)]
719/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-try-fallbacks
720/// [ [<dashed-ident> || <try-tactic>] | <'position-area'> ]
721pub enum PositionTryFallbacksItem {
722    /// `<dashed-ident> || <try-tactic>`
723    IdentAndOrTactic(DashedIdentAndOrTryTactic),
724    #[parse(parse_fn = "PositionArea::parse_except_none")]
725    /// `<position-area>`
726    PositionArea(PositionArea),
727}
728
729#[derive(
730    Clone,
731    Debug,
732    Default,
733    MallocSizeOf,
734    PartialEq,
735    SpecifiedValueInfo,
736    ToComputedValue,
737    ToCss,
738    ToResolvedValue,
739    ToShmem,
740    ToTyped,
741)]
742#[css(comma)]
743#[repr(C)]
744#[typed(todo_derive_fields)]
745/// https://drafts.csswg.org/css-anchor-position-1/#position-try-fallbacks
746pub struct PositionTryFallbacksList(
747    #[css(iterable, if_empty = "none")]
748    #[ignore_malloc_size_of = "Arc"]
749    pub crate::ArcSlice<PositionTryFallbacksItem>,
750);
751
752impl IsTreeScoped for PositionTryFallbacksList {
753    fn is_tree_scoped(&self) -> bool {
754        !self.is_none()
755    }
756}
757
758impl PositionTryFallbacksList {
759    #[inline]
760    /// Return the `none` value.
761    pub fn none() -> Self {
762        Self(Default::default())
763    }
764
765    /// Returns whether this is the `none` value.
766    pub fn is_none(&self) -> bool {
767        self.0.is_empty()
768    }
769}
770
771impl Parse for PositionTryFallbacksList {
772    fn parse<'i, 't>(
773        context: &ParserContext,
774        input: &mut Parser<'i, 't>,
775    ) -> Result<Self, ParseError<'i>> {
776        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
777            return Ok(Self::none());
778        }
779        // The common case is unlikely to include many alternate positioning
780        // styles, so space for four on the stack should typically be enough.
781        let mut items: SmallVec<[PositionTryFallbacksItem; 4]> =
782            smallvec![PositionTryFallbacksItem::parse(context, input)?];
783        while input.try_parse(|input| input.expect_comma()).is_ok() {
784            items.push(PositionTryFallbacksItem::parse(context, input)?);
785        }
786        Ok(Self(ArcSlice::from_iter(items.drain(..))))
787    }
788}
789
790/// https://drafts.csswg.org/css-anchor-position-1/#position-try-fallbacks
791pub type PositionTryFallbacks = TreeScoped<PositionTryFallbacksList>;
792
793impl PositionTryFallbacks {
794    /// Returns the default value, `none`.
795    pub fn none() -> Self {
796        Self::with_default_level(PositionTryFallbacksList::none())
797    }
798}
799
800/// https://drafts.csswg.org/css-anchor-position-1/#position-try-order-property
801#[derive(
802    Clone,
803    Copy,
804    Debug,
805    Default,
806    Eq,
807    MallocSizeOf,
808    Parse,
809    PartialEq,
810    SpecifiedValueInfo,
811    ToComputedValue,
812    ToCss,
813    ToResolvedValue,
814    ToShmem,
815    ToTyped,
816)]
817#[repr(u8)]
818pub enum PositionTryOrder {
819    #[default]
820    /// `normal`
821    Normal,
822    /// `most-width`
823    MostWidth,
824    /// `most-height`
825    MostHeight,
826    /// `most-block-size`
827    MostBlockSize,
828    /// `most-inline-size`
829    MostInlineSize,
830}
831
832impl PositionTryOrder {
833    #[inline]
834    /// Return the `auto` value.
835    pub fn normal() -> Self {
836        Self::Normal
837    }
838
839    /// Returns whether this is the `auto` value.
840    pub fn is_normal(&self) -> bool {
841        *self == Self::Normal
842    }
843}
844
845#[derive(
846    Clone,
847    Copy,
848    Debug,
849    Eq,
850    MallocSizeOf,
851    Parse,
852    PartialEq,
853    Serialize,
854    SpecifiedValueInfo,
855    ToComputedValue,
856    ToCss,
857    ToResolvedValue,
858    ToShmem,
859    ToTyped,
860)]
861#[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))]
862#[repr(C)]
863/// Specified keyword values for the position-visibility property.
864pub struct PositionVisibility(u8);
865bitflags! {
866    impl PositionVisibility: u8 {
867        /// Element is displayed without regard for its anchors or its overflowing status.
868        const ALWAYS = 0;
869        /// anchors-valid
870        const ANCHORS_VALID = 1 << 0;
871        /// anchors-visible
872        const ANCHORS_VISIBLE = 1 << 1;
873        /// no-overflow
874        const NO_OVERFLOW = 1 << 2;
875    }
876}
877
878impl Default for PositionVisibility {
879    fn default() -> Self {
880        Self::ALWAYS
881    }
882}
883
884impl PositionVisibility {
885    #[inline]
886    /// Returns the initial value of position-visibility
887    pub fn always() -> Self {
888        Self::ALWAYS
889    }
890}
891
892/// A value indicating which high level group in the formal grammar a
893/// PositionAreaKeyword or PositionArea belongs to.
894#[repr(u8)]
895#[derive(Clone, Copy, Debug, Eq, PartialEq)]
896pub enum PositionAreaType {
897    /// X || Y
898    Physical,
899    /// block || inline
900    Logical,
901    /// self-block || self-inline
902    SelfLogical,
903    /// start|end|span-* {1,2}
904    Inferred,
905    /// self-start|self-end|span-self-* {1,2}
906    SelfInferred,
907    /// center, span-all
908    Common,
909    /// none
910    None,
911}
912
913/// A three-bit value that represents the axis in which position-area operates on.
914/// Represented as 4 bits: axis type (physical or logical), direction type (physical or logical),
915/// axis value.
916///
917/// There are two special values on top (Inferred and None) that represent ambiguous or axis-less
918/// keywords, respectively.
919#[repr(u8)]
920#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
921#[allow(missing_docs)]
922pub enum PositionAreaAxis {
923    Horizontal = 0b000,
924    Vertical = 0b001,
925
926    X = 0b010,
927    Y = 0b011,
928
929    Block = 0b110,
930    Inline = 0b111,
931
932    Inferred = 0b100,
933    None = 0b101,
934}
935
936impl PositionAreaAxis {
937    /// Whether this axis is physical or not.
938    pub fn is_physical(self) -> bool {
939        (self as u8 & 0b100) == 0
940    }
941
942    /// Whether the direction is logical or not.
943    fn is_flow_relative_direction(self) -> bool {
944        self == Self::Inferred || (self as u8 & 0b10) != 0
945    }
946
947    /// Whether this axis goes first in the canonical syntax.
948    fn is_canonically_first(self) -> bool {
949        self != Self::Inferred && (self as u8) & 1 == 0
950    }
951
952    #[allow(unused)]
953    fn flip(self) -> Self {
954        if matches!(self, Self::Inferred | Self::None) {
955            return self;
956        }
957        Self::from_u8(self as u8 ^ 1u8).unwrap()
958    }
959
960    fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> {
961        Some(match self {
962            PositionAreaAxis::Horizontal | PositionAreaAxis::X => {
963                if wm.is_vertical() {
964                    LogicalAxis::Block
965                } else {
966                    LogicalAxis::Inline
967                }
968            },
969            PositionAreaAxis::Vertical | PositionAreaAxis::Y => {
970                if wm.is_vertical() {
971                    LogicalAxis::Inline
972                } else {
973                    LogicalAxis::Block
974                }
975            },
976            PositionAreaAxis::Block => LogicalAxis::Block,
977            PositionAreaAxis::Inline => LogicalAxis::Inline,
978            PositionAreaAxis::Inferred => inferred,
979            PositionAreaAxis::None => return None,
980        })
981    }
982}
983
984/// Specifies which tracks(s) on the axis that the position-area span occupies.
985/// Represented as 3 bits: start, center, end track.
986#[repr(u8)]
987#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
988pub enum PositionAreaTrack {
989    /// First track
990    Start = 0b001,
991    /// First and center.
992    SpanStart = 0b011,
993    /// Last track.
994    End = 0b100,
995    /// Last and center.
996    SpanEnd = 0b110,
997    /// Center track.
998    Center = 0b010,
999    /// All tracks
1000    SpanAll = 0b111,
1001}
1002
1003impl PositionAreaTrack {
1004    fn flip(self) -> Self {
1005        match self {
1006            Self::Start => Self::End,
1007            Self::SpanStart => Self::SpanEnd,
1008            Self::End => Self::Start,
1009            Self::SpanEnd => Self::SpanStart,
1010            Self::Center | Self::SpanAll => self,
1011        }
1012    }
1013
1014    fn start(self) -> bool {
1015        self as u8 & 1 != 0
1016    }
1017}
1018
1019/// The shift to the left needed to set the axis.
1020pub const AXIS_SHIFT: usize = 3;
1021/// The mask used to extract the axis.
1022pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT;
1023/// The mask used to extract the track.
1024pub const TRACK_MASK: u8 = 0b111u8;
1025/// The self-wm bit.
1026pub const SELF_WM: u8 = 1u8 << 6;
1027
1028#[derive(
1029    Clone,
1030    Copy,
1031    Debug,
1032    Default,
1033    Eq,
1034    MallocSizeOf,
1035    Parse,
1036    PartialEq,
1037    SpecifiedValueInfo,
1038    ToComputedValue,
1039    ToCss,
1040    ToResolvedValue,
1041    ToShmem,
1042    FromPrimitive,
1043)]
1044#[allow(missing_docs)]
1045#[repr(u8)]
1046/// Possible values for the `position-area` property's keywords.
1047/// Represented by [0z xxx yyy], where z means "self wm resolution", xxxx is the axis (as in
1048/// PositionAreaAxis) and yyy is the PositionAreaTrack
1049/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area
1050pub enum PositionAreaKeyword {
1051    #[default]
1052    None = (PositionAreaAxis::None as u8) << AXIS_SHIFT,
1053
1054    // Common (shared) keywords:
1055    Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8,
1056    SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8,
1057
1058    // Inferred-axis edges:
1059    Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1060    End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1061    SpanStart =
1062        ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1063    SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1064
1065    // Purely physical edges:
1066    Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1067    Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1068    Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1069    Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1070
1071    // Flow-relative physical-axis edges:
1072    XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1073    XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1074    YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1075    YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1076
1077    // Logical edges:
1078    BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1079    BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1080    InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1081    InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1082
1083    // Composite values with Span:
1084    SpanLeft =
1085        ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1086    SpanRight =
1087        ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1088    SpanTop =
1089        ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1090    SpanBottom =
1091        ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1092
1093    // Flow-relative physical-axis edges:
1094    SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1095    SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1096    SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1097    SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1098
1099    // Logical edges:
1100    SpanBlockStart =
1101        ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1102    SpanBlockEnd =
1103        ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1104    SpanInlineStart =
1105        ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1106    SpanInlineEnd =
1107        ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1108
1109    // Values using the Self element's writing-mode:
1110    SelfStart = SELF_WM | (Self::Start as u8),
1111    SelfEnd = SELF_WM | (Self::End as u8),
1112    SpanSelfStart = SELF_WM | (Self::SpanStart as u8),
1113    SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8),
1114
1115    SelfXStart = SELF_WM | (Self::XStart as u8),
1116    SelfXEnd = SELF_WM | (Self::XEnd as u8),
1117    SelfYStart = SELF_WM | (Self::YStart as u8),
1118    SelfYEnd = SELF_WM | (Self::YEnd as u8),
1119    SelfBlockStart = SELF_WM | (Self::BlockStart as u8),
1120    SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8),
1121    SelfInlineStart = SELF_WM | (Self::InlineStart as u8),
1122    SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8),
1123
1124    SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8),
1125    SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8),
1126    SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8),
1127    SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8),
1128    SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8),
1129    SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8),
1130    SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8),
1131    SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8),
1132}
1133
1134impl PositionAreaKeyword {
1135    /// Returns the 'none' value.
1136    #[inline]
1137    pub fn none() -> Self {
1138        Self::None
1139    }
1140
1141    /// Returns true if this is the none keyword.
1142    pub fn is_none(&self) -> bool {
1143        *self == Self::None
1144    }
1145
1146    /// Whether we're one of the self-wm keywords.
1147    pub fn self_wm(self) -> bool {
1148        (self as u8 & SELF_WM) != 0
1149    }
1150
1151    /// Get this keyword's axis.
1152    pub fn axis(self) -> PositionAreaAxis {
1153        PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap()
1154    }
1155
1156    /// Returns this keyword but with the axis swapped by the argument.
1157    pub fn with_axis(self, axis: PositionAreaAxis) -> Self {
1158        Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap()
1159    }
1160
1161    /// If this keyword uses an inferred axis, replaces it.
1162    pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self {
1163        if self.axis() == PositionAreaAxis::Inferred {
1164            self.with_axis(axis)
1165        } else {
1166            self
1167        }
1168    }
1169
1170    /// Get this keyword's track, or None if we're the `None` keyword.
1171    pub fn track(self) -> Option<PositionAreaTrack> {
1172        let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK);
1173        debug_assert_eq!(
1174            result.is_none(),
1175            self.is_none(),
1176            "Only the none keyword has no track"
1177        );
1178        result
1179    }
1180
1181    fn group_type(self) -> PositionAreaType {
1182        let axis = self.axis();
1183        if axis == PositionAreaAxis::None {
1184            if self.is_none() {
1185                return PositionAreaType::None;
1186            }
1187            return PositionAreaType::Common;
1188        }
1189        if axis == PositionAreaAxis::Inferred {
1190            return if self.self_wm() {
1191                PositionAreaType::SelfInferred
1192            } else {
1193                PositionAreaType::Inferred
1194            };
1195        }
1196        if axis.is_physical() {
1197            return PositionAreaType::Physical;
1198        }
1199        if self.self_wm() {
1200            PositionAreaType::SelfLogical
1201        } else {
1202            PositionAreaType::Logical
1203        }
1204    }
1205
1206    fn to_physical(
1207        self,
1208        cb_wm: WritingMode,
1209        self_wm: WritingMode,
1210        inferred_axis: LogicalAxis,
1211    ) -> Self {
1212        let wm = if self.self_wm() { self_wm } else { cb_wm };
1213        let axis = self.axis();
1214        if !axis.is_flow_relative_direction() {
1215            return self;
1216        }
1217        let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else {
1218            return self;
1219        };
1220        let Some(track) = self.track() else {
1221            debug_assert!(false, "How did we end up with no track here? {self:?}");
1222            return self;
1223        };
1224        let start = track.start();
1225        let logical_side = match logical_axis {
1226            LogicalAxis::Block => {
1227                if start {
1228                    LogicalSide::BlockStart
1229                } else {
1230                    LogicalSide::BlockEnd
1231                }
1232            },
1233            LogicalAxis::Inline => {
1234                if start {
1235                    LogicalSide::InlineStart
1236                } else {
1237                    LogicalSide::InlineEnd
1238                }
1239            },
1240        };
1241        let physical_side = logical_side.to_physical(wm);
1242        let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left);
1243        let new_track = if physical_start != start {
1244            track.flip()
1245        } else {
1246            track
1247        };
1248        let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) {
1249            PositionAreaAxis::Vertical
1250        } else {
1251            PositionAreaAxis::Horizontal
1252        };
1253        Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap()
1254    }
1255
1256    fn flip_track(self) -> Self {
1257        let Some(old_track) = self.track() else {
1258            return self;
1259        };
1260        let new_track = old_track.flip();
1261        Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap()
1262    }
1263
1264    /// Returns a value for the self-alignment properties in order to resolve
1265    /// `normal`, in terms of the containing block's writing mode.
1266    ///
1267    /// Note that the caller must have converted the position-area to physical
1268    /// values.
1269    ///
1270    /// <https://drafts.csswg.org/css-anchor-position/#position-area-alignment>
1271    pub fn to_self_alignment(self, axis: LogicalAxis, cb_wm: &WritingMode) -> Option<AlignFlags> {
1272        let track = self.track()?;
1273        Some(match track {
1274            // "If the only the center track in an axis is selected, the default alignment in that axis is center."
1275            PositionAreaTrack::Center => AlignFlags::CENTER,
1276            // "If all three tracks are selected, the default alignment in that axis is anchor-center."
1277            PositionAreaTrack::SpanAll => AlignFlags::ANCHOR_CENTER,
1278            // "Otherwise, the default alignment in that axis is toward the non-specified side track: if it’s
1279            // specifying the “start” track of its axis, the default alignment in that axis is end; etc."
1280            _ => {
1281                debug_assert_eq!(self.group_type(), PositionAreaType::Physical);
1282                if axis == LogicalAxis::Inline {
1283                    // For the inline axis, map 'start' to 'end' unless the axis is inline-reversed,
1284                    // meaning that its logical flow is counter to physical coordinates and therefore
1285                    // physical 'start' already corresponds to logical 'end'.
1286                    if track.start() == cb_wm.intersects(WritingMode::INLINE_REVERSED) {
1287                        AlignFlags::START
1288                    } else {
1289                        AlignFlags::END
1290                    }
1291                } else {
1292                    // For the block axis, only vertical-rl has reversed flow and therefore
1293                    // does not map 'start' to 'end' here.
1294                    if track.start() == cb_wm.is_vertical_rl() {
1295                        AlignFlags::START
1296                    } else {
1297                        AlignFlags::END
1298                    }
1299                }
1300            },
1301        })
1302    }
1303}
1304
1305#[derive(
1306    Clone,
1307    Copy,
1308    Debug,
1309    Eq,
1310    MallocSizeOf,
1311    PartialEq,
1312    SpecifiedValueInfo,
1313    ToCss,
1314    ToResolvedValue,
1315    ToShmem,
1316    ToTyped,
1317)]
1318#[repr(C)]
1319#[typed(todo_derive_fields)]
1320/// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area
1321pub struct PositionArea {
1322    /// First keyword, if any.
1323    pub first: PositionAreaKeyword,
1324    /// Second keyword, if any.
1325    #[css(skip_if = "PositionAreaKeyword::is_none")]
1326    pub second: PositionAreaKeyword,
1327}
1328
1329impl PositionArea {
1330    /// Returns the none value.
1331    #[inline]
1332    pub fn none() -> Self {
1333        Self {
1334            first: PositionAreaKeyword::None,
1335            second: PositionAreaKeyword::None,
1336        }
1337    }
1338
1339    /// Returns whether we're the none value.
1340    #[inline]
1341    pub fn is_none(&self) -> bool {
1342        self.first.is_none()
1343    }
1344
1345    /// Parses a <position-area> without allowing `none`.
1346    pub fn parse_except_none<'i, 't>(
1347        context: &ParserContext,
1348        input: &mut Parser<'i, 't>,
1349    ) -> Result<Self, ParseError<'i>> {
1350        Self::parse_internal(context, input, /*allow_none*/ false)
1351    }
1352
1353    /// Get the high-level grammar group of this.
1354    pub fn get_type(&self) -> PositionAreaType {
1355        let first = self.first.group_type();
1356        let second = self.second.group_type();
1357        if matches!(second, PositionAreaType::None | PositionAreaType::Common) {
1358            return first;
1359        }
1360        if first == PositionAreaType::Common {
1361            return second;
1362        }
1363        if first != second {
1364            return PositionAreaType::None;
1365        }
1366        let first_axis = self.first.axis();
1367        if first_axis != PositionAreaAxis::Inferred
1368            && first_axis.is_canonically_first() == self.second.axis().is_canonically_first()
1369        {
1370            return PositionAreaType::None;
1371        }
1372        first
1373    }
1374
1375    fn parse_internal<'i, 't>(
1376        _: &ParserContext,
1377        input: &mut Parser<'i, 't>,
1378        allow_none: bool,
1379    ) -> Result<Self, ParseError<'i>> {
1380        let mut location = input.current_source_location();
1381        let mut first = PositionAreaKeyword::parse(input)?;
1382        if first.is_none() {
1383            if allow_none {
1384                return Ok(Self::none());
1385            }
1386            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1387        }
1388
1389        location = input.current_source_location();
1390        let second = input.try_parse(PositionAreaKeyword::parse);
1391        if let Ok(PositionAreaKeyword::None) = second {
1392            // `none` is only allowed as a single value
1393            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1394        }
1395        let mut second = second.unwrap_or(PositionAreaKeyword::None);
1396        if second.is_none() {
1397            // Either there was no second keyword and try_parse returned a
1398            // BasicParseErrorKind::EndOfInput, or else the second "keyword"
1399            // was invalid. We assume the former case here, and if it's the
1400            // latter case then our caller detects the error (try_parse will,
1401            // have rewound, leaving an unparsed token).
1402            return Ok(Self { first, second });
1403        }
1404
1405        let pair_type = Self { first, second }.get_type();
1406        if pair_type == PositionAreaType::None {
1407            // Mismatched types or what not.
1408            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1409        }
1410        // For types that have a canonical order, remove 'span-all' (the default behavior;
1411        // unnecessary for keyword pairs with a known order).
1412        if matches!(
1413            pair_type,
1414            PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical
1415        ) {
1416            if second == PositionAreaKeyword::SpanAll {
1417                // Span-all is the default behavior, so specifying `span-all` is
1418                // superfluous.
1419                second = PositionAreaKeyword::None;
1420            } else if first == PositionAreaKeyword::SpanAll {
1421                first = second;
1422                second = PositionAreaKeyword::None;
1423            }
1424        }
1425        if first == second {
1426            second = PositionAreaKeyword::None;
1427        }
1428        let mut result = Self { first, second };
1429        result.canonicalize_order();
1430        Ok(result)
1431    }
1432
1433    fn canonicalize_order(&mut self) {
1434        let first_axis = self.first.axis();
1435        if first_axis.is_canonically_first() || self.second.is_none() {
1436            return;
1437        }
1438        let second_axis = self.second.axis();
1439        if first_axis == second_axis {
1440            // Inferred or axis-less keywords.
1441            return;
1442        }
1443        if second_axis.is_canonically_first()
1444            || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred)
1445        {
1446            std::mem::swap(&mut self.first, &mut self.second);
1447        }
1448    }
1449
1450    fn make_missing_second_explicit(&mut self) {
1451        if !self.second.is_none() {
1452            return;
1453        }
1454        let axis = self.first.axis();
1455        if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) {
1456            self.second = self.first;
1457            return;
1458        }
1459        self.second = PositionAreaKeyword::SpanAll;
1460        if !axis.is_canonically_first() {
1461            std::mem::swap(&mut self.first, &mut self.second);
1462        }
1463    }
1464
1465    /// Turns this <position-area> value into a physical <position-area>.
1466    pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self {
1467        self.make_missing_second_explicit();
1468        // If both axes are None, to_physical and canonicalize_order are not useful.
1469        // The first value refers to the block axis, the second to the inline axis;
1470        // but as a physical type, they will be interpreted as the x- and y-axis
1471        // respectively, so if the writing mode is horizontal we need to swap the
1472        // values (block -> y, inline -> x).
1473        if self.first.axis() == PositionAreaAxis::None
1474            && self.second.axis() == PositionAreaAxis::None
1475            && !cb_wm.is_vertical()
1476        {
1477            std::mem::swap(&mut self.first, &mut self.second);
1478        } else {
1479            self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block);
1480            self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline);
1481            self.canonicalize_order();
1482        }
1483        self
1484    }
1485
1486    fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) {
1487        if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) {
1488            self.first = self.first.flip_track();
1489        } else {
1490            self.second = self.second.flip_track();
1491        }
1492    }
1493
1494    fn flip_start(&mut self) {
1495        self.first = self.first.with_axis(self.first.axis().flip());
1496        self.second = self.second.with_axis(self.second.axis().flip());
1497    }
1498
1499    /// Applies a try tactic to this `<position-area>` value.
1500    pub fn with_tactic(
1501        mut self,
1502        wm: WritingMode,
1503        tactic: PositionTryFallbacksTryTacticKeyword,
1504    ) -> Self {
1505        self.make_missing_second_explicit();
1506        let axis_to_flip = match tactic {
1507            PositionTryFallbacksTryTacticKeyword::FlipStart => {
1508                self.flip_start();
1509                return self;
1510            },
1511            PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block,
1512            PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline,
1513            PositionTryFallbacksTryTacticKeyword::FlipX => {
1514                if wm.is_horizontal() {
1515                    LogicalAxis::Inline
1516                } else {
1517                    LogicalAxis::Block
1518                }
1519            },
1520            PositionTryFallbacksTryTacticKeyword::FlipY => {
1521                if wm.is_vertical() {
1522                    LogicalAxis::Inline
1523                } else {
1524                    LogicalAxis::Block
1525                }
1526            },
1527        };
1528        self.flip_logical_axis(wm, axis_to_flip);
1529        self
1530    }
1531}
1532
1533impl Parse for PositionArea {
1534    fn parse<'i, 't>(
1535        context: &ParserContext,
1536        input: &mut Parser<'i, 't>,
1537    ) -> Result<Self, ParseError<'i>> {
1538        Self::parse_internal(context, input, /* allow_none = */ true)
1539    }
1540}
1541
1542/// Represents a side, either horizontal or vertical, of a CSS position.
1543pub trait Side {
1544    /// Returns the start side.
1545    fn start() -> Self;
1546
1547    /// Returns whether this side is the start side.
1548    fn is_start(&self) -> bool;
1549}
1550
1551impl Side for HorizontalPositionKeyword {
1552    #[inline]
1553    fn start() -> Self {
1554        HorizontalPositionKeyword::Left
1555    }
1556
1557    #[inline]
1558    fn is_start(&self) -> bool {
1559        *self == Self::start()
1560    }
1561}
1562
1563impl Side for VerticalPositionKeyword {
1564    #[inline]
1565    fn start() -> Self {
1566        VerticalPositionKeyword::Top
1567    }
1568
1569    #[inline]
1570    fn is_start(&self) -> bool {
1571        *self == Self::start()
1572    }
1573}
1574
1575/// Controls how the auto-placement algorithm works specifying exactly how auto-placed items
1576/// get flowed into the grid: [ row | column ] || dense
1577/// https://drafts.csswg.org/css-grid-2/#grid-auto-flow-property
1578#[derive(
1579    Clone,
1580    Copy,
1581    Debug,
1582    Eq,
1583    MallocSizeOf,
1584    Parse,
1585    PartialEq,
1586    SpecifiedValueInfo,
1587    ToComputedValue,
1588    ToResolvedValue,
1589    ToShmem,
1590    ToTyped,
1591)]
1592#[css(bitflags(
1593    mixed = "row,column,dense",
1594    validate_mixed = "Self::validate_and_simplify"
1595))]
1596#[repr(C)]
1597pub struct GridAutoFlow(u8);
1598bitflags! {
1599    impl GridAutoFlow: u8 {
1600        /// 'row' - mutually exclusive with 'column'
1601        const ROW = 1 << 0;
1602        /// 'column' - mutually exclusive with 'row'
1603        const COLUMN = 1 << 1;
1604        /// 'dense'
1605        const DENSE = 1 << 2;
1606    }
1607}
1608
1609impl GridAutoFlow {
1610    /// [ row | column ] || dense
1611    fn validate_and_simplify(&mut self) -> bool {
1612        if self.contains(Self::ROW | Self::COLUMN) {
1613            // row and column are mutually exclusive.
1614            return false;
1615        }
1616        if *self == Self::DENSE {
1617            // If there's no column, default to row.
1618            self.insert(Self::ROW);
1619        }
1620        true
1621    }
1622}
1623
1624impl ToCss for GridAutoFlow {
1625    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1626    where
1627        W: Write,
1628    {
1629        let dense = self.intersects(Self::DENSE);
1630        if self.intersects(Self::ROW) {
1631            return if dense {
1632                dest.write_str("dense")
1633            } else {
1634                dest.write_str("row")
1635            };
1636        }
1637        debug_assert!(self.intersects(Self::COLUMN));
1638        if dense {
1639            dest.write_str("column dense")
1640        } else {
1641            dest.write_str("column")
1642        }
1643    }
1644}
1645
1646#[repr(u8)]
1647#[derive(
1648    Clone,
1649    Copy,
1650    Debug,
1651    Eq,
1652    MallocSizeOf,
1653    PartialEq,
1654    SpecifiedValueInfo,
1655    ToComputedValue,
1656    ToCss,
1657    ToResolvedValue,
1658    ToShmem,
1659)]
1660/// Masonry auto-placement algorithm packing.
1661pub enum MasonryPlacement {
1662    /// Place the item in the track(s) with the smallest extent so far.
1663    Pack,
1664    /// Place the item after the last item, from start to end.
1665    Next,
1666}
1667
1668#[repr(u8)]
1669#[derive(
1670    Clone,
1671    Copy,
1672    Debug,
1673    Eq,
1674    MallocSizeOf,
1675    PartialEq,
1676    SpecifiedValueInfo,
1677    ToComputedValue,
1678    ToCss,
1679    ToResolvedValue,
1680    ToShmem,
1681)]
1682/// Masonry auto-placement algorithm item sorting option.
1683pub enum MasonryItemOrder {
1684    /// Place all items with a definite placement before auto-placed items.
1685    DefiniteFirst,
1686    /// Place items in `order-modified document order`.
1687    Ordered,
1688}
1689
1690#[derive(
1691    Clone,
1692    Copy,
1693    Debug,
1694    Eq,
1695    MallocSizeOf,
1696    PartialEq,
1697    SpecifiedValueInfo,
1698    ToComputedValue,
1699    ToCss,
1700    ToResolvedValue,
1701    ToShmem,
1702    ToTyped,
1703)]
1704#[repr(C)]
1705#[typed(todo_derive_fields)]
1706/// Controls how the Masonry layout algorithm works
1707/// specifying exactly how auto-placed items get flowed in the masonry axis.
1708pub struct MasonryAutoFlow {
1709    /// Specify how to pick a auto-placement track.
1710    #[css(contextual_skip_if = "is_pack_with_non_default_order")]
1711    pub placement: MasonryPlacement,
1712    /// Specify how to pick an item to place.
1713    #[css(skip_if = "is_item_order_definite_first")]
1714    pub order: MasonryItemOrder,
1715}
1716
1717#[inline]
1718fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
1719    *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
1720}
1721
1722#[inline]
1723fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
1724    *order == MasonryItemOrder::DefiniteFirst
1725}
1726
1727impl MasonryAutoFlow {
1728    #[inline]
1729    /// Get initial `masonry-auto-flow` value.
1730    pub fn initial() -> MasonryAutoFlow {
1731        MasonryAutoFlow {
1732            placement: MasonryPlacement::Pack,
1733            order: MasonryItemOrder::DefiniteFirst,
1734        }
1735    }
1736}
1737
1738impl Parse for MasonryAutoFlow {
1739    /// [ definite-first | ordered ] || [ pack | next ]
1740    fn parse<'i, 't>(
1741        _context: &ParserContext,
1742        input: &mut Parser<'i, 't>,
1743    ) -> Result<MasonryAutoFlow, ParseError<'i>> {
1744        let mut value = MasonryAutoFlow::initial();
1745        let mut got_placement = false;
1746        let mut got_order = false;
1747        while !input.is_exhausted() {
1748            let location = input.current_source_location();
1749            let ident = input.expect_ident()?;
1750            let success = match_ignore_ascii_case! { &ident,
1751                "pack" if !got_placement => {
1752                    got_placement = true;
1753                    true
1754                },
1755                "next" if !got_placement => {
1756                    value.placement = MasonryPlacement::Next;
1757                    got_placement = true;
1758                    true
1759                },
1760                "definite-first" if !got_order => {
1761                    got_order = true;
1762                    true
1763                },
1764                "ordered" if !got_order => {
1765                    value.order = MasonryItemOrder::Ordered;
1766                    got_order = true;
1767                    true
1768                },
1769                _ => false
1770            };
1771            if !success {
1772                return Err(location
1773                    .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
1774            }
1775        }
1776
1777        if got_placement || got_order {
1778            Ok(value)
1779        } else {
1780            Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1781        }
1782    }
1783}
1784
1785#[derive(
1786    Clone,
1787    Debug,
1788    MallocSizeOf,
1789    PartialEq,
1790    SpecifiedValueInfo,
1791    ToComputedValue,
1792    ToCss,
1793    ToResolvedValue,
1794    ToShmem,
1795)]
1796#[repr(C)]
1797/// https://drafts.csswg.org/css-grid/#named-grid-area
1798pub struct TemplateAreas {
1799    /// `named area` containing for each template area
1800    #[css(skip)]
1801    pub areas: crate::OwnedSlice<NamedArea>,
1802    /// The simplified CSS strings for serialization purpose.
1803    /// https://drafts.csswg.org/css-grid/#serialize-template
1804    // Note: We also use the length of `strings` when computing the explicit grid end line number
1805    // (i.e. row number).
1806    #[css(iterable)]
1807    pub strings: crate::OwnedSlice<crate::OwnedStr>,
1808    /// The number of columns of the grid.
1809    #[css(skip)]
1810    pub width: u32,
1811}
1812
1813/// Parser for grid template areas.
1814#[derive(Default)]
1815pub struct TemplateAreasParser {
1816    areas: Vec<NamedArea>,
1817    area_indices: PrecomputedHashMap<Atom, usize>,
1818    strings: Vec<crate::OwnedStr>,
1819    width: u32,
1820    row: u32,
1821}
1822
1823impl TemplateAreasParser {
1824    /// Parse a single string.
1825    pub fn try_parse_string<'i>(
1826        &mut self,
1827        input: &mut Parser<'i, '_>,
1828    ) -> Result<(), ParseError<'i>> {
1829        input.try_parse(|input| {
1830            self.parse_string(input.expect_string()?)
1831                .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1832        })
1833    }
1834
1835    /// Parse a single string.
1836    fn parse_string(&mut self, string: &str) -> Result<(), ()> {
1837        self.row += 1;
1838        let mut simplified_string = String::new();
1839        let mut current_area_index: Option<usize> = None;
1840        let mut column = 0u32;
1841        for token in TemplateAreasTokenizer(string) {
1842            column += 1;
1843            if column > 1 {
1844                simplified_string.push(' ');
1845            }
1846            let name = if let Some(token) = token? {
1847                simplified_string.push_str(token);
1848                Atom::from(token)
1849            } else {
1850                if let Some(index) = current_area_index.take() {
1851                    if self.areas[index].columns.end != column {
1852                        return Err(());
1853                    }
1854                }
1855                simplified_string.push('.');
1856                continue;
1857            };
1858            if let Some(index) = current_area_index {
1859                if self.areas[index].name == name {
1860                    if self.areas[index].rows.start == self.row {
1861                        self.areas[index].columns.end += 1;
1862                    }
1863                    continue;
1864                }
1865                if self.areas[index].columns.end != column {
1866                    return Err(());
1867                }
1868            }
1869            match self.area_indices.entry(name) {
1870                Entry::Occupied(ref e) => {
1871                    let index = *e.get();
1872                    if self.areas[index].columns.start != column
1873                        || self.areas[index].rows.end != self.row
1874                    {
1875                        return Err(());
1876                    }
1877                    self.areas[index].rows.end += 1;
1878                    current_area_index = Some(index);
1879                },
1880                Entry::Vacant(v) => {
1881                    let index = self.areas.len();
1882                    let name = v.key().clone();
1883                    v.insert(index);
1884                    self.areas.push(NamedArea {
1885                        name,
1886                        columns: UnsignedRange {
1887                            start: column,
1888                            end: column + 1,
1889                        },
1890                        rows: UnsignedRange {
1891                            start: self.row,
1892                            end: self.row + 1,
1893                        },
1894                    });
1895                    current_area_index = Some(index);
1896                },
1897            }
1898        }
1899        if column == 0 {
1900            // Each string must produce a valid token.
1901            // https://github.com/w3c/csswg-drafts/issues/5110
1902            return Err(());
1903        }
1904        if let Some(index) = current_area_index {
1905            if self.areas[index].columns.end != column + 1 {
1906                debug_assert_ne!(self.areas[index].rows.start, self.row);
1907                return Err(());
1908            }
1909        }
1910        if self.row == 1 {
1911            self.width = column;
1912        } else if self.width != column {
1913            return Err(());
1914        }
1915
1916        self.strings.push(simplified_string.into());
1917        Ok(())
1918    }
1919
1920    /// Return the parsed template areas.
1921    pub fn finish(self) -> Result<TemplateAreas, ()> {
1922        if self.strings.is_empty() {
1923            return Err(());
1924        }
1925        Ok(TemplateAreas {
1926            areas: self.areas.into(),
1927            strings: self.strings.into(),
1928            width: self.width,
1929        })
1930    }
1931}
1932
1933impl TemplateAreas {
1934    fn parse_internal(input: &mut Parser) -> Result<Self, ()> {
1935        let mut parser = TemplateAreasParser::default();
1936        while parser.try_parse_string(input).is_ok() {}
1937        parser.finish()
1938    }
1939}
1940
1941impl Parse for TemplateAreas {
1942    fn parse<'i, 't>(
1943        _: &ParserContext,
1944        input: &mut Parser<'i, 't>,
1945    ) -> Result<Self, ParseError<'i>> {
1946        Self::parse_internal(input)
1947            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1948    }
1949}
1950
1951/// Arc type for `Arc<TemplateAreas>`
1952#[derive(
1953    Clone,
1954    Debug,
1955    MallocSizeOf,
1956    PartialEq,
1957    SpecifiedValueInfo,
1958    ToComputedValue,
1959    ToCss,
1960    ToResolvedValue,
1961    ToShmem,
1962)]
1963#[repr(transparent)]
1964pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
1965
1966impl Parse for TemplateAreasArc {
1967    fn parse<'i, 't>(
1968        context: &ParserContext,
1969        input: &mut Parser<'i, 't>,
1970    ) -> Result<Self, ParseError<'i>> {
1971        let parsed = TemplateAreas::parse(context, input)?;
1972        Ok(TemplateAreasArc(Arc::new(parsed)))
1973    }
1974}
1975
1976/// A range of rows or columns. Using this instead of std::ops::Range for FFI
1977/// purposes.
1978#[repr(C)]
1979#[derive(
1980    Clone,
1981    Debug,
1982    MallocSizeOf,
1983    PartialEq,
1984    SpecifiedValueInfo,
1985    ToComputedValue,
1986    ToResolvedValue,
1987    ToShmem,
1988)]
1989pub struct UnsignedRange {
1990    /// The start of the range.
1991    pub start: u32,
1992    /// The end of the range.
1993    pub end: u32,
1994}
1995
1996#[derive(
1997    Clone,
1998    Debug,
1999    MallocSizeOf,
2000    PartialEq,
2001    SpecifiedValueInfo,
2002    ToComputedValue,
2003    ToResolvedValue,
2004    ToShmem,
2005)]
2006#[repr(C)]
2007/// Not associated with any particular grid item, but can be referenced from the
2008/// grid-placement properties.
2009pub struct NamedArea {
2010    /// Name of the `named area`
2011    pub name: Atom,
2012    /// Rows of the `named area`
2013    pub rows: UnsignedRange,
2014    /// Columns of the `named area`
2015    pub columns: UnsignedRange,
2016}
2017
2018/// Tokenize the string into a list of the tokens,
2019/// using longest-match semantics
2020struct TemplateAreasTokenizer<'a>(&'a str);
2021
2022impl<'a> Iterator for TemplateAreasTokenizer<'a> {
2023    type Item = Result<Option<&'a str>, ()>;
2024
2025    fn next(&mut self) -> Option<Self::Item> {
2026        let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
2027        if rest.is_empty() {
2028            return None;
2029        }
2030        if rest.starts_with('.') {
2031            self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
2032            return Some(Ok(None));
2033        }
2034        if !rest.starts_with(is_name_code_point) {
2035            return Some(Err(()));
2036        }
2037        let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
2038        let token = &rest[..token_len];
2039        self.0 = &rest[token_len..];
2040        Some(Ok(Some(token)))
2041    }
2042}
2043
2044fn is_name_code_point(c: char) -> bool {
2045    c >= 'A' && c <= 'Z'
2046        || c >= 'a' && c <= 'z'
2047        || c >= '\u{80}'
2048        || c == '_'
2049        || c >= '0' && c <= '9'
2050        || c == '-'
2051}
2052
2053/// This property specifies named grid areas.
2054///
2055/// The syntax of this property also provides a visualization of the structure
2056/// of the grid, making the overall layout of the grid container easier to
2057/// understand.
2058#[repr(C, u8)]
2059#[derive(
2060    Clone,
2061    Debug,
2062    MallocSizeOf,
2063    Parse,
2064    PartialEq,
2065    SpecifiedValueInfo,
2066    ToComputedValue,
2067    ToCss,
2068    ToResolvedValue,
2069    ToShmem,
2070    ToTyped,
2071)]
2072#[typed(todo_derive_fields)]
2073pub enum GridTemplateAreas {
2074    /// The `none` value.
2075    None,
2076    /// The actual value.
2077    Areas(TemplateAreasArc),
2078}
2079
2080impl GridTemplateAreas {
2081    #[inline]
2082    /// Get default value as `none`
2083    pub fn none() -> GridTemplateAreas {
2084        GridTemplateAreas::None
2085    }
2086}
2087
2088/// A specified value for the `z-index` property.
2089pub type ZIndex = GenericZIndex<Integer>;
2090
2091/// A specified value for the `aspect-ratio` property.
2092pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
2093
2094impl Parse for AspectRatio {
2095    fn parse<'i, 't>(
2096        context: &ParserContext,
2097        input: &mut Parser<'i, 't>,
2098    ) -> Result<Self, ParseError<'i>> {
2099        use crate::values::generics::position::PreferredRatio;
2100        use crate::values::specified::Ratio;
2101
2102        let location = input.current_source_location();
2103        let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2104        let ratio = input.try_parse(|i| Ratio::parse(context, i));
2105        if auto.is_err() {
2106            auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2107        }
2108
2109        if auto.is_err() && ratio.is_err() {
2110            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
2111        }
2112
2113        Ok(AspectRatio {
2114            auto: auto.is_ok(),
2115            ratio: match ratio {
2116                Ok(ratio) => PreferredRatio::Ratio(ratio),
2117                Err(..) => PreferredRatio::None,
2118            },
2119        })
2120    }
2121}
2122
2123impl AspectRatio {
2124    /// Returns Self by a valid ratio.
2125    pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
2126        use crate::values::generics::position::PreferredRatio;
2127        use crate::values::generics::ratio::Ratio;
2128        AspectRatio {
2129            auto: true,
2130            ratio: PreferredRatio::Ratio(Ratio(
2131                NonNegativeNumber::new(w),
2132                NonNegativeNumber::new(h),
2133            )),
2134        }
2135    }
2136}
2137
2138/// A specified value for inset types.
2139pub type Inset = GenericInset<specified::Percentage, LengthPercentage>;
2140
2141impl Inset {
2142    /// Parses an inset type, allowing the unitless length quirk.
2143    /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk>
2144    #[inline]
2145    pub fn parse_quirky<'i, 't>(
2146        context: &ParserContext,
2147        input: &mut Parser<'i, 't>,
2148        allow_quirks: AllowQuirks,
2149    ) -> Result<Self, ParseError<'i>> {
2150        if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
2151        {
2152            return Ok(Self::LengthPercentage(l));
2153        }
2154        match input.try_parse(|i| i.expect_ident_matching("auto")) {
2155            Ok(_) => return Ok(Self::Auto),
2156            Err(e) if !static_prefs::pref!("layout.css.anchor-positioning.enabled") => {
2157                return Err(e.into());
2158            },
2159            Err(_) => (),
2160        };
2161        Self::parse_anchor_functions_quirky(context, input, allow_quirks)
2162    }
2163
2164    fn parse_as_anchor_function_fallback<'i, 't>(
2165        context: &ParserContext,
2166        input: &mut Parser<'i, 't>,
2167    ) -> Result<Self, ParseError<'i>> {
2168        if let Ok(l) =
2169            input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No))
2170        {
2171            return Ok(Self::LengthPercentage(l));
2172        }
2173        Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No)
2174    }
2175
2176    fn parse_anchor_functions_quirky<'i, 't>(
2177        context: &ParserContext,
2178        input: &mut Parser<'i, 't>,
2179        allow_quirks: AllowQuirks,
2180    ) -> Result<Self, ParseError<'i>> {
2181        debug_assert!(
2182            static_prefs::pref!("layout.css.anchor-positioning.enabled"),
2183            "How are we parsing with pref off?"
2184        );
2185        if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) {
2186            return Ok(Self::AnchorFunction(Box::new(inner)));
2187        }
2188        if let Ok(inner) =
2189            input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i))
2190        {
2191            return Ok(Self::AnchorSizeFunction(Box::new(inner)));
2192        }
2193        Ok(Self::AnchorContainingCalcFunction(input.try_parse(
2194            |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks),
2195        )?))
2196    }
2197}
2198
2199impl Parse for Inset {
2200    fn parse<'i, 't>(
2201        context: &ParserContext,
2202        input: &mut Parser<'i, 't>,
2203    ) -> Result<Self, ParseError<'i>> {
2204        Self::parse_quirky(context, input, AllowQuirks::No)
2205    }
2206}
2207
2208/// A specified value for `anchor()` function.
2209pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>;
2210
2211impl Parse for AnchorFunction {
2212    fn parse<'i, 't>(
2213        context: &ParserContext,
2214        input: &mut Parser<'i, 't>,
2215    ) -> Result<Self, ParseError<'i>> {
2216        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
2217            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
2218        }
2219        input.expect_function_matching("anchor")?;
2220        input.parse_nested_block(|i| {
2221            let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
2222            let side = GenericAnchorSide::parse(context, i)?;
2223            let target_element = if target_element.is_none() {
2224                i.try_parse(|i| DashedIdent::parse(context, i)).ok()
2225            } else {
2226                target_element
2227            };
2228            let fallback = i
2229                .try_parse(|i| {
2230                    i.expect_comma()?;
2231                    Inset::parse_as_anchor_function_fallback(context, i)
2232                })
2233                .ok();
2234            Ok(Self {
2235                target_element: TreeScoped::with_default_level(
2236                    target_element.unwrap_or_else(DashedIdent::empty),
2237                ),
2238                side,
2239                fallback: fallback.into(),
2240            })
2241        })
2242    }
2243}