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 with_value(&self, value: AlignFlags) -> Self {
84 debug_assert!(!value.intersects(Self::FLAG_BITS));
85 value | self.flags()
86 }
87
88 #[inline]
90 pub fn flags(&self) -> Self {
91 *self & AlignFlags::FLAG_BITS
92 }
93}
94
95impl ToCss for AlignFlags {
96 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
97 where
98 W: Write,
99 {
100 let flags = self.flags();
101 let value = self.value();
102 match flags {
103 AlignFlags::LEGACY => {
104 dest.write_str("legacy")?;
105 if value.is_empty() {
106 return Ok(());
107 }
108 dest.write_char(' ')?;
109 },
110 AlignFlags::SAFE => dest.write_str("safe ")?,
111 AlignFlags::UNSAFE => dest.write_str("unsafe ")?,
112 _ => {
113 debug_assert_eq!(flags, AlignFlags::empty());
114 },
115 }
116
117 dest.write_str(match value {
118 AlignFlags::AUTO => "auto",
119 AlignFlags::NORMAL => "normal",
120 AlignFlags::START => "start",
121 AlignFlags::END => "end",
122 AlignFlags::FLEX_START => "flex-start",
123 AlignFlags::FLEX_END => "flex-end",
124 AlignFlags::CENTER => "center",
125 AlignFlags::LEFT => "left",
126 AlignFlags::RIGHT => "right",
127 AlignFlags::BASELINE => "baseline",
128 AlignFlags::LAST_BASELINE => "last baseline",
129 AlignFlags::STRETCH => "stretch",
130 AlignFlags::SELF_START => "self-start",
131 AlignFlags::SELF_END => "self-end",
132 AlignFlags::SPACE_BETWEEN => "space-between",
133 AlignFlags::SPACE_AROUND => "space-around",
134 AlignFlags::SPACE_EVENLY => "space-evenly",
135 AlignFlags::ANCHOR_CENTER => "anchor-center",
136 _ => unreachable!(),
137 })
138 }
139}
140
141#[derive(Clone, Copy, PartialEq)]
144pub enum AxisDirection {
145 Block,
147 Inline,
149}
150
151#[derive(
156 Clone,
157 Copy,
158 Debug,
159 Eq,
160 MallocSizeOf,
161 PartialEq,
162 ToComputedValue,
163 ToCss,
164 ToResolvedValue,
165 ToShmem,
166 ToTyped,
167)]
168#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
169#[repr(C)]
170pub struct ContentDistribution {
171 primary: AlignFlags,
172 }
175
176impl ContentDistribution {
177 #[inline]
179 pub fn normal() -> Self {
180 Self::new(AlignFlags::NORMAL)
181 }
182
183 #[inline]
185 pub fn start() -> Self {
186 Self::new(AlignFlags::START)
187 }
188
189 #[inline]
191 pub fn new(primary: AlignFlags) -> Self {
192 Self { primary }
193 }
194
195 pub fn is_baseline_position(&self) -> bool {
197 matches!(
198 self.primary.value(),
199 AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
200 )
201 }
202
203 #[inline]
205 pub fn primary(self) -> AlignFlags {
206 self.primary
207 }
208
209 pub fn parse_block<'i>(
211 _: &ParserContext,
212 input: &mut Parser<'i, '_>,
213 ) -> Result<Self, ParseError<'i>> {
214 Self::parse(input, AxisDirection::Block)
215 }
216
217 pub fn parse_inline<'i>(
219 _: &ParserContext,
220 input: &mut Parser<'i, '_>,
221 ) -> Result<Self, ParseError<'i>> {
222 Self::parse(input, AxisDirection::Inline)
223 }
224
225 fn parse<'i, 't>(
226 input: &mut Parser<'i, 't>,
227 axis: AxisDirection,
228 ) -> Result<Self, ParseError<'i>> {
229 if input
234 .try_parse(|i| i.expect_ident_matching("normal"))
235 .is_ok()
236 {
237 return Ok(ContentDistribution::normal());
238 }
239
240 if axis == AxisDirection::Block {
242 if let Ok(value) = input.try_parse(parse_baseline) {
243 return Ok(ContentDistribution::new(value));
244 }
245 }
246
247 if let Ok(value) = input.try_parse(parse_content_distribution) {
249 return Ok(ContentDistribution::new(value));
250 }
251
252 let overflow_position = input
254 .try_parse(parse_overflow_position)
255 .unwrap_or(AlignFlags::empty());
256
257 let content_position = try_match_ident_ignore_ascii_case! { input,
258 "start" => AlignFlags::START,
259 "end" => AlignFlags::END,
260 "flex-start" => AlignFlags::FLEX_START,
261 "flex-end" => AlignFlags::FLEX_END,
262 "center" => AlignFlags::CENTER,
263 "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
264 "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
265 };
266
267 Ok(ContentDistribution::new(
268 content_position | overflow_position,
269 ))
270 }
271}
272
273impl SpecifiedValueInfo for ContentDistribution {
274 fn collect_completion_keywords(f: KeywordsCollectFn) {
275 f(&["normal"]);
276 list_baseline_keywords(f); list_content_distribution_keywords(f);
278 list_overflow_position_keywords(f);
279 f(&["start", "end", "flex-start", "flex-end", "center"]);
280 f(&["left", "right"]); }
282}
283
284#[derive(
289 Clone,
290 Copy,
291 Debug,
292 Deref,
293 Eq,
294 MallocSizeOf,
295 PartialEq,
296 ToComputedValue,
297 ToCss,
298 ToResolvedValue,
299 ToShmem,
300 ToTyped,
301)]
302#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
303#[repr(C)]
304pub struct SelfAlignment(pub AlignFlags);
305
306impl SelfAlignment {
307 #[inline]
309 pub fn auto() -> Self {
310 SelfAlignment(AlignFlags::AUTO)
311 }
312
313 pub fn is_valid_on_both_axes(&self) -> bool {
315 match self.0.value() {
316 AlignFlags::LEFT | AlignFlags::RIGHT => false,
318
319 _ => true,
320 }
321 }
322
323 pub fn parse_block<'i, 't>(
325 _: &ParserContext,
326 input: &mut Parser<'i, 't>,
327 ) -> Result<Self, ParseError<'i>> {
328 Self::parse(input, AxisDirection::Block)
329 }
330
331 pub fn parse_inline<'i, 't>(
333 _: &ParserContext,
334 input: &mut Parser<'i, 't>,
335 ) -> Result<Self, ParseError<'i>> {
336 Self::parse(input, AxisDirection::Inline)
337 }
338
339 fn parse<'i, 't>(
341 input: &mut Parser<'i, 't>,
342 axis: AxisDirection,
343 ) -> Result<Self, ParseError<'i>> {
344 if let Ok(value) = input.try_parse(parse_baseline) {
352 return Ok(SelfAlignment(value));
353 }
354
355 if let Ok(value) = input.try_parse(parse_auto_normal_stretch) {
357 return Ok(SelfAlignment(value));
358 }
359
360 let overflow_position = input
362 .try_parse(parse_overflow_position)
363 .unwrap_or(AlignFlags::empty());
364 let self_position = parse_self_position(input, axis)?;
365 Ok(SelfAlignment(overflow_position | self_position))
366 }
367
368 fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
369 list_baseline_keywords(f);
370 list_auto_normal_stretch(f);
371 list_overflow_position_keywords(f);
372 list_self_position_keywords(f, axis);
373 }
374
375 pub fn flip_position(self) -> Self {
378 let flipped_value = match self.0.value() {
379 AlignFlags::START => AlignFlags::END,
380 AlignFlags::END => AlignFlags::START,
381 AlignFlags::FLEX_START => AlignFlags::FLEX_END,
382 AlignFlags::FLEX_END => AlignFlags::FLEX_START,
383 AlignFlags::LEFT => AlignFlags::RIGHT,
384 AlignFlags::RIGHT => AlignFlags::LEFT,
385 AlignFlags::SELF_START => AlignFlags::SELF_END,
386 AlignFlags::SELF_END => AlignFlags::SELF_START,
387
388 AlignFlags::AUTO |
389 AlignFlags::NORMAL |
390 AlignFlags::BASELINE |
391 AlignFlags::LAST_BASELINE |
392 AlignFlags::STRETCH |
393 AlignFlags::CENTER |
394 AlignFlags::SPACE_BETWEEN |
395 AlignFlags::SPACE_AROUND |
396 AlignFlags::SPACE_EVENLY |
397 AlignFlags::ANCHOR_CENTER => return self,
398 _ => {
399 debug_assert!(false, "Unexpected alignment enumeration value");
400 return self;
401 }
402 };
403 self.with_value(flipped_value)
404 }
405
406 #[inline]
408 pub fn with_value(self, value: AlignFlags) -> Self {
409 Self(self.0.with_value(value))
410 }
411}
412
413impl SpecifiedValueInfo for SelfAlignment {
414 fn collect_completion_keywords(f: KeywordsCollectFn) {
415 Self::list_keywords(f, AxisDirection::Block);
418 }
419}
420
421#[derive(
426 Clone,
427 Copy,
428 Debug,
429 Deref,
430 Eq,
431 MallocSizeOf,
432 PartialEq,
433 ToComputedValue,
434 ToCss,
435 ToResolvedValue,
436 ToShmem,
437 ToTyped,
438)]
439#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
440#[repr(C)]
441pub struct ItemPlacement(pub AlignFlags);
442
443impl ItemPlacement {
444 #[inline]
446 pub fn normal() -> Self {
447 Self(AlignFlags::NORMAL)
448 }
449}
450
451impl ItemPlacement {
452 pub fn parse_block<'i>(
454 _: &ParserContext,
455 input: &mut Parser<'i, '_>,
456 ) -> Result<Self, ParseError<'i>> {
457 Self::parse(input, AxisDirection::Block)
458 }
459
460 pub fn parse_inline<'i>(
462 _: &ParserContext,
463 input: &mut Parser<'i, '_>,
464 ) -> Result<Self, ParseError<'i>> {
465 Self::parse(input, AxisDirection::Inline)
466 }
467
468 fn parse<'i, 't>(
469 input: &mut Parser<'i, 't>,
470 axis: AxisDirection,
471 ) -> Result<Self, ParseError<'i>> {
472 if let Ok(baseline) = input.try_parse(parse_baseline) {
477 return Ok(Self(baseline));
478 }
479
480 if let Ok(value) = input.try_parse(parse_normal_stretch) {
482 return Ok(Self(value));
483 }
484
485 if axis == AxisDirection::Inline {
486 if let Ok(value) = input.try_parse(parse_legacy) {
488 return Ok(Self(value));
489 }
490 }
491
492 let overflow = input
494 .try_parse(parse_overflow_position)
495 .unwrap_or(AlignFlags::empty());
496 let self_position = parse_self_position(input, axis)?;
497 Ok(ItemPlacement(self_position | overflow))
498 }
499}
500
501impl SpecifiedValueInfo for ItemPlacement {
502 fn collect_completion_keywords(f: KeywordsCollectFn) {
503 list_baseline_keywords(f);
504 list_normal_stretch(f);
505 list_overflow_position_keywords(f);
506 list_self_position_keywords(f, AxisDirection::Block);
507 }
508}
509
510#[derive(
514 Clone, Copy, Debug, Deref, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem, ToTyped,
515)]
516#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
517#[repr(C)]
518pub struct JustifyItems(pub ItemPlacement);
519
520impl JustifyItems {
521 #[inline]
523 pub fn legacy() -> Self {
524 Self(ItemPlacement(AlignFlags::LEGACY))
525 }
526
527 #[inline]
529 pub fn normal() -> Self {
530 Self(ItemPlacement::normal())
531 }
532}
533
534impl Parse for JustifyItems {
535 fn parse<'i, 't>(
536 context: &ParserContext,
537 input: &mut Parser<'i, 't>,
538 ) -> Result<Self, ParseError<'i>> {
539 ItemPlacement::parse_inline(context, input).map(Self)
540 }
541}
542
543impl SpecifiedValueInfo for JustifyItems {
544 fn collect_completion_keywords(f: KeywordsCollectFn) {
545 ItemPlacement::collect_completion_keywords(f);
546 list_legacy_keywords(f); }
548}
549
550fn parse_auto_normal_stretch<'i, 't>(
552 input: &mut Parser<'i, 't>,
553) -> Result<AlignFlags, ParseError<'i>> {
554 try_match_ident_ignore_ascii_case! { input,
557 "auto" => Ok(AlignFlags::AUTO),
558 "normal" => Ok(AlignFlags::NORMAL),
559 "stretch" => Ok(AlignFlags::STRETCH),
560 }
561}
562
563fn list_auto_normal_stretch(f: KeywordsCollectFn) {
564 f(&["auto", "normal", "stretch"]);
565}
566
567fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
569 try_match_ident_ignore_ascii_case! { input,
572 "normal" => Ok(AlignFlags::NORMAL),
573 "stretch" => Ok(AlignFlags::STRETCH),
574 }
575}
576
577fn list_normal_stretch(f: KeywordsCollectFn) {
578 f(&["normal", "stretch"]);
579}
580
581fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
583 try_match_ident_ignore_ascii_case! { input,
586 "baseline" => Ok(AlignFlags::BASELINE),
587 "first" => {
588 input.expect_ident_matching("baseline")?;
589 Ok(AlignFlags::BASELINE)
590 },
591 "last" => {
592 input.expect_ident_matching("baseline")?;
593 Ok(AlignFlags::LAST_BASELINE)
594 },
595 }
596}
597
598fn list_baseline_keywords(f: KeywordsCollectFn) {
599 f(&["baseline", "first baseline", "last baseline"]);
600}
601
602fn parse_content_distribution<'i, 't>(
604 input: &mut Parser<'i, 't>,
605) -> Result<AlignFlags, ParseError<'i>> {
606 try_match_ident_ignore_ascii_case! { input,
609 "stretch" => Ok(AlignFlags::STRETCH),
610 "space-between" => Ok(AlignFlags::SPACE_BETWEEN),
611 "space-around" => Ok(AlignFlags::SPACE_AROUND),
612 "space-evenly" => Ok(AlignFlags::SPACE_EVENLY),
613 }
614}
615
616fn list_content_distribution_keywords(f: KeywordsCollectFn) {
617 f(&["stretch", "space-between", "space-around", "space-evenly"]);
618}
619
620fn parse_overflow_position<'i, 't>(
622 input: &mut Parser<'i, 't>,
623) -> Result<AlignFlags, ParseError<'i>> {
624 try_match_ident_ignore_ascii_case! { input,
627 "safe" => Ok(AlignFlags::SAFE),
628 "unsafe" => Ok(AlignFlags::UNSAFE),
629 }
630}
631
632fn list_overflow_position_keywords(f: KeywordsCollectFn) {
633 f(&["safe", "unsafe"]);
634}
635
636fn parse_self_position<'i, 't>(
638 input: &mut Parser<'i, 't>,
639 axis: AxisDirection,
640) -> Result<AlignFlags, ParseError<'i>> {
641 Ok(try_match_ident_ignore_ascii_case! { input,
644 "start" => AlignFlags::START,
645 "end" => AlignFlags::END,
646 "flex-start" => AlignFlags::FLEX_START,
647 "flex-end" => AlignFlags::FLEX_END,
648 "center" => AlignFlags::CENTER,
649 "self-start" => AlignFlags::SELF_START,
650 "self-end" => AlignFlags::SELF_END,
651 "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
652 "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
653 "anchor-center" if static_prefs::pref!("layout.css.anchor-positioning.enabled") => AlignFlags::ANCHOR_CENTER,
654 })
655}
656
657fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
658 f(&[
659 "start",
660 "end",
661 "flex-start",
662 "flex-end",
663 "center",
664 "self-start",
665 "self-end",
666 ]);
667
668 if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
669 f(&["anchor-center"]);
670 }
671
672 if axis == AxisDirection::Inline {
673 f(&["left", "right"]);
674 }
675}
676
677fn parse_left_right_center<'i, 't>(
678 input: &mut Parser<'i, 't>,
679) -> Result<AlignFlags, ParseError<'i>> {
680 Ok(try_match_ident_ignore_ascii_case! { input,
683 "left" => AlignFlags::LEFT,
684 "right" => AlignFlags::RIGHT,
685 "center" => AlignFlags::CENTER,
686 })
687}
688
689fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
691 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}