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