1use 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#[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 const AUTO = 0;
36 const NORMAL = 1;
38 const START = 2;
40 const END = 3;
42 const FLEX_START = 4;
44 const FLEX_END = 5;
46 const CENTER = 6;
48 const LEFT = 7;
50 const RIGHT = 8;
52 const BASELINE = 9;
54 const LAST_BASELINE = 10;
56 const STRETCH = 11;
58 const SELF_START = 12;
60 const SELF_END = 13;
62 const SPACE_BETWEEN = 14;
64 const SPACE_AROUND = 15;
66 const SPACE_EVENLY = 16;
68 const ANCHOR_CENTER = 17;
70
71 const LEGACY = 1 << 5;
74 const SAFE = 1 << 6;
76 const UNSAFE = 1 << 7;
78
79 const FLAG_BITS = 0b11100000;
81 }
82}
83
84impl AlignFlags {
85 #[inline]
87 pub fn value(&self) -> Self {
88 *self & !AlignFlags::FLAG_BITS
89 }
90
91 #[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 #[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#[derive(Clone, Copy, PartialEq)]
154pub enum AxisDirection {
155 Block,
157 Inline,
159}
160
161#[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 }
187
188impl ContentDistribution {
189 #[inline]
191 pub fn normal() -> Self {
192 Self::new(AlignFlags::NORMAL)
193 }
194
195 #[inline]
197 pub fn start() -> Self {
198 Self::new(AlignFlags::START)
199 }
200
201 #[inline]
203 pub fn new(primary: AlignFlags) -> Self {
204 Self { primary }
205 }
206
207 pub fn is_baseline_position(&self) -> bool {
209 matches!(
210 self.primary.value(),
211 AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
212 )
213 }
214
215 #[inline]
217 pub fn primary(self) -> AlignFlags {
218 self.primary
219 }
220
221 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 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 if input
246 .try_parse(|i| i.expect_ident_matching("normal"))
247 .is_ok()
248 {
249 return Ok(ContentDistribution::normal());
250 }
251
252 if axis == AxisDirection::Block {
254 if let Ok(value) = input.try_parse(parse_baseline) {
255 return Ok(ContentDistribution::new(value));
256 }
257 }
258
259 if let Ok(value) = input.try_parse(parse_content_distribution) {
261 return Ok(ContentDistribution::new(value));
262 }
263
264 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); list_content_distribution_keywords(f);
290 list_overflow_position_keywords(f);
291 f(&["start", "end", "flex-start", "flex-end", "center"]);
292 f(&["left", "right"]); }
294}
295
296#[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 #[inline]
323 pub fn auto() -> Self {
324 SelfAlignment(AlignFlags::AUTO)
325 }
326
327 pub fn is_valid_on_both_axes(&self) -> bool {
329 match self.0.value() {
330 AlignFlags::LEFT | AlignFlags::RIGHT => false,
332
333 _ => true,
334 }
335 }
336
337 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 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 fn parse<'i, 't>(
355 input: &mut Parser<'i, 't>,
356 axis: AxisDirection,
357 ) -> Result<Self, ParseError<'i>> {
358 if let Ok(value) = input.try_parse(parse_baseline) {
366 return Ok(SelfAlignment(value));
367 }
368
369 if let Ok(value) = input.try_parse(parse_auto_normal_stretch) {
371 return Ok(SelfAlignment(value));
372 }
373
374 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 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 #[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 Self::list_keywords(f, AxisDirection::Block);
432 }
433}
434
435#[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 #[inline]
462 pub fn normal() -> Self {
463 Self(AlignFlags::NORMAL)
464 }
465}
466
467impl ItemPlacement {
468 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 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 if let Ok(baseline) = input.try_parse(parse_baseline) {
493 return Ok(Self(baseline));
494 }
495
496 if let Ok(value) = input.try_parse(parse_normal_stretch) {
498 return Ok(Self(value));
499 }
500
501 if axis == AxisDirection::Inline {
502 if let Ok(value) = input.try_parse(parse_legacy) {
504 return Ok(Self(value));
505 }
506 }
507
508 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#[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 #[inline]
550 pub fn legacy() -> Self {
551 Self(ItemPlacement(AlignFlags::LEGACY))
552 }
553
554 #[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); }
575}
576
577fn parse_auto_normal_stretch<'i, 't>(
579 input: &mut Parser<'i, 't>,
580) -> Result<AlignFlags, ParseError<'i>> {
581 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
594fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
596 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
608fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
610 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
629fn parse_content_distribution<'i, 't>(
631 input: &mut Parser<'i, 't>,
632) -> Result<AlignFlags, ParseError<'i>> {
633 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
647fn parse_overflow_position<'i, 't>(
649 input: &mut Parser<'i, 't>,
650) -> Result<AlignFlags, ParseError<'i>> {
651 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
663fn parse_self_position<'i, 't>(
665 input: &mut Parser<'i, 't>,
666 axis: AxisDirection,
667) -> Result<AlignFlags, ParseError<'i>> {
668 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 Ok(try_match_ident_ignore_ascii_case! { input,
710 "left" => AlignFlags::LEFT,
711 "right" => AlignFlags::RIGHT,
712 "center" => AlignFlags::CENTER,
713 })
714}
715
716fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
718 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}