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