1use crate::derives::*;
11use crate::logical_geometry::{LogicalAxis, LogicalSide, PhysicalSide, WritingMode};
12use crate::parser::{Parse, ParserContext};
13use crate::selector_map::PrecomputedHashMap;
14use crate::str::HTML_SPACE_CHARACTERS;
15use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
16use crate::values::computed::{Context, Percentage, ToComputedValue};
17use crate::values::generics::length::GenericAnchorSizeFunction;
18use crate::values::generics::position::PositionComponent as GenericPositionComponent;
19use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
20use crate::values::generics::position::ZIndex as GenericZIndex;
21use crate::values::generics::position::{AspectRatio as GenericAspectRatio, GenericAnchorSide};
22use crate::values::generics::position::{GenericAnchorFunction, GenericInset, TreeScoped};
23use crate::values::generics::position::{IsTreeScoped, Position as GenericPosition};
24use crate::values::specified;
25use crate::values::specified::align::AlignFlags;
26use crate::values::specified::percentage::NoCalcPercentage;
27use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
28use crate::values::{AtomIdent, DashedIdent};
29use crate::Atom;
30use cssparser::{match_ignore_ascii_case, Parser};
31use num_traits::FromPrimitive;
32use selectors::parser::SelectorParseErrorKind;
33use servo_arc::Arc;
34use smallvec::{smallvec, SmallVec};
35use std::collections::hash_map::Entry;
36use std::fmt::{self, Write};
37use style_traits::arc_slice::ArcSlice;
38use style_traits::values::specified::AllowedNumericType;
39use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
40use thin_vec::ThinVec;
41
42pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
44
45pub type PositionOrAuto = GenericPositionOrAuto<Position>;
47
48pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
50
51pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;
53
54#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
56#[typed(todo_derive_fields)]
57pub enum PositionComponent<S> {
58 Center,
60 Length(LengthPercentage),
62 Side(S, Option<LengthPercentage>),
64}
65
66#[derive(
68 Clone,
69 Copy,
70 Debug,
71 Eq,
72 Hash,
73 MallocSizeOf,
74 Parse,
75 PartialEq,
76 SpecifiedValueInfo,
77 ToComputedValue,
78 ToCss,
79 ToResolvedValue,
80 ToShmem,
81)]
82#[allow(missing_docs)]
83#[repr(u8)]
84pub enum HorizontalPositionKeyword {
85 Left,
86 Right,
87}
88
89#[derive(
91 Clone,
92 Copy,
93 Debug,
94 Eq,
95 Hash,
96 MallocSizeOf,
97 Parse,
98 PartialEq,
99 SpecifiedValueInfo,
100 ToComputedValue,
101 ToCss,
102 ToResolvedValue,
103 ToShmem,
104)]
105#[allow(missing_docs)]
106#[repr(u8)]
107pub enum VerticalPositionKeyword {
108 Top,
109 Bottom,
110}
111
112impl Parse for Position {
113 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
114 let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?;
115 if position.is_three_value_syntax() {
116 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
117 }
118 Ok(position)
119 }
120}
121
122impl Position {
123 pub fn parse_three_value_quirky(
125 context: &ParserContext,
126 input: &mut Parser,
127 allow_quirks: AllowQuirks,
128 ) -> Result<Self, ParseError> {
129 match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
130 Ok(x_pos @ PositionComponent::Center) => {
131 if let Ok(y_pos) =
132 input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
133 {
134 return Ok(Self::new(x_pos, y_pos));
135 }
136 let x_pos = input
137 .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
138 .unwrap_or(x_pos);
139 let y_pos = PositionComponent::Center;
140 return Ok(Self::new(x_pos, y_pos));
141 },
142 Ok(PositionComponent::Side(x_keyword, lp)) => {
143 if input
144 .try_parse(|i| i.expect_ident_matching("center"))
145 .is_ok()
146 {
147 let x_pos = PositionComponent::Side(x_keyword, lp);
148 let y_pos = PositionComponent::Center;
149 return Ok(Self::new(x_pos, y_pos));
150 }
151 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
152 let y_lp = input
153 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
154 .ok();
155 let x_pos = PositionComponent::Side(x_keyword, lp);
156 let y_pos = PositionComponent::Side(y_keyword, y_lp);
157 return Ok(Self::new(x_pos, y_pos));
158 }
159 let x_pos = PositionComponent::Side(x_keyword, None);
160 let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length);
161 return Ok(Self::new(x_pos, y_pos));
162 },
163 Ok(x_pos @ PositionComponent::Length(_)) => {
164 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
165 let y_pos = PositionComponent::Side(y_keyword, None);
166 return Ok(Self::new(x_pos, y_pos));
167 }
168 if let Ok(y_lp) =
169 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
170 {
171 let y_pos = PositionComponent::Length(y_lp);
172 return Ok(Self::new(x_pos, y_pos));
173 }
174 let y_pos = PositionComponent::Center;
175 let _ = input.try_parse(|i| i.expect_ident_matching("center"));
176 return Ok(Self::new(x_pos, y_pos));
177 },
178 Err(_) => {},
179 }
180 let y_keyword = VerticalPositionKeyword::parse(input)?;
181 let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| {
182 let y_lp = i
183 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
184 .ok();
185 if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
186 let x_lp = i
187 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
188 .ok();
189 let x_pos = PositionComponent::Side(x_keyword, x_lp);
190 return Ok((y_lp, x_pos));
191 };
192 i.expect_ident_matching("center")?;
193 let x_pos = PositionComponent::Center;
194 Ok((y_lp, x_pos))
195 });
196 if let Ok((y_lp, x_pos)) = lp_and_x_pos {
197 let y_pos = PositionComponent::Side(y_keyword, y_lp);
198 return Ok(Self::new(x_pos, y_pos));
199 }
200 let x_pos = PositionComponent::Center;
201 let y_pos = PositionComponent::Side(y_keyword, None);
202 Ok(Self::new(x_pos, y_pos))
203 }
204
205 #[inline]
207 pub fn center() -> Self {
208 Self::new(PositionComponent::Center, PositionComponent::Center)
209 }
210
211 #[inline]
213 fn is_three_value_syntax(&self) -> bool {
214 self.horizontal.component_count() != self.vertical.component_count()
215 }
216}
217
218impl ToCss for Position {
219 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
220 where
221 W: Write,
222 {
223 match (&self.horizontal, &self.vertical) {
224 (x_pos @ &PositionComponent::Side(_, Some(_)), PositionComponent::Length(y_lp)) => {
225 x_pos.to_css(dest)?;
226 dest.write_str(" top ")?;
227 y_lp.to_css(dest)
228 },
229 (PositionComponent::Length(x_lp), y_pos @ &PositionComponent::Side(_, Some(_))) => {
230 dest.write_str("left ")?;
231 x_lp.to_css(dest)?;
232 dest.write_char(' ')?;
233 y_pos.to_css(dest)
234 },
235 (x_pos, y_pos) => {
236 x_pos.to_css(dest)?;
237 dest.write_char(' ')?;
238 y_pos.to_css(dest)
239 },
240 }
241 }
242}
243
244impl<S: Parse> Parse for PositionComponent<S> {
245 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
246 Self::parse_quirky(context, input, AllowQuirks::No)
247 }
248}
249
250impl<S: Parse> PositionComponent<S> {
251 pub fn parse_quirky(
253 context: &ParserContext,
254 input: &mut Parser,
255 allow_quirks: AllowQuirks,
256 ) -> Result<Self, ParseError> {
257 if input
258 .try_parse(|i| i.expect_ident_matching("center"))
259 .is_ok()
260 {
261 return Ok(PositionComponent::Center);
262 }
263 if let Ok(lp) =
264 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
265 {
266 return Ok(PositionComponent::Length(lp));
267 }
268 let keyword = S::parse(context, input)?;
269 let lp = input
270 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
271 .ok();
272 Ok(PositionComponent::Side(keyword, lp))
273 }
274}
275
276impl<S> GenericPositionComponent for PositionComponent<S> {
277 fn is_center(&self) -> bool {
278 match *self {
279 PositionComponent::Center => true,
280 PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.get() == 0.5,
281 PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => {
283 per.get() == 0.5
284 },
285 _ => false,
286 }
287 }
288}
289
290impl<S> PositionComponent<S> {
291 pub fn zero() -> Self {
293 PositionComponent::Length(LengthPercentage::Percentage(NoCalcPercentage::zero()))
294 }
295
296 fn component_count(&self) -> usize {
298 match *self {
299 PositionComponent::Length(..) | PositionComponent::Center => 1,
300 PositionComponent::Side(_, ref lp) => {
301 if lp.is_some() {
302 2
303 } else {
304 1
305 }
306 },
307 }
308 }
309}
310
311impl<S: Side> ToComputedValue for PositionComponent<S> {
312 type ComputedValue = ComputedLengthPercentage;
313
314 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
315 match *self {
316 PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)),
317 PositionComponent::Side(ref keyword, None) => {
318 let p = Percentage(if keyword.is_start() { 0. } else { 1. });
319 ComputedLengthPercentage::new_percent(p)
320 },
321 PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
322 let length = length.to_computed_value(context);
323 ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All)
325 },
326 PositionComponent::Side(_, Some(ref length))
327 | PositionComponent::Length(ref length) => length.to_computed_value(context),
328 }
329 }
330
331 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
332 PositionComponent::Length(ToComputedValue::from_computed_value(computed))
333 }
334}
335
336impl<S: Side> PositionComponent<S> {
337 pub fn initial_specified_value() -> Self {
339 PositionComponent::Side(S::start(), None)
340 }
341}
342
343#[derive(
345 Animate,
346 Clone,
347 Debug,
348 MallocSizeOf,
349 PartialEq,
350 SpecifiedValueInfo,
351 ToComputedValue,
352 ToCss,
353 ToResolvedValue,
354 ToShmem,
355 ToTyped,
356)]
357#[css(comma)]
358#[repr(transparent)]
359#[typed(todo_derive_fields)]
360pub struct AnchorNameIdent(
361 #[css(iterable, if_empty = "none")]
362 #[ignore_malloc_size_of = "Arc"]
363 #[animation(constant)]
364 pub crate::ArcSlice<DashedIdent>,
365);
366
367impl AnchorNameIdent {
368 pub fn none() -> Self {
370 Self(Default::default())
371 }
372}
373
374impl Parse for AnchorNameIdent {
375 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
376 let first = input.expect_ident()?;
377 if first.eq_ignore_ascii_case("none") {
378 return Ok(Self::none());
379 }
380 let mut idents: SmallVec<[DashedIdent; 4]> = smallvec![DashedIdent::from_ident(first,)?];
383 while input.try_parse(|input| input.expect_comma()).is_ok() {
384 idents.push(DashedIdent::parse(context, input)?);
385 }
386 Ok(AnchorNameIdent(ArcSlice::from_iter(idents.drain(..))))
387 }
388}
389
390impl IsTreeScoped for AnchorNameIdent {
391 fn is_tree_scoped(&self) -> bool {
392 !self.0.is_empty()
393 }
394}
395
396pub type AnchorName = TreeScoped<AnchorNameIdent>;
398
399impl AnchorName {
400 pub fn none() -> Self {
402 Self::with_default_level(AnchorNameIdent::none())
403 }
404}
405
406#[derive(
408 Clone,
409 Debug,
410 MallocSizeOf,
411 PartialEq,
412 SpecifiedValueInfo,
413 ToComputedValue,
414 ToCss,
415 ToResolvedValue,
416 ToShmem,
417 ToTyped,
418)]
419#[repr(transparent)]
420#[css(comma)]
421#[typed(todo_derive_fields)]
422#[value_info(other_values = "all")]
423pub struct ScopedNameList(
424 #[css(iterable, if_empty = "none")]
426 #[ignore_malloc_size_of = "Arc"]
427 crate::ArcSlice<AtomIdent>,
428);
429
430impl ScopedNameList {
431 pub fn none() -> Self {
433 Self(crate::ArcSlice::default())
434 }
435
436 pub fn is_none(&self) -> bool {
438 self.0.is_empty()
439 }
440
441 pub fn all() -> Self {
443 static ALL: std::sync::LazyLock<ScopedNameList> = std::sync::LazyLock::new(|| {
444 ScopedNameList(crate::ArcSlice::from_iter_leaked(std::iter::once(
445 AtomIdent::new(atom!("all")),
446 )))
447 });
448 ALL.clone()
449 }
450}
451
452impl Parse for ScopedNameList {
453 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
454 let first = input.expect_ident()?;
455 if first.eq_ignore_ascii_case("none") {
456 return Ok(Self::none());
457 }
458 if first.eq_ignore_ascii_case("all") {
459 return Ok(Self::all());
460 }
461 let mut idents = SmallVec::<[AtomIdent; 8]>::new();
464 idents.push(AtomIdent::new(DashedIdent::from_ident(first)?.0));
465 while input.try_parse(|input| input.expect_comma()).is_ok() {
466 idents.push(AtomIdent::new(DashedIdent::parse(context, input)?.0));
467 }
468 Ok(Self(ArcSlice::from_iter(idents.drain(..))))
469 }
470}
471
472impl IsTreeScoped for ScopedNameList {
473 fn is_tree_scoped(&self) -> bool {
474 !self.is_none()
475 }
476}
477
478pub type ScopedName = TreeScoped<ScopedNameList>;
481
482impl ScopedName {
483 pub fn none() -> Self {
485 Self::with_default_level(ScopedNameList::none())
486 }
487
488 pub fn is_none(&self) -> bool {
490 self.value.is_none()
491 }
492}
493
494#[derive(
496 Clone,
497 Debug,
498 MallocSizeOf,
499 Parse,
500 PartialEq,
501 SpecifiedValueInfo,
502 ToComputedValue,
503 ToCss,
504 ToResolvedValue,
505 ToShmem,
506 ToTyped,
507)]
508#[repr(u8)]
509#[typed(todo_derive_fields)]
510pub enum PositionAnchorKeyword {
511 Normal,
513 None,
515 Auto,
517 Ident(DashedIdent),
519}
520
521impl IsTreeScoped for PositionAnchorKeyword {
522 fn is_tree_scoped(&self) -> bool {
523 match *self {
524 Self::Normal | Self::None | Self::Auto => false,
525 Self::Ident(_) => true,
526 }
527 }
528}
529
530pub type PositionAnchor = TreeScoped<PositionAnchorKeyword>;
532
533impl PositionAnchor {
534 pub fn normal() -> Self {
536 Self::with_default_level(PositionAnchorKeyword::Normal)
537 }
538}
539
540#[derive(
541 Clone,
542 Copy,
543 Debug,
544 Eq,
545 MallocSizeOf,
546 Parse,
547 PartialEq,
548 Serialize,
549 SpecifiedValueInfo,
550 ToComputedValue,
551 ToCss,
552 ToResolvedValue,
553 ToShmem,
554)]
555#[repr(u8)]
556pub enum PositionTryFallbacksTryTacticKeyword {
558 FlipBlock,
560 FlipInline,
562 FlipStart,
564 FlipX,
566 FlipY,
568}
569
570#[derive(
571 Clone,
572 Debug,
573 Default,
574 Eq,
575 MallocSizeOf,
576 PartialEq,
577 SpecifiedValueInfo,
578 ToComputedValue,
579 ToCss,
580 ToResolvedValue,
581 ToShmem,
582)]
583#[repr(transparent)]
584pub struct PositionTryFallbacksTryTactic(
589 #[css(iterable)] pub ThinVec<PositionTryFallbacksTryTacticKeyword>,
590);
591
592impl Parse for PositionTryFallbacksTryTactic {
593 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
594 let mut result = ThinVec::with_capacity(5);
595 for _ in 0..5 {
597 if let Ok(kw) = input.try_parse(PositionTryFallbacksTryTacticKeyword::parse) {
598 if result.contains(&kw) {
599 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
600 }
601 result.push(kw);
602 } else {
603 break;
604 }
605 }
606 if result.is_empty() {
607 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
608 }
609 Ok(Self(result))
610 }
611}
612
613impl PositionTryFallbacksTryTactic {
614 #[inline]
616 pub fn is_empty(&self) -> bool {
617 self.0.is_empty()
618 }
619
620 #[inline]
622 pub fn iter(&self) -> impl Iterator<Item = &PositionTryFallbacksTryTacticKeyword> {
623 self.0.iter()
624 }
625}
626
627#[derive(
628 Clone,
629 Debug,
630 MallocSizeOf,
631 PartialEq,
632 SpecifiedValueInfo,
633 ToComputedValue,
634 ToCss,
635 ToResolvedValue,
636 ToShmem,
637)]
638#[repr(C)]
639pub struct DashedIdentAndOrTryTactic {
642 pub ident: DashedIdent,
644 pub try_tactic: PositionTryFallbacksTryTactic,
646}
647
648impl Parse for DashedIdentAndOrTryTactic {
649 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
650 let mut result = Self {
651 ident: DashedIdent::empty(),
652 try_tactic: PositionTryFallbacksTryTactic::default(),
653 };
654
655 loop {
656 if result.ident.is_empty() {
657 if let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i)) {
658 result.ident = ident;
659 continue;
660 }
661 }
662 if result.try_tactic.is_empty() {
663 if let Ok(try_tactic) =
664 input.try_parse(|i| PositionTryFallbacksTryTactic::parse(context, i))
665 {
666 result.try_tactic = try_tactic;
667 continue;
668 }
669 }
670 break;
671 }
672
673 if result.ident.is_empty() && result.try_tactic.is_empty() {
674 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
675 }
676 Ok(result)
677 }
678}
679
680#[derive(
681 Clone,
682 Debug,
683 MallocSizeOf,
684 Parse,
685 PartialEq,
686 SpecifiedValueInfo,
687 ToComputedValue,
688 ToCss,
689 ToResolvedValue,
690 ToShmem,
691)]
692#[repr(u8)]
693pub enum PositionTryFallbacksItem {
696 IdentAndOrTactic(DashedIdentAndOrTryTactic),
698 #[parse(parse_fn = "PositionArea::parse_except_none")]
699 PositionArea(PositionArea),
701}
702
703#[derive(
704 Clone,
705 Debug,
706 Default,
707 MallocSizeOf,
708 PartialEq,
709 SpecifiedValueInfo,
710 ToComputedValue,
711 ToCss,
712 ToResolvedValue,
713 ToShmem,
714 ToTyped,
715)]
716#[css(comma)]
717#[repr(C)]
718#[typed(todo_derive_fields)]
719pub struct PositionTryFallbacksList(
721 #[css(iterable, if_empty = "none")]
722 #[ignore_malloc_size_of = "Arc"]
723 pub crate::ArcSlice<PositionTryFallbacksItem>,
724);
725
726impl IsTreeScoped for PositionTryFallbacksList {
727 fn is_tree_scoped(&self) -> bool {
728 !self.is_none()
729 }
730}
731
732impl PositionTryFallbacksList {
733 #[inline]
734 pub fn none() -> Self {
736 Self(Default::default())
737 }
738
739 pub fn is_none(&self) -> bool {
741 self.0.is_empty()
742 }
743}
744
745impl Parse for PositionTryFallbacksList {
746 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
747 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
748 return Ok(Self::none());
749 }
750 let mut items: SmallVec<[PositionTryFallbacksItem; 4]> =
753 smallvec![PositionTryFallbacksItem::parse(context, input)?];
754 while input.try_parse(|input| input.expect_comma()).is_ok() {
755 items.push(PositionTryFallbacksItem::parse(context, input)?);
756 }
757 Ok(Self(ArcSlice::from_iter(items.drain(..))))
758 }
759}
760
761pub type PositionTryFallbacks = TreeScoped<PositionTryFallbacksList>;
763
764impl PositionTryFallbacks {
765 pub fn none() -> Self {
767 Self::with_default_level(PositionTryFallbacksList::none())
768 }
769}
770
771#[derive(
773 Clone,
774 Copy,
775 Debug,
776 Default,
777 Eq,
778 MallocSizeOf,
779 Parse,
780 PartialEq,
781 SpecifiedValueInfo,
782 ToComputedValue,
783 ToCss,
784 ToResolvedValue,
785 ToShmem,
786 ToTyped,
787)]
788#[repr(u8)]
789pub enum PositionTryOrder {
790 #[default]
791 Normal,
793 MostWidth,
795 MostHeight,
797 MostBlockSize,
799 MostInlineSize,
801}
802
803impl PositionTryOrder {
804 #[inline]
805 pub fn normal() -> Self {
807 Self::Normal
808 }
809
810 pub fn is_normal(&self) -> bool {
812 *self == Self::Normal
813 }
814}
815
816#[derive(
817 Clone,
818 Copy,
819 Debug,
820 Eq,
821 MallocSizeOf,
822 Parse,
823 PartialEq,
824 Serialize,
825 SpecifiedValueInfo,
826 ToComputedValue,
827 ToCss,
828 ToResolvedValue,
829 ToShmem,
830 ToTyped,
831)]
832#[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))]
833#[repr(C)]
834pub struct PositionVisibility(u8);
836bitflags! {
837 impl PositionVisibility: u8 {
838 const ALWAYS = 0;
840 const ANCHORS_VALID = 1 << 0;
842 const ANCHORS_VISIBLE = 1 << 1;
844 const NO_OVERFLOW = 1 << 2;
846 }
847}
848
849impl Default for PositionVisibility {
850 fn default() -> Self {
851 Self::ALWAYS
852 }
853}
854
855impl PositionVisibility {
856 #[inline]
857 pub fn always() -> Self {
859 Self::ALWAYS
860 }
861}
862
863#[repr(u8)]
866#[derive(Clone, Copy, Debug, Eq, PartialEq)]
867pub enum PositionAreaType {
868 Physical,
870 Logical,
872 SelfLogical,
874 Inferred,
876 SelfInferred,
878 Common,
880 None,
882}
883
884#[repr(u8)]
891#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
892#[allow(missing_docs)]
893pub enum PositionAreaAxis {
894 Horizontal = 0b000,
895 Vertical = 0b001,
896
897 X = 0b010,
898 Y = 0b011,
899
900 Block = 0b110,
901 Inline = 0b111,
902
903 Inferred = 0b100,
904 None = 0b101,
905}
906
907impl PositionAreaAxis {
908 pub fn is_physical(self) -> bool {
910 (self as u8 & 0b100) == 0
911 }
912
913 fn is_flow_relative_direction(self) -> bool {
915 self == Self::Inferred || (self as u8 & 0b10) != 0
916 }
917
918 fn is_canonically_first(self) -> bool {
920 self != Self::Inferred && (self as u8) & 1 == 0
921 }
922
923 #[allow(unused)]
924 fn flip(self) -> Self {
925 if matches!(self, Self::Inferred | Self::None) {
926 return self;
927 }
928 Self::from_u8(self as u8 ^ 1u8).unwrap()
929 }
930
931 fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> {
932 Some(match self {
933 PositionAreaAxis::Horizontal | PositionAreaAxis::X => {
934 if wm.is_vertical() {
935 LogicalAxis::Block
936 } else {
937 LogicalAxis::Inline
938 }
939 },
940 PositionAreaAxis::Vertical | PositionAreaAxis::Y => {
941 if wm.is_vertical() {
942 LogicalAxis::Inline
943 } else {
944 LogicalAxis::Block
945 }
946 },
947 PositionAreaAxis::Block => LogicalAxis::Block,
948 PositionAreaAxis::Inline => LogicalAxis::Inline,
949 PositionAreaAxis::Inferred => inferred,
950 PositionAreaAxis::None => return None,
951 })
952 }
953}
954
955#[repr(u8)]
958#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
959pub enum PositionAreaTrack {
960 Start = 0b001,
962 SpanStart = 0b011,
964 End = 0b100,
966 SpanEnd = 0b110,
968 Center = 0b010,
970 SpanAll = 0b111,
972}
973
974impl PositionAreaTrack {
975 fn flip(self) -> Self {
976 match self {
977 Self::Start => Self::End,
978 Self::SpanStart => Self::SpanEnd,
979 Self::End => Self::Start,
980 Self::SpanEnd => Self::SpanStart,
981 Self::Center | Self::SpanAll => self,
982 }
983 }
984
985 fn start(self) -> bool {
986 self as u8 & 1 != 0
987 }
988}
989
990pub const AXIS_SHIFT: usize = 3;
992pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT;
994pub const TRACK_MASK: u8 = 0b111u8;
996pub const SELF_WM: u8 = 1u8 << 6;
998
999#[derive(
1000 Clone,
1001 Copy,
1002 Debug,
1003 Default,
1004 Eq,
1005 MallocSizeOf,
1006 Parse,
1007 PartialEq,
1008 SpecifiedValueInfo,
1009 ToComputedValue,
1010 ToCss,
1011 ToResolvedValue,
1012 ToShmem,
1013 FromPrimitive,
1014)]
1015#[allow(missing_docs)]
1016#[repr(u8)]
1017pub enum PositionAreaKeyword {
1022 #[default]
1023 None = (PositionAreaAxis::None as u8) << AXIS_SHIFT,
1024
1025 Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8,
1027 SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8,
1028
1029 Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1031 End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1032 SpanStart =
1033 ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1034 SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1035
1036 Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1038 Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1039 Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1040 Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1041
1042 XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1044 XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1045 YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1046 YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1047
1048 BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1050 BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1051 InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1052 InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1053
1054 SpanLeft =
1056 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1057 SpanRight =
1058 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1059 SpanTop =
1060 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1061 SpanBottom =
1062 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1063
1064 SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1066 SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1067 SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1068 SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1069
1070 SpanBlockStart =
1072 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1073 SpanBlockEnd =
1074 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1075 SpanInlineStart =
1076 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1077 SpanInlineEnd =
1078 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1079
1080 SelfStart = SELF_WM | (Self::Start as u8),
1082 SelfEnd = SELF_WM | (Self::End as u8),
1083 SpanSelfStart = SELF_WM | (Self::SpanStart as u8),
1084 SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8),
1085
1086 SelfXStart = SELF_WM | (Self::XStart as u8),
1087 SelfXEnd = SELF_WM | (Self::XEnd as u8),
1088 SelfYStart = SELF_WM | (Self::YStart as u8),
1089 SelfYEnd = SELF_WM | (Self::YEnd as u8),
1090 SelfBlockStart = SELF_WM | (Self::BlockStart as u8),
1091 SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8),
1092 SelfInlineStart = SELF_WM | (Self::InlineStart as u8),
1093 SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8),
1094
1095 SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8),
1096 SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8),
1097 SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8),
1098 SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8),
1099 SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8),
1100 SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8),
1101 SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8),
1102 SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8),
1103}
1104
1105impl PositionAreaKeyword {
1106 #[inline]
1108 pub fn none() -> Self {
1109 Self::None
1110 }
1111
1112 pub fn is_none(&self) -> bool {
1114 *self == Self::None
1115 }
1116
1117 pub fn self_wm(self) -> bool {
1119 (self as u8 & SELF_WM) != 0
1120 }
1121
1122 pub fn axis(self) -> PositionAreaAxis {
1124 PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap()
1125 }
1126
1127 pub fn with_axis(self, axis: PositionAreaAxis) -> Self {
1129 Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap()
1130 }
1131
1132 pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self {
1134 if self.axis() == PositionAreaAxis::Inferred {
1135 self.with_axis(axis)
1136 } else {
1137 self
1138 }
1139 }
1140
1141 pub fn track(self) -> Option<PositionAreaTrack> {
1143 let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK);
1144 debug_assert_eq!(
1145 result.is_none(),
1146 self.is_none(),
1147 "Only the none keyword has no track"
1148 );
1149 result
1150 }
1151
1152 fn group_type(self) -> PositionAreaType {
1153 let axis = self.axis();
1154 if axis == PositionAreaAxis::None {
1155 if self.is_none() {
1156 return PositionAreaType::None;
1157 }
1158 return PositionAreaType::Common;
1159 }
1160 if axis == PositionAreaAxis::Inferred {
1161 return if self.self_wm() {
1162 PositionAreaType::SelfInferred
1163 } else {
1164 PositionAreaType::Inferred
1165 };
1166 }
1167 if axis.is_physical() {
1168 return PositionAreaType::Physical;
1169 }
1170 if self.self_wm() {
1171 PositionAreaType::SelfLogical
1172 } else {
1173 PositionAreaType::Logical
1174 }
1175 }
1176
1177 fn to_physical(
1178 self,
1179 cb_wm: WritingMode,
1180 self_wm: WritingMode,
1181 inferred_axis: LogicalAxis,
1182 ) -> Self {
1183 let wm = if self.self_wm() { self_wm } else { cb_wm };
1184 let axis = self.axis();
1185 if !axis.is_flow_relative_direction() {
1186 return self;
1187 }
1188 let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else {
1189 return self;
1190 };
1191 let Some(track) = self.track() else {
1192 debug_assert!(false, "How did we end up with no track here? {self:?}");
1193 return self;
1194 };
1195 let start = track.start();
1196 let logical_side = match logical_axis {
1197 LogicalAxis::Block => {
1198 if start {
1199 LogicalSide::BlockStart
1200 } else {
1201 LogicalSide::BlockEnd
1202 }
1203 },
1204 LogicalAxis::Inline => {
1205 if start {
1206 LogicalSide::InlineStart
1207 } else {
1208 LogicalSide::InlineEnd
1209 }
1210 },
1211 };
1212 let physical_side = logical_side.to_physical(wm);
1213 let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left);
1214 let new_track = if physical_start != start {
1215 track.flip()
1216 } else {
1217 track
1218 };
1219 let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) {
1220 PositionAreaAxis::Vertical
1221 } else {
1222 PositionAreaAxis::Horizontal
1223 };
1224 Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap()
1225 }
1226
1227 fn flip_track(self) -> Self {
1228 let Some(old_track) = self.track() else {
1229 return self;
1230 };
1231 let new_track = old_track.flip();
1232 Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap()
1233 }
1234
1235 pub fn to_self_alignment(self, axis: LogicalAxis, cb_wm: &WritingMode) -> Option<AlignFlags> {
1243 let track = self.track()?;
1244 Some(match track {
1245 PositionAreaTrack::Center => AlignFlags::CENTER,
1247 PositionAreaTrack::SpanAll => AlignFlags::ANCHOR_CENTER,
1249 _ => {
1252 debug_assert_eq!(self.group_type(), PositionAreaType::Physical);
1253 if axis == LogicalAxis::Inline {
1254 if track.start() == cb_wm.intersects(WritingMode::INLINE_REVERSED) {
1258 AlignFlags::START
1259 } else {
1260 AlignFlags::END
1261 }
1262 } else {
1263 if track.start() == cb_wm.is_vertical_rl() {
1266 AlignFlags::START
1267 } else {
1268 AlignFlags::END
1269 }
1270 }
1271 },
1272 })
1273 }
1274}
1275
1276#[derive(
1277 Clone,
1278 Copy,
1279 Debug,
1280 Eq,
1281 MallocSizeOf,
1282 PartialEq,
1283 SpecifiedValueInfo,
1284 ToCss,
1285 ToResolvedValue,
1286 ToShmem,
1287 ToTyped,
1288)]
1289#[repr(C)]
1290#[typed(todo_derive_fields)]
1291pub struct PositionArea {
1293 pub first: PositionAreaKeyword,
1295 #[css(skip_if = "PositionAreaKeyword::is_none")]
1297 pub second: PositionAreaKeyword,
1298}
1299
1300impl PositionArea {
1301 #[inline]
1303 pub fn none() -> Self {
1304 Self {
1305 first: PositionAreaKeyword::None,
1306 second: PositionAreaKeyword::None,
1307 }
1308 }
1309
1310 #[inline]
1312 pub fn is_none(&self) -> bool {
1313 self.first.is_none()
1314 }
1315
1316 pub fn parse_except_none(
1318 context: &ParserContext,
1319 input: &mut Parser,
1320 ) -> Result<Self, ParseError> {
1321 Self::parse_internal(context, input, false)
1322 }
1323
1324 pub fn get_type(&self) -> PositionAreaType {
1326 let first = self.first.group_type();
1327 let second = self.second.group_type();
1328 if matches!(second, PositionAreaType::None | PositionAreaType::Common) {
1329 return first;
1330 }
1331 if first == PositionAreaType::Common {
1332 return second;
1333 }
1334 if first != second {
1335 return PositionAreaType::None;
1336 }
1337 let first_axis = self.first.axis();
1338 if first_axis != PositionAreaAxis::Inferred
1339 && first_axis.is_canonically_first() == self.second.axis().is_canonically_first()
1340 {
1341 return PositionAreaType::None;
1342 }
1343 first
1344 }
1345
1346 fn parse_internal(
1347 _: &ParserContext,
1348 input: &mut Parser,
1349 allow_none: bool,
1350 ) -> Result<Self, ParseError> {
1351 let mut first = PositionAreaKeyword::parse(input)?;
1352 if first.is_none() {
1353 if allow_none {
1354 return Ok(Self::none());
1355 }
1356 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1357 }
1358
1359 let second = input.try_parse(PositionAreaKeyword::parse);
1360 if let Ok(PositionAreaKeyword::None) = second {
1361 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1363 }
1364 let mut second = second.unwrap_or(PositionAreaKeyword::None);
1365 if second.is_none() {
1366 return Ok(Self { first, second });
1372 }
1373
1374 let pair_type = Self { first, second }.get_type();
1375 if pair_type == PositionAreaType::None {
1376 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1378 }
1379 if matches!(
1382 pair_type,
1383 PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical
1384 ) {
1385 if second == PositionAreaKeyword::SpanAll {
1386 second = PositionAreaKeyword::None;
1389 } else if first == PositionAreaKeyword::SpanAll {
1390 first = second;
1391 second = PositionAreaKeyword::None;
1392 }
1393 }
1394 if first == second {
1395 second = PositionAreaKeyword::None;
1396 }
1397 let mut result = Self { first, second };
1398 result.canonicalize_order();
1399 Ok(result)
1400 }
1401
1402 fn canonicalize_order(&mut self) {
1403 let first_axis = self.first.axis();
1404 if first_axis.is_canonically_first() || self.second.is_none() {
1405 return;
1406 }
1407 let second_axis = self.second.axis();
1408 if first_axis == second_axis {
1409 return;
1411 }
1412 if second_axis.is_canonically_first()
1413 || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred)
1414 {
1415 std::mem::swap(&mut self.first, &mut self.second);
1416 }
1417 }
1418
1419 fn make_missing_second_explicit(&mut self) {
1420 if !self.second.is_none() {
1421 return;
1422 }
1423 let axis = self.first.axis();
1424 if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) {
1425 self.second = self.first;
1426 return;
1427 }
1428 self.second = PositionAreaKeyword::SpanAll;
1429 if !axis.is_canonically_first() {
1430 std::mem::swap(&mut self.first, &mut self.second);
1431 }
1432 }
1433
1434 pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self {
1436 self.make_missing_second_explicit();
1437 if self.first.axis() == PositionAreaAxis::None
1443 && self.second.axis() == PositionAreaAxis::None
1444 && !cb_wm.is_vertical()
1445 {
1446 std::mem::swap(&mut self.first, &mut self.second);
1447 } else {
1448 self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block);
1449 self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline);
1450 self.canonicalize_order();
1451 }
1452 self
1453 }
1454
1455 fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) {
1456 if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) {
1457 self.first = self.first.flip_track();
1458 } else {
1459 self.second = self.second.flip_track();
1460 }
1461 }
1462
1463 fn flip_start(&mut self) {
1464 self.first = self.first.with_axis(self.first.axis().flip());
1465 self.second = self.second.with_axis(self.second.axis().flip());
1466 }
1467
1468 pub fn with_tactic(
1470 mut self,
1471 wm: WritingMode,
1472 tactic: PositionTryFallbacksTryTacticKeyword,
1473 ) -> Self {
1474 self.make_missing_second_explicit();
1475 let axis_to_flip = match tactic {
1476 PositionTryFallbacksTryTacticKeyword::FlipStart => {
1477 self.flip_start();
1478 return self;
1479 },
1480 PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block,
1481 PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline,
1482 PositionTryFallbacksTryTacticKeyword::FlipX => {
1483 if wm.is_horizontal() {
1484 LogicalAxis::Inline
1485 } else {
1486 LogicalAxis::Block
1487 }
1488 },
1489 PositionTryFallbacksTryTacticKeyword::FlipY => {
1490 if wm.is_vertical() {
1491 LogicalAxis::Inline
1492 } else {
1493 LogicalAxis::Block
1494 }
1495 },
1496 };
1497 self.flip_logical_axis(wm, axis_to_flip);
1498 self
1499 }
1500}
1501
1502impl Parse for PositionArea {
1503 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1504 Self::parse_internal(context, input, true)
1505 }
1506}
1507
1508pub trait Side {
1510 fn start() -> Self;
1512
1513 fn is_start(&self) -> bool;
1515}
1516
1517impl Side for HorizontalPositionKeyword {
1518 #[inline]
1519 fn start() -> Self {
1520 HorizontalPositionKeyword::Left
1521 }
1522
1523 #[inline]
1524 fn is_start(&self) -> bool {
1525 *self == Self::start()
1526 }
1527}
1528
1529impl Side for VerticalPositionKeyword {
1530 #[inline]
1531 fn start() -> Self {
1532 VerticalPositionKeyword::Top
1533 }
1534
1535 #[inline]
1536 fn is_start(&self) -> bool {
1537 *self == Self::start()
1538 }
1539}
1540
1541#[derive(
1545 Clone,
1546 Copy,
1547 Debug,
1548 Eq,
1549 MallocSizeOf,
1550 Parse,
1551 PartialEq,
1552 SpecifiedValueInfo,
1553 ToComputedValue,
1554 ToResolvedValue,
1555 ToShmem,
1556 ToTyped,
1557)]
1558#[css(bitflags(
1559 mixed = "row,column,dense",
1560 validate_mixed = "Self::validate_and_simplify"
1561))]
1562#[repr(C)]
1563pub struct GridAutoFlow(u8);
1564bitflags! {
1565 impl GridAutoFlow: u8 {
1566 const ROW = 1 << 0;
1568 const COLUMN = 1 << 1;
1570 const DENSE = 1 << 2;
1572 }
1573}
1574
1575impl GridAutoFlow {
1576 fn validate_and_simplify(&mut self) -> bool {
1578 if self.contains(Self::ROW | Self::COLUMN) {
1579 return false;
1581 }
1582 if *self == Self::DENSE {
1583 self.insert(Self::ROW);
1585 }
1586 true
1587 }
1588}
1589
1590impl ToCss for GridAutoFlow {
1591 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1592 where
1593 W: Write,
1594 {
1595 let dense = self.intersects(Self::DENSE);
1596 if self.intersects(Self::ROW) {
1597 return if dense {
1598 dest.write_str("dense")
1599 } else {
1600 dest.write_str("row")
1601 };
1602 }
1603 debug_assert!(self.intersects(Self::COLUMN));
1604 if dense {
1605 dest.write_str("column dense")
1606 } else {
1607 dest.write_str("column")
1608 }
1609 }
1610}
1611
1612#[repr(u8)]
1613#[derive(
1614 Clone,
1615 Copy,
1616 Debug,
1617 Eq,
1618 MallocSizeOf,
1619 PartialEq,
1620 SpecifiedValueInfo,
1621 ToComputedValue,
1622 ToCss,
1623 ToResolvedValue,
1624 ToShmem,
1625)]
1626pub enum MasonryPlacement {
1628 Pack,
1630 Next,
1632}
1633
1634#[repr(u8)]
1635#[derive(
1636 Clone,
1637 Copy,
1638 Debug,
1639 Eq,
1640 MallocSizeOf,
1641 PartialEq,
1642 SpecifiedValueInfo,
1643 ToComputedValue,
1644 ToCss,
1645 ToResolvedValue,
1646 ToShmem,
1647)]
1648pub enum MasonryItemOrder {
1650 DefiniteFirst,
1652 Ordered,
1654}
1655
1656#[derive(
1657 Clone,
1658 Copy,
1659 Debug,
1660 Eq,
1661 MallocSizeOf,
1662 PartialEq,
1663 SpecifiedValueInfo,
1664 ToComputedValue,
1665 ToCss,
1666 ToResolvedValue,
1667 ToShmem,
1668 ToTyped,
1669)]
1670#[repr(C)]
1671#[typed(todo_derive_fields)]
1672pub struct MasonryAutoFlow {
1675 #[css(contextual_skip_if = "is_pack_with_non_default_order")]
1677 pub placement: MasonryPlacement,
1678 #[css(skip_if = "is_item_order_definite_first")]
1680 pub order: MasonryItemOrder,
1681}
1682
1683#[inline]
1684fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
1685 *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
1686}
1687
1688#[inline]
1689fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
1690 *order == MasonryItemOrder::DefiniteFirst
1691}
1692
1693impl MasonryAutoFlow {
1694 #[inline]
1695 pub fn initial() -> MasonryAutoFlow {
1697 MasonryAutoFlow {
1698 placement: MasonryPlacement::Pack,
1699 order: MasonryItemOrder::DefiniteFirst,
1700 }
1701 }
1702}
1703
1704impl Parse for MasonryAutoFlow {
1705 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<MasonryAutoFlow, ParseError> {
1707 let mut value = MasonryAutoFlow::initial();
1708 let mut got_placement = false;
1709 let mut got_order = false;
1710 while !input.is_exhausted() {
1711 let ident = input.expect_ident()?;
1712 let success = match_ignore_ascii_case! { &ident,
1713 "pack" if !got_placement => {
1714 got_placement = true;
1715 true
1716 },
1717 "next" if !got_placement => {
1718 value.placement = MasonryPlacement::Next;
1719 got_placement = true;
1720 true
1721 },
1722 "definite-first" if !got_order => {
1723 got_order = true;
1724 true
1725 },
1726 "ordered" if !got_order => {
1727 value.order = MasonryItemOrder::Ordered;
1728 got_order = true;
1729 true
1730 },
1731 _ => false
1732 };
1733 if !success {
1734 return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
1735 }
1736 }
1737
1738 if got_placement || got_order {
1739 Ok(value)
1740 } else {
1741 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1742 }
1743 }
1744}
1745
1746#[inline]
1748fn flex_wrap_balance_enabled() -> bool {
1749 crate::pref!("layout.flexbox.balance", gecko = false)
1750}
1751
1752#[derive(
1757 Clone,
1758 Copy,
1759 Debug,
1760 Eq,
1761 MallocSizeOf,
1762 Parse,
1763 PartialEq,
1764 SpecifiedValueInfo,
1765 ToComputedValue,
1766 ToCss,
1767 ToResolvedValue,
1768 ToShmem,
1769 ToTyped,
1770)]
1771#[css(bitflags(
1772 single = "nowrap",
1773 mixed = "wrap,wrap-reverse,balance",
1774 validate_mixed = "Self::validate_and_simplify"
1775))]
1776#[repr(C)]
1777pub struct FlexWrap(u8);
1778bitflags! {
1779 impl FlexWrap: u8 {
1780 const NOWRAP = 0;
1782 const WRAP = 1 << 0;
1784 const WRAP_REVERSE = 1 << 1;
1786 const BALANCE = 1 << 2;
1788 }
1789}
1790
1791impl FlexWrap {
1792 fn validate_and_simplify(&mut self) -> bool {
1794 if self.contains(Self::WRAP | Self::WRAP_REVERSE) {
1795 return false;
1796 }
1797 if self.contains(Self::BALANCE) {
1798 if !flex_wrap_balance_enabled() {
1799 return false;
1800 }
1801 self.remove(Self::WRAP);
1803 }
1804 true
1805 }
1806}
1807
1808#[derive(
1809 Clone,
1810 Debug,
1811 MallocSizeOf,
1812 PartialEq,
1813 SpecifiedValueInfo,
1814 ToComputedValue,
1815 ToCss,
1816 ToResolvedValue,
1817 ToShmem,
1818)]
1819#[repr(C)]
1820pub struct TemplateAreas {
1822 #[css(skip)]
1824 pub areas: crate::OwnedSlice<NamedArea>,
1825 #[css(iterable)]
1830 pub strings: crate::OwnedSlice<crate::OwnedStr>,
1831 #[css(skip)]
1833 pub width: u32,
1834}
1835
1836#[derive(Default)]
1838pub struct TemplateAreasParser {
1839 areas: Vec<NamedArea>,
1840 area_indices: PrecomputedHashMap<Atom, usize>,
1841 strings: Vec<crate::OwnedStr>,
1842 width: u32,
1843 row: u32,
1844}
1845
1846impl TemplateAreasParser {
1847 pub fn try_parse_string(&mut self, input: &mut Parser) -> Result<(), ParseError> {
1849 input.try_parse(|input| {
1850 self.parse_string(input.expect_string()?)
1851 .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1852 })
1853 }
1854
1855 fn parse_string(&mut self, string: &str) -> Result<(), ()> {
1857 self.row += 1;
1858 let mut simplified_string = String::new();
1859 let mut current_area_index: Option<usize> = None;
1860 let mut column = 0u32;
1861 for token in TemplateAreasTokenizer(string) {
1862 column += 1;
1863 if column > 1 {
1864 simplified_string.push(' ');
1865 }
1866 let name = if let Some(token) = token? {
1867 simplified_string.push_str(token);
1868 Atom::from(token)
1869 } else {
1870 if let Some(index) = current_area_index.take() {
1871 if self.areas[index].columns.end != column {
1872 return Err(());
1873 }
1874 }
1875 simplified_string.push('.');
1876 continue;
1877 };
1878 if let Some(index) = current_area_index {
1879 if self.areas[index].name == name {
1880 if self.areas[index].rows.start == self.row {
1881 self.areas[index].columns.end += 1;
1882 }
1883 continue;
1884 }
1885 if self.areas[index].columns.end != column {
1886 return Err(());
1887 }
1888 }
1889 match self.area_indices.entry(name) {
1890 Entry::Occupied(ref e) => {
1891 let index = *e.get();
1892 if self.areas[index].columns.start != column
1893 || self.areas[index].rows.end != self.row
1894 {
1895 return Err(());
1896 }
1897 self.areas[index].rows.end += 1;
1898 current_area_index = Some(index);
1899 },
1900 Entry::Vacant(v) => {
1901 let index = self.areas.len();
1902 let name = v.key().clone();
1903 v.insert(index);
1904 self.areas.push(NamedArea {
1905 name,
1906 columns: UnsignedRange {
1907 start: column,
1908 end: column + 1,
1909 },
1910 rows: UnsignedRange {
1911 start: self.row,
1912 end: self.row + 1,
1913 },
1914 });
1915 current_area_index = Some(index);
1916 },
1917 }
1918 }
1919 if column == 0 {
1920 return Err(());
1923 }
1924 if let Some(index) = current_area_index {
1925 if self.areas[index].columns.end != column + 1 {
1926 debug_assert_ne!(self.areas[index].rows.start, self.row);
1927 return Err(());
1928 }
1929 }
1930 if self.row == 1 {
1931 self.width = column;
1932 } else if self.width != column {
1933 return Err(());
1934 }
1935
1936 self.strings.push(simplified_string.into());
1937 Ok(())
1938 }
1939
1940 pub fn finish(self) -> Result<TemplateAreas, ()> {
1942 if self.strings.is_empty() {
1943 return Err(());
1944 }
1945 Ok(TemplateAreas {
1946 areas: self.areas.into(),
1947 strings: self.strings.into(),
1948 width: self.width,
1949 })
1950 }
1951}
1952
1953impl TemplateAreas {
1954 fn parse_internal(input: &mut Parser) -> Result<Self, ()> {
1955 let mut parser = TemplateAreasParser::default();
1956 while parser.try_parse_string(input).is_ok() {}
1957 parser.finish()
1958 }
1959}
1960
1961impl Parse for TemplateAreas {
1962 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1963 Self::parse_internal(input)
1964 .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1965 }
1966}
1967
1968#[derive(
1970 Clone,
1971 Debug,
1972 MallocSizeOf,
1973 PartialEq,
1974 SpecifiedValueInfo,
1975 ToComputedValue,
1976 ToCss,
1977 ToResolvedValue,
1978 ToShmem,
1979)]
1980#[repr(transparent)]
1981pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
1982
1983impl Parse for TemplateAreasArc {
1984 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1985 let parsed = TemplateAreas::parse(context, input)?;
1986 Ok(TemplateAreasArc(Arc::new(parsed)))
1987 }
1988}
1989
1990#[repr(C)]
1993#[derive(
1994 Clone,
1995 Debug,
1996 MallocSizeOf,
1997 PartialEq,
1998 SpecifiedValueInfo,
1999 ToComputedValue,
2000 ToResolvedValue,
2001 ToShmem,
2002)]
2003pub struct UnsignedRange {
2004 pub start: u32,
2006 pub end: u32,
2008}
2009
2010#[derive(
2011 Clone,
2012 Debug,
2013 MallocSizeOf,
2014 PartialEq,
2015 SpecifiedValueInfo,
2016 ToComputedValue,
2017 ToResolvedValue,
2018 ToShmem,
2019)]
2020#[repr(C)]
2021pub struct NamedArea {
2024 pub name: Atom,
2026 pub rows: UnsignedRange,
2028 pub columns: UnsignedRange,
2030}
2031
2032struct TemplateAreasTokenizer<'a>(&'a str);
2035
2036impl<'a> Iterator for TemplateAreasTokenizer<'a> {
2037 type Item = Result<Option<&'a str>, ()>;
2038
2039 fn next(&mut self) -> Option<Self::Item> {
2040 let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
2041 if rest.is_empty() {
2042 return None;
2043 }
2044 if rest.starts_with('.') {
2045 self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
2046 return Some(Ok(None));
2047 }
2048 if !rest.starts_with(is_name_code_point) {
2049 return Some(Err(()));
2050 }
2051 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
2052 let token = &rest[..token_len];
2053 self.0 = &rest[token_len..];
2054 Some(Ok(Some(token)))
2055 }
2056}
2057
2058fn is_name_code_point(c: char) -> bool {
2059 c.is_ascii_uppercase()
2060 || c.is_ascii_lowercase()
2061 || c >= '\u{80}'
2062 || c == '_'
2063 || c.is_ascii_digit()
2064 || c == '-'
2065}
2066
2067#[repr(C, u8)]
2073#[derive(
2074 Clone,
2075 Debug,
2076 MallocSizeOf,
2077 Parse,
2078 PartialEq,
2079 SpecifiedValueInfo,
2080 ToComputedValue,
2081 ToCss,
2082 ToResolvedValue,
2083 ToShmem,
2084 ToTyped,
2085)]
2086#[typed(todo_derive_fields)]
2087pub enum GridTemplateAreas {
2088 None,
2090 Areas(TemplateAreasArc),
2092}
2093
2094impl GridTemplateAreas {
2095 #[inline]
2096 pub fn none() -> GridTemplateAreas {
2098 GridTemplateAreas::None
2099 }
2100}
2101
2102pub type ZIndex = GenericZIndex<Integer>;
2104
2105pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
2107
2108impl Parse for AspectRatio {
2109 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
2110 use crate::values::generics::position::PreferredRatio;
2111 use crate::values::specified::Ratio;
2112
2113 let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2114 let ratio = input.try_parse(|i| Ratio::parse(context, i));
2115 if auto.is_err() {
2116 auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2117 }
2118
2119 if auto.is_err() && ratio.is_err() {
2120 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2121 }
2122
2123 Ok(AspectRatio {
2124 auto: auto.is_ok(),
2125 ratio: match ratio {
2126 Ok(ratio) => PreferredRatio::Ratio(ratio),
2127 Err(..) => PreferredRatio::None,
2128 },
2129 })
2130 }
2131}
2132
2133impl AspectRatio {
2134 pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
2136 use crate::values::generics::position::PreferredRatio;
2137 use crate::values::generics::ratio::Ratio;
2138 AspectRatio {
2139 auto: true,
2140 ratio: PreferredRatio::Ratio(Ratio(
2141 NonNegativeNumber::new(w),
2142 NonNegativeNumber::new(h),
2143 )),
2144 }
2145 }
2146}
2147
2148pub type Inset = GenericInset<specified::Percentage, LengthPercentage>;
2150
2151impl Inset {
2152 #[inline]
2155 pub fn parse_quirky(
2156 context: &ParserContext,
2157 input: &mut Parser,
2158 allow_quirks: AllowQuirks,
2159 ) -> Result<Self, ParseError> {
2160 if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
2161 {
2162 return Ok(Self::LengthPercentage(l));
2163 }
2164 match input.try_parse(|i| i.expect_ident_matching("auto")) {
2165 Ok(_) => return Ok(Self::Auto),
2166 Err(e) if !crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) => {
2167 return Err(e.into());
2168 },
2169 Err(_) => (),
2170 };
2171 Self::parse_anchor_functions_quirky(context, input, allow_quirks)
2172 }
2173
2174 fn parse_as_anchor_function_fallback(
2175 context: &ParserContext,
2176 input: &mut Parser,
2177 ) -> Result<Self, ParseError> {
2178 if let Ok(l) =
2179 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No))
2180 {
2181 return Ok(Self::LengthPercentage(l));
2182 }
2183 Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No)
2184 }
2185
2186 fn parse_anchor_functions_quirky(
2187 context: &ParserContext,
2188 input: &mut Parser,
2189 allow_quirks: AllowQuirks,
2190 ) -> Result<Self, ParseError> {
2191 debug_assert!(
2192 crate::pref!("layout.css.anchor-positioning.enabled", gecko = true),
2193 "How are we parsing with pref off?"
2194 );
2195 if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) {
2196 return Ok(Self::AnchorFunction(Box::new(inner)));
2197 }
2198 if let Ok(inner) =
2199 input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i))
2200 {
2201 return Ok(Self::AnchorSizeFunction(Box::new(inner)));
2202 }
2203 Ok(Self::AnchorContainingCalcFunction(input.try_parse(
2204 |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks),
2205 )?))
2206 }
2207}
2208
2209impl Parse for Inset {
2210 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
2211 Self::parse_quirky(context, input, AllowQuirks::No)
2212 }
2213}
2214
2215pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>;
2217
2218impl Parse for AnchorFunction {
2219 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
2220 if !crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) {
2221 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2222 }
2223 input.expect_function_matching("anchor")?;
2224 input.parse_nested_block(|i| {
2225 let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
2226 let side = GenericAnchorSide::parse(context, i)?;
2227 let target_element = if target_element.is_none() {
2228 i.try_parse(|i| DashedIdent::parse(context, i)).ok()
2229 } else {
2230 target_element
2231 };
2232 let fallback = i
2233 .try_parse(|i| {
2234 i.expect_comma()?;
2235 Inset::parse_as_anchor_function_fallback(context, i)
2236 })
2237 .ok();
2238 Ok(Self {
2239 target_element: TreeScoped::with_default_level(
2240 target_element.unwrap_or_else(DashedIdent::empty),
2241 ),
2242 side,
2243 fallback: fallback.into(),
2244 })
2245 })
2246 }
2247}