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(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
223        Self::parse(input, AxisDirection::Block)
224    }
225
226    /// Parse a value for justify-content
227    pub fn parse_inline(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
228        Self::parse(input, AxisDirection::Inline)
229    }
230
231    fn parse(input: &mut Parser, axis: AxisDirection) -> Result<Self, ParseError> {
232        // NOTE Please also update the `list_keywords` function below
233        //      when this function is updated.
234
235        // Try to parse normal first
236        if input
237            .try_parse(|i| i.expect_ident_matching("normal"))
238            .is_ok()
239        {
240            return Ok(ContentDistribution::normal());
241        }
242
243        // Parse <baseline-position>, but only on the block axis.
244        if axis == AxisDirection::Block {
245            if let Ok(value) = input.try_parse(parse_baseline) {
246                return Ok(ContentDistribution::new(value));
247            }
248        }
249
250        // <content-distribution>
251        if let Ok(value) = input.try_parse(parse_content_distribution) {
252            return Ok(ContentDistribution::new(value));
253        }
254
255        // <overflow-position>? <content-position>
256        let overflow_position = input
257            .try_parse(parse_overflow_position)
258            .unwrap_or(AlignFlags::empty());
259
260        let content_position = try_match_ident_ignore_ascii_case! { input,
261            "start" => AlignFlags::START,
262            "end" => AlignFlags::END,
263            "flex-start" => AlignFlags::FLEX_START,
264            "flex-end" => AlignFlags::FLEX_END,
265            "center" => AlignFlags::CENTER,
266            "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
267            "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
268        };
269
270        Ok(ContentDistribution::new(
271            content_position | overflow_position,
272        ))
273    }
274}
275
276impl SpecifiedValueInfo for ContentDistribution {
277    fn collect_completion_keywords(f: KeywordsCollectFn) {
278        f(&["normal"]);
279        list_baseline_keywords(f); // block-axis only
280        list_content_distribution_keywords(f);
281        list_overflow_position_keywords(f);
282        f(&["start", "end", "flex-start", "flex-end", "center"]);
283        f(&["left", "right"]); // inline-axis only
284    }
285}
286
287/// The specified value of the {align,justify}-self properties.
288///
289/// <https://drafts.csswg.org/css-align/#self-alignment>
290/// <https://drafts.csswg.org/css-align/#propdef-align-self>
291#[derive(
292    Clone,
293    Copy,
294    Debug,
295    Deref,
296    Deserialize,
297    Eq,
298    MallocSizeOf,
299    PartialEq,
300    Serialize,
301    ToComputedValue,
302    ToCss,
303    ToResolvedValue,
304    ToShmem,
305    ToTyped,
306)]
307#[repr(C)]
308#[typed(todo_derive_fields)]
309pub struct SelfAlignment(pub AlignFlags);
310
311impl SelfAlignment {
312    /// The initial value 'auto'
313    #[inline]
314    pub fn auto() -> Self {
315        SelfAlignment(AlignFlags::AUTO)
316    }
317
318    /// Returns whether this value is valid for both axis directions.
319    pub fn is_valid_on_both_axes(&self) -> bool {
320        match self.0.value() {
321            // left | right are only allowed on the inline axis.
322            AlignFlags::LEFT | AlignFlags::RIGHT => false,
323
324            _ => true,
325        }
326    }
327
328    /// Parse self-alignment on the block axis (for align-self)
329    pub fn parse_block(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
330        Self::parse(input, AxisDirection::Block)
331    }
332
333    /// Parse self-alignment on the block axis (for align-self)
334    pub fn parse_inline(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
335        Self::parse(input, AxisDirection::Inline)
336    }
337
338    /// Parse a self-alignment value on one of the axes.
339    fn parse(input: &mut Parser, axis: AxisDirection) -> Result<Self, ParseError> {
340        // NOTE Please also update the `list_keywords` function below
341        //      when this function is updated.
342
343        // <baseline-position>
344        //
345        // It's weird that this accepts <baseline-position>, but not
346        // justify-content...
347        if let Ok(value) = input.try_parse(parse_baseline) {
348            return Ok(SelfAlignment(value));
349        }
350
351        // auto | normal | stretch
352        if let Ok(value) = input.try_parse(parse_auto_normal_stretch) {
353            return Ok(SelfAlignment(value));
354        }
355
356        // <overflow-position>? <self-position>
357        let overflow_position = input
358            .try_parse(parse_overflow_position)
359            .unwrap_or(AlignFlags::empty());
360        let self_position = parse_self_position(input, axis, AllowAnchorCenter::Yes)?;
361        Ok(SelfAlignment(overflow_position | self_position))
362    }
363
364    fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
365        list_baseline_keywords(f);
366        list_auto_normal_stretch(f);
367        list_overflow_position_keywords(f);
368        list_self_position_keywords(f, axis);
369    }
370
371    /// Performs a flip of the position, that is, for self-start we return self-end, for left
372    /// we return right, etc.
373    pub fn flip_position(self) -> Self {
374        let flipped_value = match self.0.value() {
375            AlignFlags::START => AlignFlags::END,
376            AlignFlags::END => AlignFlags::START,
377            AlignFlags::FLEX_START => AlignFlags::FLEX_END,
378            AlignFlags::FLEX_END => AlignFlags::FLEX_START,
379            AlignFlags::LEFT => AlignFlags::RIGHT,
380            AlignFlags::RIGHT => AlignFlags::LEFT,
381            AlignFlags::SELF_START => AlignFlags::SELF_END,
382            AlignFlags::SELF_END => AlignFlags::SELF_START,
383
384            AlignFlags::AUTO
385            | AlignFlags::NORMAL
386            | AlignFlags::BASELINE
387            | AlignFlags::LAST_BASELINE
388            | AlignFlags::STRETCH
389            | AlignFlags::CENTER
390            | AlignFlags::SPACE_BETWEEN
391            | AlignFlags::SPACE_AROUND
392            | AlignFlags::SPACE_EVENLY
393            | AlignFlags::ANCHOR_CENTER => return self,
394            _ => {
395                debug_assert!(false, "Unexpected alignment enumeration value");
396                return self;
397            },
398        };
399        self.with_value(flipped_value)
400    }
401
402    /// Returns a fixed-up alignment value.
403    #[inline]
404    pub fn with_value(self, value: AlignFlags) -> Self {
405        Self(self.0.with_value(value))
406    }
407}
408
409impl SpecifiedValueInfo for SelfAlignment {
410    fn collect_completion_keywords(f: KeywordsCollectFn) {
411        // TODO: This technically lists left/right for align-self. Not amazing but also not sure
412        // worth fixing here, could be special-cased on the caller.
413        Self::list_keywords(f, AxisDirection::Block);
414    }
415}
416
417/// Value of the `align-items` and `justify-items` properties
418///
419/// <https://drafts.csswg.org/css-align/#propdef-align-items>
420/// <https://drafts.csswg.org/css-align/#propdef-justify-items>
421#[derive(
422    Clone,
423    Copy,
424    Debug,
425    Deref,
426    Deserialize,
427    Eq,
428    MallocSizeOf,
429    PartialEq,
430    Serialize,
431    ToComputedValue,
432    ToCss,
433    ToResolvedValue,
434    ToShmem,
435    ToTyped,
436)]
437#[repr(C)]
438#[typed(todo_derive_fields)]
439pub struct ItemPlacement(pub AlignFlags);
440
441impl ItemPlacement {
442    /// The value 'normal'
443    #[inline]
444    pub fn normal() -> Self {
445        Self(AlignFlags::NORMAL)
446    }
447}
448
449impl ItemPlacement {
450    /// Parse a value for align-items
451    pub fn parse_block(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
452        Self::parse(input, AxisDirection::Block)
453    }
454
455    /// Parse a value for justify-items
456    pub fn parse_inline(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
457        Self::parse(input, AxisDirection::Inline)
458    }
459
460    fn parse(input: &mut Parser, axis: AxisDirection) -> Result<Self, ParseError> {
461        // NOTE Please also update `impl SpecifiedValueInfo` below when
462        //      this function is updated.
463
464        // <baseline-position>
465        if let Ok(baseline) = input.try_parse(parse_baseline) {
466            return Ok(Self(baseline));
467        }
468
469        // normal | stretch
470        if let Ok(value) = input.try_parse(parse_normal_stretch) {
471            return Ok(Self(value));
472        }
473
474        if axis == AxisDirection::Inline {
475            // legacy | [ legacy && [ left | right | center ] ]
476            if let Ok(value) = input.try_parse(parse_legacy) {
477                return Ok(Self(value));
478            }
479        }
480
481        // <overflow-position>? <self-position>
482        let overflow = input
483            .try_parse(parse_overflow_position)
484            .unwrap_or(AlignFlags::empty());
485        let self_position = parse_self_position(input, axis, AllowAnchorCenter::No)?;
486        Ok(ItemPlacement(self_position | overflow))
487    }
488}
489
490impl SpecifiedValueInfo for ItemPlacement {
491    fn collect_completion_keywords(f: KeywordsCollectFn) {
492        list_baseline_keywords(f);
493        list_normal_stretch(f);
494        list_overflow_position_keywords(f);
495        list_self_position_keywords(f, AxisDirection::Block);
496    }
497}
498
499/// Value of the `justify-items` property
500///
501/// <https://drafts.csswg.org/css-align/#justify-items-property>
502#[derive(
503    Clone,
504    Copy,
505    Debug,
506    Deref,
507    Deserialize,
508    Eq,
509    MallocSizeOf,
510    PartialEq,
511    Serialize,
512    ToCss,
513    ToResolvedValue,
514    ToShmem,
515    ToTyped,
516)]
517#[repr(C)]
518pub struct JustifyItems(pub ItemPlacement);
519
520impl JustifyItems {
521    /// The initial value 'legacy'
522    #[inline]
523    pub fn legacy() -> Self {
524        Self(ItemPlacement(AlignFlags::LEGACY))
525    }
526
527    /// The value 'normal'
528    #[inline]
529    pub fn normal() -> Self {
530        Self(ItemPlacement::normal())
531    }
532}
533
534impl Parse for JustifyItems {
535    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
536        ItemPlacement::parse_inline(context, input).map(Self)
537    }
538}
539
540impl SpecifiedValueInfo for JustifyItems {
541    fn collect_completion_keywords(f: KeywordsCollectFn) {
542        ItemPlacement::collect_completion_keywords(f);
543        list_legacy_keywords(f); // Inline axis only
544    }
545}
546
547// auto | normal | stretch
548fn parse_auto_normal_stretch(input: &mut Parser) -> Result<AlignFlags, ParseError> {
549    // NOTE Please also update the `list_auto_normal_stretch` function
550    //      below when this function is updated.
551    try_match_ident_ignore_ascii_case! { input,
552        "auto" => Ok(AlignFlags::AUTO),
553        "normal" => Ok(AlignFlags::NORMAL),
554        "stretch" => Ok(AlignFlags::STRETCH),
555    }
556}
557
558fn list_auto_normal_stretch(f: KeywordsCollectFn) {
559    f(&["auto", "normal", "stretch"]);
560}
561
562// normal | stretch
563fn parse_normal_stretch(input: &mut Parser) -> Result<AlignFlags, ParseError> {
564    // NOTE Please also update the `list_normal_stretch` function below
565    //      when this function is updated.
566    try_match_ident_ignore_ascii_case! { input,
567        "normal" => Ok(AlignFlags::NORMAL),
568        "stretch" => Ok(AlignFlags::STRETCH),
569    }
570}
571
572fn list_normal_stretch(f: KeywordsCollectFn) {
573    f(&["normal", "stretch"]);
574}
575
576// <baseline-position>
577fn parse_baseline(input: &mut Parser) -> Result<AlignFlags, ParseError> {
578    // NOTE Please also update the `list_baseline_keywords` function
579    //      below when this function is updated.
580    try_match_ident_ignore_ascii_case! { input,
581        "baseline" => Ok(AlignFlags::BASELINE),
582        "first" => {
583            input.expect_ident_matching("baseline")?;
584            Ok(AlignFlags::BASELINE)
585        },
586        "last" => {
587            input.expect_ident_matching("baseline")?;
588            Ok(AlignFlags::LAST_BASELINE)
589        },
590    }
591}
592
593fn list_baseline_keywords(f: KeywordsCollectFn) {
594    f(&["baseline", "first baseline", "last baseline"]);
595}
596
597// <content-distribution>
598fn parse_content_distribution(input: &mut Parser) -> Result<AlignFlags, ParseError> {
599    // NOTE Please also update the `list_content_distribution_keywords`
600    //      function below when this function is updated.
601    try_match_ident_ignore_ascii_case! { input,
602        "stretch" => Ok(AlignFlags::STRETCH),
603        "space-between" => Ok(AlignFlags::SPACE_BETWEEN),
604        "space-around" => Ok(AlignFlags::SPACE_AROUND),
605        "space-evenly" => Ok(AlignFlags::SPACE_EVENLY),
606    }
607}
608
609fn list_content_distribution_keywords(f: KeywordsCollectFn) {
610    f(&["stretch", "space-between", "space-around", "space-evenly"]);
611}
612
613// <overflow-position>
614fn parse_overflow_position(input: &mut Parser) -> Result<AlignFlags, ParseError> {
615    // NOTE Please also update the `list_overflow_position_keywords`
616    //      function below when this function is updated.
617    try_match_ident_ignore_ascii_case! { input,
618        "safe" => Ok(AlignFlags::SAFE),
619        "unsafe" => Ok(AlignFlags::UNSAFE),
620    }
621}
622
623fn list_overflow_position_keywords(f: KeywordsCollectFn) {
624    f(&["safe", "unsafe"]);
625}
626
627enum AllowAnchorCenter {
628    No,
629    Yes,
630}
631
632// <self-position> | left | right in the inline axis.
633fn parse_self_position(
634    input: &mut Parser,
635    axis: AxisDirection,
636    allow_anchor_center: AllowAnchorCenter,
637) -> Result<AlignFlags, ParseError> {
638    // NOTE Please also update the `list_self_position_keywords`
639    //      function below when this function is updated.
640    Ok(try_match_ident_ignore_ascii_case! { input,
641        "start" => AlignFlags::START,
642        "end" => AlignFlags::END,
643        "flex-start" => AlignFlags::FLEX_START,
644        "flex-end" => AlignFlags::FLEX_END,
645        "center" => AlignFlags::CENTER,
646        "self-start" => AlignFlags::SELF_START,
647        "self-end" => AlignFlags::SELF_END,
648        "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
649        "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
650        "anchor-center"
651            if matches!(allow_anchor_center, AllowAnchorCenter::Yes)
652                && crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) =>
653        {
654            AlignFlags::ANCHOR_CENTER
655        },
656    })
657}
658
659fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
660    f(&[
661        "start",
662        "end",
663        "flex-start",
664        "flex-end",
665        "center",
666        "self-start",
667        "self-end",
668    ]);
669
670    if crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) {
671        f(&["anchor-center"]);
672    }
673
674    if axis == AxisDirection::Inline {
675        f(&["left", "right"]);
676    }
677}
678
679fn parse_left_right_center(input: &mut Parser) -> Result<AlignFlags, ParseError> {
680    // NOTE Please also update the `list_legacy_keywords` function below
681    //      when this function is updated.
682    Ok(try_match_ident_ignore_ascii_case! { input,
683        "left" => AlignFlags::LEFT,
684        "right" => AlignFlags::RIGHT,
685        "center" => AlignFlags::CENTER,
686    })
687}
688
689// legacy | [ legacy && [ left | right | center ] ]
690fn parse_legacy(input: &mut Parser) -> Result<AlignFlags, ParseError> {
691    // NOTE Please also update the `list_legacy_keywords` function below
692    //      when this function is updated.
693    let flags = try_match_ident_ignore_ascii_case! { input,
694        "legacy" => {
695            let flags = input.try_parse(parse_left_right_center)
696                .unwrap_or(AlignFlags::empty());
697
698            return Ok(AlignFlags::LEGACY | flags)
699        },
700        "left" => AlignFlags::LEFT,
701        "right" => AlignFlags::RIGHT,
702        "center" => AlignFlags::CENTER,
703    };
704
705    input.expect_ident_matching("legacy")?;
706    Ok(AlignFlags::LEGACY | flags)
707}
708
709fn list_legacy_keywords(f: KeywordsCollectFn) {
710    f(&["legacy", "left", "right", "center"]);
711}