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