1use crate::parser::{Parse, ParserContext};
10use cssparser::Parser;
11use std::fmt::{self, Write};
12use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, ToCss};
13
14#[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 const AUTO = 0;
26 const NORMAL = 1;
28 const START = 2;
30 const END = 3;
32 const FLEX_START = 4;
34 const FLEX_END = 5;
36 const CENTER = 6;
38 const LEFT = 7;
40 const RIGHT = 8;
42 const BASELINE = 9;
44 const LAST_BASELINE = 10;
46 const STRETCH = 11;
48 const SELF_START = 12;
50 const SELF_END = 13;
52 const SPACE_BETWEEN = 14;
54 const SPACE_AROUND = 15;
56 const SPACE_EVENLY = 16;
58 const ANCHOR_CENTER = 17;
60
61 const LEGACY = 1 << 5;
64 const SAFE = 1 << 6;
66 const UNSAFE = 1 << 7;
68
69 const FLAG_BITS = 0b11100000;
71 }
72}
73
74impl AlignFlags {
75 #[inline]
77 pub fn value(&self) -> Self {
78 *self & !AlignFlags::FLAG_BITS
79 }
80
81 #[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#[derive(Clone, Copy, PartialEq)]
138pub enum AxisDirection {
139 Block,
141 Inline,
143}
144
145#[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 }
167
168impl ContentDistribution {
169 #[inline]
171 pub fn normal() -> Self {
172 Self::new(AlignFlags::NORMAL)
173 }
174
175 #[inline]
177 pub fn start() -> Self {
178 Self::new(AlignFlags::START)
179 }
180
181 #[inline]
183 pub fn new(primary: AlignFlags) -> Self {
184 Self { primary }
185 }
186
187 pub fn is_baseline_position(&self) -> bool {
189 matches!(
190 self.primary.value(),
191 AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
192 )
193 }
194
195 #[inline]
197 pub fn primary(self) -> AlignFlags {
198 self.primary
199 }
200
201 pub fn parse<'i, 't>(
203 input: &mut Parser<'i, 't>,
204 axis: AxisDirection,
205 ) -> Result<Self, ParseError<'i>> {
206 if input
211 .try_parse(|i| i.expect_ident_matching("normal"))
212 .is_ok()
213 {
214 return Ok(ContentDistribution::normal());
215 }
216
217 if axis == AxisDirection::Block {
219 if let Ok(value) = input.try_parse(parse_baseline) {
220 return Ok(ContentDistribution::new(value));
221 }
222 }
223
224 if let Ok(value) = input.try_parse(parse_content_distribution) {
226 return Ok(ContentDistribution::new(value));
227 }
228
229 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#[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 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#[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 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#[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 #[inline]
361 pub fn auto() -> Self {
362 SelfAlignment(AlignFlags::AUTO)
363 }
364
365 pub fn is_valid_on_both_axes(&self) -> bool {
367 match self.0.value() {
368 AlignFlags::LEFT | AlignFlags::RIGHT => false,
370
371 _ => true,
372 }
373 }
374
375 pub fn parse<'i, 't>(
377 input: &mut Parser<'i, 't>,
378 axis: AxisDirection,
379 ) -> Result<Self, ParseError<'i>> {
380 if let Ok(value) = input.try_parse(parse_baseline) {
388 return Ok(SelfAlignment(value));
389 }
390
391 if let Ok(value) = input.try_parse(parse_auto_normal_stretch) {
393 return Ok(SelfAlignment(value));
394 }
395
396 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#[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 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#[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 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#[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 #[inline]
512 pub fn normal() -> Self {
513 AlignItems(AlignFlags::NORMAL)
514 }
515}
516
517impl Parse for AlignItems {
518 fn parse<'i, 't>(
521 _: &ParserContext,
522 input: &mut Parser<'i, 't>,
523 ) -> Result<Self, ParseError<'i>> {
524 if let Ok(baseline) = input.try_parse(parse_baseline) {
529 return Ok(AlignItems(baseline));
530 }
531
532 if let Ok(value) = input.try_parse(parse_normal_stretch) {
534 return Ok(AlignItems(value));
535 }
536 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#[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 #[inline]
565 pub fn legacy() -> Self {
566 JustifyItems(AlignFlags::LEGACY)
567 }
568
569 #[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 if let Ok(baseline) = input.try_parse(parse_baseline) {
589 return Ok(JustifyItems(baseline));
590 }
591
592 if let Ok(value) = input.try_parse(parse_normal_stretch) {
594 return Ok(JustifyItems(value));
595 }
596
597 if let Ok(value) = input.try_parse(parse_legacy) {
599 return Ok(JustifyItems(value));
600 }
601
602 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
621fn parse_auto_normal_stretch<'i, 't>(
623 input: &mut Parser<'i, 't>,
624) -> Result<AlignFlags, ParseError<'i>> {
625 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
638fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
640 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
652fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
654 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
673fn parse_content_distribution<'i, 't>(
675 input: &mut Parser<'i, 't>,
676) -> Result<AlignFlags, ParseError<'i>> {
677 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
691fn parse_overflow_position<'i, 't>(
693 input: &mut Parser<'i, 't>,
694) -> Result<AlignFlags, ParseError<'i>> {
695 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
707fn parse_self_position<'i, 't>(
709 input: &mut Parser<'i, 't>,
710 axis: AxisDirection,
711) -> Result<AlignFlags, ParseError<'i>> {
712 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 Ok(try_match_ident_ignore_ascii_case! { input,
754 "left" => AlignFlags::LEFT,
755 "right" => AlignFlags::RIGHT,
756 "center" => AlignFlags::CENTER,
757 })
758}
759
760fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
762 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}