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 ToTyped,
278)]
279#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
280#[repr(transparent)]
281pub struct AlignContent(pub ContentDistribution);
282
283impl Parse for AlignContent {
284 fn parse<'i, 't>(
285 _: &ParserContext,
286 input: &mut Parser<'i, 't>,
287 ) -> Result<Self, ParseError<'i>> {
288 Ok(AlignContent(ContentDistribution::parse(
291 input,
292 AxisDirection::Block,
293 )?))
294 }
295}
296
297impl SpecifiedValueInfo for AlignContent {
298 fn collect_completion_keywords(f: KeywordsCollectFn) {
299 ContentDistribution::list_keywords(f, AxisDirection::Block);
300 }
301}
302
303#[derive(
307 Clone,
308 Copy,
309 Debug,
310 Eq,
311 MallocSizeOf,
312 PartialEq,
313 ToComputedValue,
314 ToCss,
315 ToResolvedValue,
316 ToShmem,
317 ToTyped,
318)]
319#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
320#[repr(transparent)]
321pub struct JustifyContent(pub ContentDistribution);
322
323impl Parse for JustifyContent {
324 fn parse<'i, 't>(
325 _: &ParserContext,
326 input: &mut Parser<'i, 't>,
327 ) -> Result<Self, ParseError<'i>> {
328 Ok(JustifyContent(ContentDistribution::parse(
331 input,
332 AxisDirection::Inline,
333 )?))
334 }
335}
336
337impl SpecifiedValueInfo for JustifyContent {
338 fn collect_completion_keywords(f: KeywordsCollectFn) {
339 ContentDistribution::list_keywords(f, AxisDirection::Inline);
340 }
341}
342
343#[derive(
345 Clone,
346 Copy,
347 Debug,
348 Eq,
349 MallocSizeOf,
350 PartialEq,
351 ToComputedValue,
352 ToCss,
353 ToResolvedValue,
354 ToShmem,
355)]
356#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
357#[repr(transparent)]
358pub struct SelfAlignment(pub AlignFlags);
359
360impl SelfAlignment {
361 #[inline]
363 pub fn auto() -> Self {
364 SelfAlignment(AlignFlags::AUTO)
365 }
366
367 pub fn is_valid_on_both_axes(&self) -> bool {
369 match self.0.value() {
370 AlignFlags::LEFT | AlignFlags::RIGHT => false,
372
373 _ => true,
374 }
375 }
376
377 pub fn parse<'i, 't>(
379 input: &mut Parser<'i, 't>,
380 axis: AxisDirection,
381 ) -> Result<Self, ParseError<'i>> {
382 if let Ok(value) = input.try_parse(parse_baseline) {
390 return Ok(SelfAlignment(value));
391 }
392
393 if let Ok(value) = input.try_parse(parse_auto_normal_stretch) {
395 return Ok(SelfAlignment(value));
396 }
397
398 let overflow_position = input
400 .try_parse(parse_overflow_position)
401 .unwrap_or(AlignFlags::empty());
402 let self_position = parse_self_position(input, axis)?;
403 Ok(SelfAlignment(overflow_position | self_position))
404 }
405
406 fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
407 list_baseline_keywords(f);
408 list_auto_normal_stretch(f);
409 list_overflow_position_keywords(f);
410 list_self_position_keywords(f, axis);
411 }
412}
413
414#[derive(
418 Clone,
419 Copy,
420 Debug,
421 Eq,
422 MallocSizeOf,
423 PartialEq,
424 ToComputedValue,
425 ToCss,
426 ToResolvedValue,
427 ToShmem,
428 ToTyped,
429)]
430#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
431#[repr(C)]
432pub struct AlignSelf(pub SelfAlignment);
433
434impl Parse for AlignSelf {
435 fn parse<'i, 't>(
436 _: &ParserContext,
437 input: &mut Parser<'i, 't>,
438 ) -> Result<Self, ParseError<'i>> {
439 Ok(AlignSelf(SelfAlignment::parse(
442 input,
443 AxisDirection::Block,
444 )?))
445 }
446}
447
448impl SpecifiedValueInfo for AlignSelf {
449 fn collect_completion_keywords(f: KeywordsCollectFn) {
450 SelfAlignment::list_keywords(f, AxisDirection::Block);
451 }
452}
453
454#[derive(
458 Clone,
459 Copy,
460 Debug,
461 Eq,
462 MallocSizeOf,
463 PartialEq,
464 ToComputedValue,
465 ToCss,
466 ToResolvedValue,
467 ToShmem,
468 ToTyped,
469)]
470#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
471#[repr(C)]
472pub struct JustifySelf(pub SelfAlignment);
473
474impl Parse for JustifySelf {
475 fn parse<'i, 't>(
476 _: &ParserContext,
477 input: &mut Parser<'i, 't>,
478 ) -> Result<Self, ParseError<'i>> {
479 Ok(JustifySelf(SelfAlignment::parse(
482 input,
483 AxisDirection::Inline,
484 )?))
485 }
486}
487
488impl SpecifiedValueInfo for JustifySelf {
489 fn collect_completion_keywords(f: KeywordsCollectFn) {
490 SelfAlignment::list_keywords(f, AxisDirection::Inline);
491 }
492}
493
494#[derive(
498 Clone,
499 Copy,
500 Debug,
501 Eq,
502 MallocSizeOf,
503 PartialEq,
504 ToComputedValue,
505 ToCss,
506 ToResolvedValue,
507 ToShmem,
508 ToTyped,
509)]
510#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
511#[repr(C)]
512pub struct AlignItems(pub AlignFlags);
513
514impl AlignItems {
515 #[inline]
517 pub fn normal() -> Self {
518 AlignItems(AlignFlags::NORMAL)
519 }
520}
521
522impl Parse for AlignItems {
523 fn parse<'i, 't>(
526 _: &ParserContext,
527 input: &mut Parser<'i, 't>,
528 ) -> Result<Self, ParseError<'i>> {
529 if let Ok(baseline) = input.try_parse(parse_baseline) {
534 return Ok(AlignItems(baseline));
535 }
536
537 if let Ok(value) = input.try_parse(parse_normal_stretch) {
539 return Ok(AlignItems(value));
540 }
541 let overflow = input
543 .try_parse(parse_overflow_position)
544 .unwrap_or(AlignFlags::empty());
545 let self_position = parse_self_position(input, AxisDirection::Block)?;
546 Ok(AlignItems(self_position | overflow))
547 }
548}
549
550impl SpecifiedValueInfo for AlignItems {
551 fn collect_completion_keywords(f: KeywordsCollectFn) {
552 list_baseline_keywords(f);
553 list_normal_stretch(f);
554 list_overflow_position_keywords(f);
555 list_self_position_keywords(f, AxisDirection::Block);
556 }
557}
558
559#[derive(
563 Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem, ToTyped,
564)]
565#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
566#[repr(C)]
567pub struct JustifyItems(pub AlignFlags);
568
569impl JustifyItems {
570 #[inline]
572 pub fn legacy() -> Self {
573 JustifyItems(AlignFlags::LEGACY)
574 }
575
576 #[inline]
578 pub fn normal() -> Self {
579 JustifyItems(AlignFlags::NORMAL)
580 }
581}
582
583impl Parse for JustifyItems {
584 fn parse<'i, 't>(
585 _: &ParserContext,
586 input: &mut Parser<'i, 't>,
587 ) -> Result<Self, ParseError<'i>> {
588 if let Ok(baseline) = input.try_parse(parse_baseline) {
596 return Ok(JustifyItems(baseline));
597 }
598
599 if let Ok(value) = input.try_parse(parse_normal_stretch) {
601 return Ok(JustifyItems(value));
602 }
603
604 if let Ok(value) = input.try_parse(parse_legacy) {
606 return Ok(JustifyItems(value));
607 }
608
609 let overflow = input
611 .try_parse(parse_overflow_position)
612 .unwrap_or(AlignFlags::empty());
613 let self_position = parse_self_position(input, AxisDirection::Inline)?;
614 Ok(JustifyItems(overflow | self_position))
615 }
616}
617
618impl SpecifiedValueInfo for JustifyItems {
619 fn collect_completion_keywords(f: KeywordsCollectFn) {
620 list_baseline_keywords(f);
621 list_normal_stretch(f);
622 list_legacy_keywords(f);
623 list_overflow_position_keywords(f);
624 list_self_position_keywords(f, AxisDirection::Inline);
625 }
626}
627
628fn parse_auto_normal_stretch<'i, 't>(
630 input: &mut Parser<'i, 't>,
631) -> Result<AlignFlags, ParseError<'i>> {
632 try_match_ident_ignore_ascii_case! { input,
635 "auto" => Ok(AlignFlags::AUTO),
636 "normal" => Ok(AlignFlags::NORMAL),
637 "stretch" => Ok(AlignFlags::STRETCH),
638 }
639}
640
641fn list_auto_normal_stretch(f: KeywordsCollectFn) {
642 f(&["auto", "normal", "stretch"]);
643}
644
645fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
647 try_match_ident_ignore_ascii_case! { input,
650 "normal" => Ok(AlignFlags::NORMAL),
651 "stretch" => Ok(AlignFlags::STRETCH),
652 }
653}
654
655fn list_normal_stretch(f: KeywordsCollectFn) {
656 f(&["normal", "stretch"]);
657}
658
659fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
661 try_match_ident_ignore_ascii_case! { input,
664 "baseline" => Ok(AlignFlags::BASELINE),
665 "first" => {
666 input.expect_ident_matching("baseline")?;
667 Ok(AlignFlags::BASELINE)
668 },
669 "last" => {
670 input.expect_ident_matching("baseline")?;
671 Ok(AlignFlags::LAST_BASELINE)
672 },
673 }
674}
675
676fn list_baseline_keywords(f: KeywordsCollectFn) {
677 f(&["baseline", "first baseline", "last baseline"]);
678}
679
680fn parse_content_distribution<'i, 't>(
682 input: &mut Parser<'i, 't>,
683) -> Result<AlignFlags, ParseError<'i>> {
684 try_match_ident_ignore_ascii_case! { input,
687 "stretch" => Ok(AlignFlags::STRETCH),
688 "space-between" => Ok(AlignFlags::SPACE_BETWEEN),
689 "space-around" => Ok(AlignFlags::SPACE_AROUND),
690 "space-evenly" => Ok(AlignFlags::SPACE_EVENLY),
691 }
692}
693
694fn list_content_distribution_keywords(f: KeywordsCollectFn) {
695 f(&["stretch", "space-between", "space-around", "space-evenly"]);
696}
697
698fn parse_overflow_position<'i, 't>(
700 input: &mut Parser<'i, 't>,
701) -> Result<AlignFlags, ParseError<'i>> {
702 try_match_ident_ignore_ascii_case! { input,
705 "safe" => Ok(AlignFlags::SAFE),
706 "unsafe" => Ok(AlignFlags::UNSAFE),
707 }
708}
709
710fn list_overflow_position_keywords(f: KeywordsCollectFn) {
711 f(&["safe", "unsafe"]);
712}
713
714fn parse_self_position<'i, 't>(
716 input: &mut Parser<'i, 't>,
717 axis: AxisDirection,
718) -> Result<AlignFlags, ParseError<'i>> {
719 Ok(try_match_ident_ignore_ascii_case! { input,
722 "start" => AlignFlags::START,
723 "end" => AlignFlags::END,
724 "flex-start" => AlignFlags::FLEX_START,
725 "flex-end" => AlignFlags::FLEX_END,
726 "center" => AlignFlags::CENTER,
727 "self-start" => AlignFlags::SELF_START,
728 "self-end" => AlignFlags::SELF_END,
729 "left" if axis == AxisDirection::Inline => AlignFlags::LEFT,
730 "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT,
731 "anchor-center" if static_prefs::pref!("layout.css.anchor-positioning.enabled") => AlignFlags::ANCHOR_CENTER,
732 })
733}
734
735fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) {
736 f(&[
737 "start",
738 "end",
739 "flex-start",
740 "flex-end",
741 "center",
742 "self-start",
743 "self-end",
744 ]);
745
746 if static_prefs::pref!("layout.css.anchor-positioning.enabled") {
747 f(&["anchor-center"]);
748 }
749
750 if axis == AxisDirection::Inline {
751 f(&["left", "right"]);
752 }
753}
754
755fn parse_left_right_center<'i, 't>(
756 input: &mut Parser<'i, 't>,
757) -> Result<AlignFlags, ParseError<'i>> {
758 Ok(try_match_ident_ignore_ascii_case! { input,
761 "left" => AlignFlags::LEFT,
762 "right" => AlignFlags::RIGHT,
763 "center" => AlignFlags::CENTER,
764 })
765}
766
767fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> {
769 let flags = try_match_ident_ignore_ascii_case! { input,
772 "legacy" => {
773 let flags = input.try_parse(parse_left_right_center)
774 .unwrap_or(AlignFlags::empty());
775
776 return Ok(AlignFlags::LEGACY | flags)
777 },
778 "left" => AlignFlags::LEFT,
779 "right" => AlignFlags::RIGHT,
780 "center" => AlignFlags::CENTER,
781 };
782
783 input.expect_ident_matching("legacy")?;
784 Ok(AlignFlags::LEGACY | flags)
785}
786
787fn list_legacy_keywords(f: KeywordsCollectFn) {
788 f(&["legacy", "left", "right", "center"]);
789}