Skip to main content

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