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, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
18)]
19#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
20#[repr(C)]
21pub struct AlignFlags(u8);
22bitflags! {
23 impl AlignFlags: u8 {
24 const AUTO = 0;
27 const NORMAL = 1;
29 const START = 2;
31 const END = 3;
33 const FLEX_START = 4;
35 const FLEX_END = 5;
37 const CENTER = 6;
39 const LEFT = 7;
41 const RIGHT = 8;
43 const BASELINE = 9;
45 const LAST_BASELINE = 10;
47 const STRETCH = 11;
49 const SELF_START = 12;
51 const SELF_END = 13;
53 const SPACE_BETWEEN = 14;
55 const SPACE_AROUND = 15;
57 const SPACE_EVENLY = 16;
59 const ANCHOR_CENTER = 17;
61
62 const LEGACY = 1 << 5;
65 const SAFE = 1 << 6;
67 const UNSAFE = 1 << 7;
69
70 const FLAG_BITS = 0b11100000;
72 }
73}
74
75impl AlignFlags {
76 #[inline]
78 pub fn value(&self) -> Self {
79 *self & !AlignFlags::FLAG_BITS
80 }
81
82 #[inline]
84 pub fn with_value(&self, value: AlignFlags) -> Self {
85 debug_assert!(!value.intersects(Self::FLAG_BITS));
86 value | self.flags()
87 }
88
89 #[inline]
91 pub fn flags(&self) -> Self {
92 *self & AlignFlags::FLAG_BITS
93 }
94}
95
96impl ToCss for AlignFlags {
97 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
98 where
99 W: Write,
100 {
101 let flags = self.flags();
102 let value = self.value();
103 match flags {
104 AlignFlags::LEGACY => {
105 dest.write_str("legacy")?;
106 if value.is_empty() {
107 return Ok(());
108 }
109 dest.write_char(' ')?;
110 },
111 AlignFlags::SAFE => dest.write_str("safe ")?,
112 AlignFlags::UNSAFE => dest.write_str("unsafe ")?,
113 _ => {
114 debug_assert_eq!(flags, AlignFlags::empty());
115 },
116 }
117
118 dest.write_str(match value {
119 AlignFlags::AUTO => "auto",
120 AlignFlags::NORMAL => "normal",
121 AlignFlags::START => "start",
122 AlignFlags::END => "end",
123 AlignFlags::FLEX_START => "flex-start",
124 AlignFlags::FLEX_END => "flex-end",
125 AlignFlags::CENTER => "center",
126 AlignFlags::LEFT => "left",
127 AlignFlags::RIGHT => "right",
128 AlignFlags::BASELINE => "baseline",
129 AlignFlags::LAST_BASELINE => "last baseline",
130 AlignFlags::STRETCH => "stretch",
131 AlignFlags::SELF_START => "self-start",
132 AlignFlags::SELF_END => "self-end",
133 AlignFlags::SPACE_BETWEEN => "space-between",
134 AlignFlags::SPACE_AROUND => "space-around",
135 AlignFlags::SPACE_EVENLY => "space-evenly",
136 AlignFlags::ANCHOR_CENTER => "anchor-center",
137 _ => unreachable!(),
138 })
139 }
140}
141
142#[derive(Clone, Copy, PartialEq)]
145pub enum AxisDirection {
146 Block,
148 Inline,
150}
151
152#[derive(
157 Clone,
158 Copy,
159 Debug,
160 Eq,
161 MallocSizeOf,
162 PartialEq,
163 ToComputedValue,
164 ToCss,
165 ToResolvedValue,
166 ToShmem,
167 ToTyped,
168)]
169#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
170#[repr(C)]
171pub struct ContentDistribution {
172 primary: AlignFlags,
173 }
176
177impl ContentDistribution {
178 #[inline]
180 pub fn normal() -> Self {
181 Self::new(AlignFlags::NORMAL)
182 }
183
184 #[inline]
186 pub fn start() -> Self {
187 Self::new(AlignFlags::START)
188 }
189
190 #[inline]
192 pub fn new(primary: AlignFlags) -> Self {
193 Self { primary }
194 }
195
196 pub fn is_baseline_position(&self) -> bool {
198 matches!(
199 self.primary.value(),
200 AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
201 )
202 }
203
204 #[inline]
206 pub fn primary(self) -> AlignFlags {
207 self.primary
208 }
209
210 pub fn parse_block<'i>(
212 _: &ParserContext,
213 input: &mut Parser<'i, '_>,
214 ) -> Result<Self, ParseError<'i>> {
215 Self::parse(input, AxisDirection::Block)
216 }
217
218 pub fn parse_inline<'i>(
220 _: &ParserContext,
221 input: &mut Parser<'i, '_>,
222 ) -> Result<Self, ParseError<'i>> {
223 Self::parse(input, AxisDirection::Inline)
224 }
225
226 fn parse<'i, 't>(
227 input: &mut Parser<'i, 't>,
228 axis: AxisDirection,
229 ) -> Result<Self, ParseError<'i>> {
230 if input
235 .try_parse(|i| i.expect_ident_matching("normal"))
236 .is_ok()
237 {
238 return Ok(ContentDistribution::normal());
239 }
240
241 if axis == AxisDirection::Block {
243 if let Ok(value) = input.try_parse(parse_baseline) {
244 return Ok(ContentDistribution::new(value));
245 }
246 }
247
248 if let Ok(value) = input.try_parse(parse_content_distribution) {
250 return Ok(ContentDistribution::new(value));
251 }
252
253 let overflow_position = input
255 .try_parse(parse_overflow_position)
256 .unwrap_or(AlignFlags::empty());
257
258 let content_position = try_match_ident_ignore_ascii_case! { input,
259 "start" => AlignFlags::START,
260 "end" => AlignFlags::END,
261 "flex-start" => AlignFlags::FLEX_START,
262 "flex-end" => AlignFlags::FLEX_END,
263 "center" => AlignFlags::CENTER,
264 "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
265 "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
266 };
267
268 Ok(ContentDistribution::new(
269 content_position | overflow_position,
270 ))
271 }
272}
273
274impl SpecifiedValueInfo for ContentDistribution {
275 fn collect_completion_keywords(f: KeywordsCollectFn) {
276 f(&["normal"]);
277 list_baseline_keywords(f); list_content_distribution_keywords(f);
279 list_overflow_position_keywords(f);
280 f(&["start", "end", "flex-start", "flex-end", "center"]);
281 f(&["left", "right"]); }
283}
284
285#[derive(
290 Clone,
291 Copy,
292 Debug,
293 Deref,
294 Eq,
295 MallocSizeOf,
296 PartialEq,
297 ToComputedValue,
298 ToCss,
299 ToResolvedValue,
300 ToShmem,
301 ToTyped,
302)]
303#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
304#[repr(C)]
305pub struct SelfAlignment(pub AlignFlags);
306
307impl SelfAlignment {
308 #[inline]
310 pub fn auto() -> Self {
311 SelfAlignment(AlignFlags::AUTO)
312 }
313
314 pub fn is_valid_on_both_axes(&self) -> bool {
316 match self.0.value() {
317 AlignFlags::LEFT | AlignFlags::RIGHT => false,
319
320 _ => true,
321 }
322 }
323
324 pub fn parse_block<'i, 't>(
326 _: &ParserContext,
327 input: &mut Parser<'i, 't>,
328 ) -> Result<Self, ParseError<'i>> {
329 Self::parse(input, AxisDirection::Block)
330 }
331
332 pub fn parse_inline<'i, 't>(
334 _: &ParserContext,
335 input: &mut Parser<'i, 't>,
336 ) -> Result<Self, ParseError<'i>> {
337 Self::parse(input, AxisDirection::Inline)
338 }
339
340 fn parse<'i, 't>(
342 input: &mut Parser<'i, 't>,
343 axis: AxisDirection,
344 ) -> Result<Self, ParseError<'i>> {
345 if let Ok(value) = input.try_parse(parse_baseline) {
353 return Ok(SelfAlignment(value));
354 }
355
356 if let Ok(value) = input.try_parse(parse_auto_normal_stretch) {
358 return Ok(SelfAlignment(value));
359 }
360
361 let overflow_position = input
363 .try_parse(parse_overflow_position)
364 .unwrap_or(AlignFlags::empty());
365 let self_position = parse_self_position(input, axis)?;
366 Ok(SelfAlignment(overflow_position | self_position))
367 }
368
369 fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
370 list_baseline_keywords(f);
371 list_auto_normal_stretch(f);
372 list_overflow_position_keywords(f);
373 list_self_position_keywords(f, axis);
374 }
375
376 pub fn flip_position(self) -> Self {
379 let flipped_value = match self.0.value() {
380 AlignFlags::START => AlignFlags::END,
381 AlignFlags::END => AlignFlags::START,
382 AlignFlags::FLEX_START => AlignFlags::FLEX_END,
383 AlignFlags::FLEX_END => AlignFlags::FLEX_START,
384 AlignFlags::LEFT => AlignFlags::RIGHT,
385 AlignFlags::RIGHT => AlignFlags::LEFT,
386 AlignFlags::SELF_START => AlignFlags::SELF_END,
387 AlignFlags::SELF_END => AlignFlags::SELF_START,
388
389 AlignFlags::AUTO
390 | AlignFlags::NORMAL
391 | AlignFlags::BASELINE
392 | AlignFlags::LAST_BASELINE
393 | AlignFlags::STRETCH
394 | AlignFlags::CENTER
395 | AlignFlags::SPACE_BETWEEN
396 | AlignFlags::SPACE_AROUND
397 | AlignFlags::SPACE_EVENLY
398 | AlignFlags::ANCHOR_CENTER => return self,
399 _ => {
400 debug_assert!(false, "Unexpected alignment enumeration value");
401 return self;
402 },
403 };
404 self.with_value(flipped_value)
405 }
406
407 #[inline]
409 pub fn with_value(self, value: AlignFlags) -> Self {
410 Self(self.0.with_value(value))
411 }
412}
413
414impl SpecifiedValueInfo for SelfAlignment {
415 fn collect_completion_keywords(f: KeywordsCollectFn) {
416 Self::list_keywords(f, AxisDirection::Block);
419 }
420}
421
422#[derive(
427 Clone,
428 Copy,
429 Debug,
430 Deref,
431 Eq,
432 MallocSizeOf,
433 PartialEq,
434 ToComputedValue,
435 ToCss,
436 ToResolvedValue,
437 ToShmem,
438 ToTyped,
439)]
440#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
441#[repr(C)]
442pub struct ItemPlacement(pub AlignFlags);
443
444impl ItemPlacement {
445 #[inline]
447 pub fn normal() -> Self {
448 Self(AlignFlags::NORMAL)
449 }
450}
451
452impl ItemPlacement {
453 pub fn parse_block<'i>(
455 _: &ParserContext,
456 input: &mut Parser<'i, '_>,
457 ) -> Result<Self, ParseError<'i>> {
458 Self::parse(input, AxisDirection::Block)
459 }
460
461 pub fn parse_inline<'i>(
463 _: &ParserContext,
464 input: &mut Parser<'i, '_>,
465 ) -> Result<Self, ParseError<'i>> {
466 Self::parse(input, AxisDirection::Inline)
467 }
468
469 fn parse<'i, 't>(
470 input: &mut Parser<'i, 't>,
471 axis: AxisDirection,
472 ) -> Result<Self, ParseError<'i>> {
473 if let Ok(baseline) = input.try_parse(parse_baseline) {
478 return Ok(Self(baseline));
479 }
480
481 if let Ok(value) = input.try_parse(parse_normal_stretch) {
483 return Ok(Self(value));
484 }
485
486 if axis == AxisDirection::Inline {
487 if let Ok(value) = input.try_parse(parse_legacy) {
489 return Ok(Self(value));
490 }
491 }
492
493 let overflow = input
495 .try_parse(parse_overflow_position)
496 .unwrap_or(AlignFlags::empty());
497 let self_position = parse_self_position(input, axis)?;
498 Ok(ItemPlacement(self_position | overflow))
499 }
500}
501
502impl SpecifiedValueInfo for ItemPlacement {
503 fn collect_completion_keywords(f: KeywordsCollectFn) {
504 list_baseline_keywords(f);
505 list_normal_stretch(f);
506 list_overflow_position_keywords(f);
507 list_self_position_keywords(f, AxisDirection::Block);
508 }
509}
510
511#[derive(
515 Clone, Copy, Debug, Deref, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem, ToTyped,
516)]
517#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
518#[repr(C)]
519pub struct JustifyItems(pub ItemPlacement);
520
521impl JustifyItems {
522 #[inline]
524 pub fn legacy() -> Self {
525 Self(ItemPlacement(AlignFlags::LEGACY))
526 }
527
528 #[inline]
530 pub fn normal() -> Self {
531 Self(ItemPlacement::normal())
532 }
533}
534
535impl Parse for JustifyItems {
536 fn parse<'i, 't>(
537 context: &ParserContext,
538 input: &mut Parser<'i, 't>,
539 ) -> Result<Self, ParseError<'i>> {
540 ItemPlacement::parse_inline(context, input).map(Self)
541 }
542}
543
544impl SpecifiedValueInfo for JustifyItems {
545 fn collect_completion_keywords(f: KeywordsCollectFn) {
546 ItemPlacement::collect_completion_keywords(f);
547 list_legacy_keywords(f); }
549}
550
551fn parse_auto_normal_stretch<'i, 't>(
553 input: &mut Parser<'i, 't>,
554) -> Result<AlignFlags, ParseError<'i>> {
555 try_match_ident_ignore_ascii_case! { input,
558 "auto" => Ok(AlignFlags::AUTO),
559 "normal" => Ok(AlignFlags::NORMAL),
560 "stretch" => Ok(AlignFlags::STRETCH),
561 }
562}
563
564fn list_auto_normal_stretch(f: KeywordsCollectFn) {
565 f(&["auto", "normal", "stretch"]);
566}
567
568fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
570 try_match_ident_ignore_ascii_case! { input,
573 "normal" => Ok(AlignFlags::NORMAL),
574 "stretch" => Ok(AlignFlags::STRETCH),
575 }
576}
577
578fn list_normal_stretch(f: KeywordsCollectFn) {
579 f(&["normal", "stretch"]);
580}
581
582fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
584 try_match_ident_ignore_ascii_case! { input,
587 "baseline" => Ok(AlignFlags::BASELINE),
588 "first" => {
589 input.expect_ident_matching("baseline")?;
590 Ok(AlignFlags::BASELINE)
591 },
592 "last" => {
593 input.expect_ident_matching("baseline")?;
594 Ok(AlignFlags::LAST_BASELINE)
595 },
596 }
597}
598
599fn list_baseline_keywords(f: KeywordsCollectFn) {
600 f(&["baseline", "first baseline", "last baseline"]);
601}
602
603fn parse_content_distribution<'i, 't>(
605 input: &mut Parser<'i, 't>,
606) -> Result<AlignFlags, ParseError<'i>> {
607 try_match_ident_ignore_ascii_case! { input,
610 "stretch" => Ok(AlignFlags::STRETCH),
611 "space-between" => Ok(AlignFlags::SPACE_BETWEEN),
612 "space-around" => Ok(AlignFlags::SPACE_AROUND),
613 "space-evenly" => Ok(AlignFlags::SPACE_EVENLY),
614 }
615}
616
617fn list_content_distribution_keywords(f: KeywordsCollectFn) {
618 f(&["stretch", "space-between", "space-around", "space-evenly"]);
619}
620
621fn parse_overflow_position<'i, 't>(
623 input: &mut Parser<'i, 't>,
624) -> Result<AlignFlags, ParseError<'i>> {
625 try_match_ident_ignore_ascii_case! { input,
628 "safe" => Ok(AlignFlags::SAFE),
629 "unsafe" => Ok(AlignFlags::UNSAFE),
630 }
631}
632
633fn list_overflow_position_keywords(f: KeywordsCollectFn) {
634 f(&["safe", "unsafe"]);
635}
636
637fn parse_self_position<'i, 't>(
639 input: &mut Parser<'i, 't>,
640 axis: AxisDirection,
641) -> Result<AlignFlags, ParseError<'i>> {
642 Ok(try_match_ident_ignore_ascii_case! { input,
645 "start" => AlignFlags::START,
646 "end" => AlignFlags::END,
647 "flex-start" => AlignFlags::FLEX_START,
648 "flex-end" => AlignFlags::FLEX_END,
649 "center" => AlignFlags::CENTER,
650 "self-start" => AlignFlags::SELF_START,
651 "self-end" => AlignFlags::SELF_END,
652 "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
653 "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
654 "anchor-center" if static_prefs::pref!("layout.css.anchor-positioning.enabled") => AlignFlags::ANCHOR_CENTER,
655 })
656}
657
658fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
659 f(&[
660 "start",
661 "end",
662 "flex-start",
663 "flex-end",
664 "center",
665 "self-start",
666 "self-end",
667 ]);
668
669 if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
670 f(&["anchor-center"]);
671 }
672
673 if axis == AxisDirection::Inline {
674 f(&["left", "right"]);
675 }
676}
677
678fn parse_left_right_center<'i, 't>(
679 input: &mut Parser<'i, 't>,
680) -> Result<AlignFlags, ParseError<'i>> {
681 Ok(try_match_ident_ignore_ascii_case! { input,
684 "left" => AlignFlags::LEFT,
685 "right" => AlignFlags::RIGHT,
686 "center" => AlignFlags::CENTER,
687 })
688}
689
690fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
692 let flags = try_match_ident_ignore_ascii_case! { input,
695 "legacy" => {
696 let flags = input.try_parse(parse_left_right_center)
697 .unwrap_or(AlignFlags::empty());
698
699 return Ok(AlignFlags::LEGACY | flags)
700 },
701 "left" => AlignFlags::LEFT,
702 "right" => AlignFlags::RIGHT,
703 "center" => AlignFlags::CENTER,
704 };
705
706 input.expect_ident_matching("legacy")?;
707 Ok(AlignFlags::LEGACY | flags)
708}
709
710fn list_legacy_keywords(f: KeywordsCollectFn) {
711 f(&["legacy", "left", "right", "center"]);
712}