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