style/values/specified/
align.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//! Values for CSS Box Alignment properties
6//!
7//! https://drafts.csswg.org/css-align/
8
9use crate::parser::{Parse, ParserContext};
10use cssparser::Parser;
11use std::fmt::{self, Write};
12use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, ToCss};
13
14/// Constants shared by multiple CSS Box Alignment properties
15#[derive(
16    Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
17)]
18#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
19#[repr(C)]
20pub struct AlignFlags(u8);
21bitflags! {
22    impl AlignFlags: u8 {
23        // Enumeration stored in the lower 5 bits:
24        /// {align,justify}-{content,items,self}: 'auto'
25        const AUTO = 0;
26        /// 'normal'
27        const NORMAL = 1;
28        /// 'start'
29        const START = 2;
30        /// 'end'
31        const END = 3;
32        /// 'flex-start'
33        const FLEX_START = 4;
34        /// 'flex-end'
35        const FLEX_END = 5;
36        /// 'center'
37        const CENTER = 6;
38        /// 'left'
39        const LEFT = 7;
40        /// 'right'
41        const RIGHT = 8;
42        /// 'baseline'
43        const BASELINE = 9;
44        /// 'last-baseline'
45        const LAST_BASELINE = 10;
46        /// 'stretch'
47        const STRETCH = 11;
48        /// 'self-start'
49        const SELF_START = 12;
50        /// 'self-end'
51        const SELF_END = 13;
52        /// 'space-between'
53        const SPACE_BETWEEN = 14;
54        /// 'space-around'
55        const SPACE_AROUND = 15;
56        /// 'space-evenly'
57        const SPACE_EVENLY = 16;
58        /// `anchor-center`
59        const ANCHOR_CENTER = 17;
60
61        // Additional flags stored in the upper bits:
62        /// 'legacy' (mutually exclusive w. SAFE & UNSAFE)
63        const LEGACY = 1 << 5;
64        /// 'safe'
65        const SAFE = 1 << 6;
66        /// 'unsafe' (mutually exclusive w. SAFE)
67        const UNSAFE = 1 << 7;
68
69        /// Mask for the additional flags above.
70        const FLAG_BITS = 0b11100000;
71    }
72}
73
74impl AlignFlags {
75    /// Returns the enumeration value stored in the lower 5 bits.
76    #[inline]
77    pub fn value(&self) -> Self {
78        *self & !AlignFlags::FLAG_BITS
79    }
80
81    /// Returns the flags stored in the upper 3 bits.
82    #[inline]
83    pub fn flags(&self) -> Self {
84        *self & AlignFlags::FLAG_BITS
85    }
86}
87
88impl ToCss for AlignFlags {
89    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
90    where
91        W: Write,
92    {
93        let extra_flags = *self & AlignFlags::FLAG_BITS;
94        let value = self.value();
95
96        match extra_flags {
97            AlignFlags::LEGACY => {
98                dest.write_str("legacy")?;
99                if value.is_empty() {
100                    return Ok(());
101                }
102                dest.write_char(' ')?;
103            },
104            AlignFlags::SAFE => dest.write_str("safe ")?,
105            AlignFlags::UNSAFE => dest.write_str("unsafe ")?,
106            _ => {
107                debug_assert_eq!(extra_flags, AlignFlags::empty());
108            },
109        }
110
111        dest.write_str(match value {
112            AlignFlags::AUTO => "auto",
113            AlignFlags::NORMAL => "normal",
114            AlignFlags::START => "start",
115            AlignFlags::END => "end",
116            AlignFlags::FLEX_START => "flex-start",
117            AlignFlags::FLEX_END => "flex-end",
118            AlignFlags::CENTER => "center",
119            AlignFlags::LEFT => "left",
120            AlignFlags::RIGHT => "right",
121            AlignFlags::BASELINE => "baseline",
122            AlignFlags::LAST_BASELINE => "last baseline",
123            AlignFlags::STRETCH => "stretch",
124            AlignFlags::SELF_START => "self-start",
125            AlignFlags::SELF_END => "self-end",
126            AlignFlags::SPACE_BETWEEN => "space-between",
127            AlignFlags::SPACE_AROUND => "space-around",
128            AlignFlags::SPACE_EVENLY => "space-evenly",
129            AlignFlags::ANCHOR_CENTER => "anchor-center",
130            _ => unreachable!(),
131        })
132    }
133}
134
135/// An axis direction, either inline (for the `justify` properties) or block,
136/// (for the `align` properties).
137#[derive(Clone, Copy, PartialEq)]
138pub enum AxisDirection {
139    /// Block direction.
140    Block,
141    /// Inline direction.
142    Inline,
143}
144
145/// Shared value for the `align-content` and `justify-content` properties.
146///
147/// <https://drafts.csswg.org/css-align/#content-distribution>
148#[derive(
149    Clone,
150    Copy,
151    Debug,
152    Eq,
153    MallocSizeOf,
154    PartialEq,
155    ToComputedValue,
156    ToCss,
157    ToResolvedValue,
158    ToShmem,
159)]
160#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
161#[repr(C)]
162pub struct ContentDistribution {
163    primary: AlignFlags,
164    // FIXME(https://github.com/w3c/csswg-drafts/issues/1002): This will need to
165    // accept fallback alignment, eventually.
166}
167
168impl ContentDistribution {
169    /// The initial value 'normal'
170    #[inline]
171    pub fn normal() -> Self {
172        Self::new(AlignFlags::NORMAL)
173    }
174
175    /// `start`
176    #[inline]
177    pub fn start() -> Self {
178        Self::new(AlignFlags::START)
179    }
180
181    /// The initial value 'normal'
182    #[inline]
183    pub fn new(primary: AlignFlags) -> Self {
184        Self { primary }
185    }
186
187    /// Returns whether this value is a <baseline-position>.
188    pub fn is_baseline_position(&self) -> bool {
189        matches!(
190            self.primary.value(),
191            AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
192        )
193    }
194
195    /// The primary alignment
196    #[inline]
197    pub fn primary(self) -> AlignFlags {
198        self.primary
199    }
200
201    /// Parse a value for align-content / justify-content.
202    pub fn parse<'i, 't>(
203        input: &mut Parser<'i, 't>,
204        axis: AxisDirection,
205    ) -> Result<Self, ParseError<'i>> {
206        // NOTE Please also update the `list_keywords` function below
207        //      when this function is updated.
208
209        // Try to parse normal first
210        if input
211            .try_parse(|i| i.expect_ident_matching("normal"))
212            .is_ok()
213        {
214            return Ok(ContentDistribution::normal());
215        }
216
217        // Parse <baseline-position>, but only on the block axis.
218        if axis == AxisDirection::Block {
219            if let Ok(value) = input.try_parse(parse_baseline) {
220                return Ok(ContentDistribution::new(value));
221            }
222        }
223
224        // <content-distribution>
225        if let Ok(value) = input.try_parse(parse_content_distribution) {
226            return Ok(ContentDistribution::new(value));
227        }
228
229        // <overflow-position>? <content-position>
230        let overflow_position = input
231            .try_parse(parse_overflow_position)
232            .unwrap_or(AlignFlags::empty());
233
234        let content_position = try_match_ident_ignore_ascii_case! { input,
235            "start" => AlignFlags::START,
236            "end" => AlignFlags::END,
237            "flex-start" => AlignFlags::FLEX_START,
238            "flex-end" => AlignFlags::FLEX_END,
239            "center" => AlignFlags::CENTER,
240            "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
241            "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
242        };
243
244        Ok(ContentDistribution::new(
245            content_position | overflow_position,
246        ))
247    }
248
249    fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
250        f(&["normal"]);
251        if axis == AxisDirection::Block {
252            list_baseline_keywords(f);
253        }
254        list_content_distribution_keywords(f);
255        list_overflow_position_keywords(f);
256        f(&["start", "end", "flex-start", "flex-end", "center"]);
257        if axis == AxisDirection::Inline {
258            f(&["left", "right"]);
259        }
260    }
261}
262
263/// Value for the `align-content` property.
264///
265/// <https://drafts.csswg.org/css-align/#propdef-align-content>
266#[derive(
267    Clone,
268    Copy,
269    Debug,
270    Eq,
271    MallocSizeOf,
272    PartialEq,
273    ToComputedValue,
274    ToCss,
275    ToResolvedValue,
276    ToShmem,
277)]
278#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
279#[repr(transparent)]
280pub struct AlignContent(pub ContentDistribution);
281
282impl Parse for AlignContent {
283    fn parse<'i, 't>(
284        _: &ParserContext,
285        input: &mut Parser<'i, 't>,
286    ) -> Result<Self, ParseError<'i>> {
287        // NOTE Please also update `impl SpecifiedValueInfo` below when
288        //      this function is updated.
289        Ok(AlignContent(ContentDistribution::parse(
290            input,
291            AxisDirection::Block,
292        )?))
293    }
294}
295
296impl SpecifiedValueInfo for AlignContent {
297    fn collect_completion_keywords(f: KeywordsCollectFn) {
298        ContentDistribution::list_keywords(f, AxisDirection::Block);
299    }
300}
301
302/// Value for the `justify-content` property.
303///
304/// <https://drafts.csswg.org/css-align/#propdef-justify-content>
305#[derive(
306    Clone,
307    Copy,
308    Debug,
309    Eq,
310    MallocSizeOf,
311    PartialEq,
312    ToComputedValue,
313    ToCss,
314    ToResolvedValue,
315    ToShmem,
316)]
317#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
318#[repr(transparent)]
319pub struct JustifyContent(pub ContentDistribution);
320
321impl Parse for JustifyContent {
322    fn parse<'i, 't>(
323        _: &ParserContext,
324        input: &mut Parser<'i, 't>,
325    ) -> Result<Self, ParseError<'i>> {
326        // NOTE Please also update `impl SpecifiedValueInfo` below when
327        //      this function is updated.
328        Ok(JustifyContent(ContentDistribution::parse(
329            input,
330            AxisDirection::Inline,
331        )?))
332    }
333}
334
335impl SpecifiedValueInfo for JustifyContent {
336    fn collect_completion_keywords(f: KeywordsCollectFn) {
337        ContentDistribution::list_keywords(f, AxisDirection::Inline);
338    }
339}
340
341/// <https://drafts.csswg.org/css-align/#self-alignment>
342#[derive(
343    Clone,
344    Copy,
345    Debug,
346    Eq,
347    MallocSizeOf,
348    PartialEq,
349    ToComputedValue,
350    ToCss,
351    ToResolvedValue,
352    ToShmem,
353)]
354#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
355#[repr(transparent)]
356pub struct SelfAlignment(pub AlignFlags);
357
358impl SelfAlignment {
359    /// The initial value 'auto'
360    #[inline]
361    pub fn auto() -> Self {
362        SelfAlignment(AlignFlags::AUTO)
363    }
364
365    /// Returns whether this value is valid for both axis directions.
366    pub fn is_valid_on_both_axes(&self) -> bool {
367        match self.0.value() {
368            // left | right are only allowed on the inline axis.
369            AlignFlags::LEFT | AlignFlags::RIGHT => false,
370
371            _ => true,
372        }
373    }
374
375    /// Parse a self-alignment value on one of the axis.
376    pub fn parse<'i, 't>(
377        input: &mut Parser<'i, 't>,
378        axis: AxisDirection,
379    ) -> Result<Self, ParseError<'i>> {
380        // NOTE Please also update the `list_keywords` function below
381        //      when this function is updated.
382
383        // <baseline-position>
384        //
385        // It's weird that this accepts <baseline-position>, but not
386        // justify-content...
387        if let Ok(value) = input.try_parse(parse_baseline) {
388            return Ok(SelfAlignment(value));
389        }
390
391        // auto | normal | stretch
392        if let Ok(value) = input.try_parse(parse_auto_normal_stretch) {
393            return Ok(SelfAlignment(value));
394        }
395
396        // <overflow-position>? <self-position>
397        let overflow_position = input
398            .try_parse(parse_overflow_position)
399            .unwrap_or(AlignFlags::empty());
400        let self_position = parse_self_position(input, axis)?;
401        Ok(SelfAlignment(overflow_position | self_position))
402    }
403
404    fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
405        list_baseline_keywords(f);
406        list_auto_normal_stretch(f);
407        list_overflow_position_keywords(f);
408        list_self_position_keywords(f, axis);
409    }
410}
411
412/// The specified value of the align-self property.
413///
414/// <https://drafts.csswg.org/css-align/#propdef-align-self>
415#[derive(
416    Clone,
417    Copy,
418    Debug,
419    Eq,
420    MallocSizeOf,
421    PartialEq,
422    ToComputedValue,
423    ToCss,
424    ToResolvedValue,
425    ToShmem,
426)]
427#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
428#[repr(C)]
429pub struct AlignSelf(pub SelfAlignment);
430
431impl Parse for AlignSelf {
432    fn parse<'i, 't>(
433        _: &ParserContext,
434        input: &mut Parser<'i, 't>,
435    ) -> Result<Self, ParseError<'i>> {
436        // NOTE Please also update `impl SpecifiedValueInfo` below when
437        //      this function is updated.
438        Ok(AlignSelf(SelfAlignment::parse(
439            input,
440            AxisDirection::Block,
441        )?))
442    }
443}
444
445impl SpecifiedValueInfo for AlignSelf {
446    fn collect_completion_keywords(f: KeywordsCollectFn) {
447        SelfAlignment::list_keywords(f, AxisDirection::Block);
448    }
449}
450
451/// The specified value of the justify-self property.
452///
453/// <https://drafts.csswg.org/css-align/#propdef-justify-self>
454#[derive(
455    Clone,
456    Copy,
457    Debug,
458    Eq,
459    MallocSizeOf,
460    PartialEq,
461    ToComputedValue,
462    ToCss,
463    ToResolvedValue,
464    ToShmem,
465)]
466#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
467#[repr(C)]
468pub struct JustifySelf(pub SelfAlignment);
469
470impl Parse for JustifySelf {
471    fn parse<'i, 't>(
472        _: &ParserContext,
473        input: &mut Parser<'i, 't>,
474    ) -> Result<Self, ParseError<'i>> {
475        // NOTE Please also update `impl SpecifiedValueInfo` below when
476        //      this function is updated.
477        Ok(JustifySelf(SelfAlignment::parse(
478            input,
479            AxisDirection::Inline,
480        )?))
481    }
482}
483
484impl SpecifiedValueInfo for JustifySelf {
485    fn collect_completion_keywords(f: KeywordsCollectFn) {
486        SelfAlignment::list_keywords(f, AxisDirection::Inline);
487    }
488}
489
490/// Value of the `align-items` property
491///
492/// <https://drafts.csswg.org/css-align/#propdef-align-items>
493#[derive(
494    Clone,
495    Copy,
496    Debug,
497    Eq,
498    MallocSizeOf,
499    PartialEq,
500    ToComputedValue,
501    ToCss,
502    ToResolvedValue,
503    ToShmem,
504)]
505#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
506#[repr(C)]
507pub struct AlignItems(pub AlignFlags);
508
509impl AlignItems {
510    /// The initial value 'normal'
511    #[inline]
512    pub fn normal() -> Self {
513        AlignItems(AlignFlags::NORMAL)
514    }
515}
516
517impl Parse for AlignItems {
518    // normal | stretch | <baseline-position> |
519    // <overflow-position>? <self-position>
520    fn parse<'i, 't>(
521        _: &ParserContext,
522        input: &mut Parser<'i, 't>,
523    ) -> Result<Self, ParseError<'i>> {
524        // NOTE Please also update `impl SpecifiedValueInfo` below when
525        //      this function is updated.
526
527        // <baseline-position>
528        if let Ok(baseline) = input.try_parse(parse_baseline) {
529            return Ok(AlignItems(baseline));
530        }
531
532        // normal | stretch
533        if let Ok(value) = input.try_parse(parse_normal_stretch) {
534            return Ok(AlignItems(value));
535        }
536        // <overflow-position>? <self-position>
537        let overflow = input
538            .try_parse(parse_overflow_position)
539            .unwrap_or(AlignFlags::empty());
540        let self_position = parse_self_position(input, AxisDirection::Block)?;
541        Ok(AlignItems(self_position | overflow))
542    }
543}
544
545impl SpecifiedValueInfo for AlignItems {
546    fn collect_completion_keywords(f: KeywordsCollectFn) {
547        list_baseline_keywords(f);
548        list_normal_stretch(f);
549        list_overflow_position_keywords(f);
550        list_self_position_keywords(f, AxisDirection::Block);
551    }
552}
553
554/// Value of the `justify-items` property
555///
556/// <https://drafts.csswg.org/css-align/#justify-items-property>
557#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)]
558#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
559#[repr(C)]
560pub struct JustifyItems(pub AlignFlags);
561
562impl JustifyItems {
563    /// The initial value 'legacy'
564    #[inline]
565    pub fn legacy() -> Self {
566        JustifyItems(AlignFlags::LEGACY)
567    }
568
569    /// The value 'normal'
570    #[inline]
571    pub fn normal() -> Self {
572        JustifyItems(AlignFlags::NORMAL)
573    }
574}
575
576impl Parse for JustifyItems {
577    fn parse<'i, 't>(
578        _: &ParserContext,
579        input: &mut Parser<'i, 't>,
580    ) -> Result<Self, ParseError<'i>> {
581        // NOTE Please also update `impl SpecifiedValueInfo` below when
582        //      this function is updated.
583
584        // <baseline-position>
585        //
586        // It's weird that this accepts <baseline-position>, but not
587        // justify-content...
588        if let Ok(baseline) = input.try_parse(parse_baseline) {
589            return Ok(JustifyItems(baseline));
590        }
591
592        // normal | stretch
593        if let Ok(value) = input.try_parse(parse_normal_stretch) {
594            return Ok(JustifyItems(value));
595        }
596
597        // legacy | [ legacy && [ left | right | center ] ]
598        if let Ok(value) = input.try_parse(parse_legacy) {
599            return Ok(JustifyItems(value));
600        }
601
602        // <overflow-position>? <self-position>
603        let overflow = input
604            .try_parse(parse_overflow_position)
605            .unwrap_or(AlignFlags::empty());
606        let self_position = parse_self_position(input, AxisDirection::Inline)?;
607        Ok(JustifyItems(overflow | self_position))
608    }
609}
610
611impl SpecifiedValueInfo for JustifyItems {
612    fn collect_completion_keywords(f: KeywordsCollectFn) {
613        list_baseline_keywords(f);
614        list_normal_stretch(f);
615        list_legacy_keywords(f);
616        list_overflow_position_keywords(f);
617        list_self_position_keywords(f, AxisDirection::Inline);
618    }
619}
620
621// auto | normal | stretch
622fn parse_auto_normal_stretch<'i, 't>(
623    input: &mut Parser<'i, 't>,
624) -> Result<AlignFlags, ParseError<'i>> {
625    // NOTE Please also update the `list_auto_normal_stretch` function
626    //      below when this function is updated.
627    try_match_ident_ignore_ascii_case! { input,
628        "auto" => Ok(AlignFlags::AUTO),
629        "normal" => Ok(AlignFlags::NORMAL),
630        "stretch" => Ok(AlignFlags::STRETCH),
631    }
632}
633
634fn list_auto_normal_stretch(f: KeywordsCollectFn) {
635    f(&["auto", "normal", "stretch"]);
636}
637
638// normal | stretch
639fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
640    // NOTE Please also update the `list_normal_stretch` function below
641    //      when this function is updated.
642    try_match_ident_ignore_ascii_case! { input,
643        "normal" => Ok(AlignFlags::NORMAL),
644        "stretch" => Ok(AlignFlags::STRETCH),
645    }
646}
647
648fn list_normal_stretch(f: KeywordsCollectFn) {
649    f(&["normal", "stretch"]);
650}
651
652// <baseline-position>
653fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
654    // NOTE Please also update the `list_baseline_keywords` function
655    //      below when this function is updated.
656    try_match_ident_ignore_ascii_case! { input,
657        "baseline" => Ok(AlignFlags::BASELINE),
658        "first" => {
659            input.expect_ident_matching("baseline")?;
660            Ok(AlignFlags::BASELINE)
661        },
662        "last" => {
663            input.expect_ident_matching("baseline")?;
664            Ok(AlignFlags::LAST_BASELINE)
665        },
666    }
667}
668
669fn list_baseline_keywords(f: KeywordsCollectFn) {
670    f(&["baseline", "first baseline", "last baseline"]);
671}
672
673// <content-distribution>
674fn parse_content_distribution<'i, 't>(
675    input: &mut Parser<'i, 't>,
676) -> Result<AlignFlags, ParseError<'i>> {
677    // NOTE Please also update the `list_content_distribution_keywords`
678    //      function below when this function is updated.
679    try_match_ident_ignore_ascii_case! { input,
680        "stretch" => Ok(AlignFlags::STRETCH),
681        "space-between" => Ok(AlignFlags::SPACE_BETWEEN),
682        "space-around" => Ok(AlignFlags::SPACE_AROUND),
683        "space-evenly" => Ok(AlignFlags::SPACE_EVENLY),
684    }
685}
686
687fn list_content_distribution_keywords(f: KeywordsCollectFn) {
688    f(&["stretch", "space-between", "space-around", "space-evenly"]);
689}
690
691// <overflow-position>
692fn parse_overflow_position<'i, 't>(
693    input: &mut Parser<'i, 't>,
694) -> Result<AlignFlags, ParseError<'i>> {
695    // NOTE Please also update the `list_overflow_position_keywords`
696    //      function below when this function is updated.
697    try_match_ident_ignore_ascii_case! { input,
698        "safe" => Ok(AlignFlags::SAFE),
699        "unsafe" => Ok(AlignFlags::UNSAFE),
700    }
701}
702
703fn list_overflow_position_keywords(f: KeywordsCollectFn) {
704    f(&["safe", "unsafe"]);
705}
706
707// <self-position> | left | right in the inline axis.
708fn parse_self_position<'i, 't>(
709    input: &mut Parser<'i, 't>,
710    axis: AxisDirection,
711) -> Result<AlignFlags, ParseError<'i>> {
712    // NOTE Please also update the `list_self_position_keywords`
713    //      function below when this function is updated.
714    Ok(try_match_ident_ignore_ascii_case! { input,
715        "start" => AlignFlags::START,
716        "end" => AlignFlags::END,
717        "flex-start" => AlignFlags::FLEX_START,
718        "flex-end" => AlignFlags::FLEX_END,
719        "center" => AlignFlags::CENTER,
720        "self-start" => AlignFlags::SELF_START,
721        "self-end" => AlignFlags::SELF_END,
722        "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
723        "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
724        "anchor-center" if static_prefs::pref!("layout.css.anchor-positioning.enabled") => AlignFlags::ANCHOR_CENTER,
725    })
726}
727
728fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
729    f(&[
730        "start",
731        "end",
732        "flex-start",
733        "flex-end",
734        "center",
735        "self-start",
736        "self-end",
737    ]);
738
739    if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
740        f(&["anchor-center"]);
741    }
742
743    if axis == AxisDirection::Inline {
744        f(&["left", "right"]);
745    }
746}
747
748fn parse_left_right_center<'i, 't>(
749    input: &mut Parser<'i, 't>,
750) -> Result<AlignFlags, ParseError<'i>> {
751    // NOTE Please also update the `list_legacy_keywords` function below
752    //      when this function is updated.
753    Ok(try_match_ident_ignore_ascii_case! { input,
754        "left" => AlignFlags::LEFT,
755        "right" => AlignFlags::RIGHT,
756        "center" => AlignFlags::CENTER,
757    })
758}
759
760// legacy | [ legacy && [ left | right | center ] ]
761fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
762    // NOTE Please also update the `list_legacy_keywords` function below
763    //      when this function is updated.
764    let flags = try_match_ident_ignore_ascii_case! { input,
765        "legacy" => {
766            let flags = input.try_parse(parse_left_right_center)
767                .unwrap_or(AlignFlags::empty());
768
769            return Ok(AlignFlags::LEGACY | flags)
770        },
771        "left" => AlignFlags::LEFT,
772        "right" => AlignFlags::RIGHT,
773        "center" => AlignFlags::CENTER,
774    };
775
776    input.expect_ident_matching("legacy")?;
777    Ok(AlignFlags::LEGACY | flags)
778}
779
780fn list_legacy_keywords(f: KeywordsCollectFn) {
781    f(&["legacy", "left", "right", "center"]);
782}