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)]
55pub enum PositionComponent<S> {
56 Center,
58 Length(LengthPercentage),
60 Side(S, Option<LengthPercentage>),
62}
63
64#[derive(
66 Clone,
67 Copy,
68 Debug,
69 Eq,
70 Hash,
71 MallocSizeOf,
72 Parse,
73 PartialEq,
74 SpecifiedValueInfo,
75 ToComputedValue,
76 ToCss,
77 ToResolvedValue,
78 ToShmem,
79)]
80#[allow(missing_docs)]
81#[repr(u8)]
82pub enum HorizontalPositionKeyword {
83 Left,
84 Right,
85}
86
87#[derive(
89 Clone,
90 Copy,
91 Debug,
92 Eq,
93 Hash,
94 MallocSizeOf,
95 Parse,
96 PartialEq,
97 SpecifiedValueInfo,
98 ToComputedValue,
99 ToCss,
100 ToResolvedValue,
101 ToShmem,
102)]
103#[allow(missing_docs)]
104#[repr(u8)]
105pub enum VerticalPositionKeyword {
106 Top,
107 Bottom,
108}
109
110impl Parse for Position {
111 fn parse<'i, 't>(
112 context: &ParserContext,
113 input: &mut Parser<'i, 't>,
114 ) -> Result<Self, ParseError<'i>> {
115 let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?;
116 if position.is_three_value_syntax() {
117 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
118 }
119 Ok(position)
120 }
121}
122
123impl Position {
124 pub fn parse_three_value_quirky<'i, 't>(
126 context: &ParserContext,
127 input: &mut Parser<'i, 't>,
128 allow_quirks: AllowQuirks,
129 ) -> Result<Self, ParseError<'i>> {
130 match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
131 Ok(x_pos @ PositionComponent::Center) => {
132 if let Ok(y_pos) =
133 input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
134 {
135 return Ok(Self::new(x_pos, y_pos));
136 }
137 let x_pos = input
138 .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
139 .unwrap_or(x_pos);
140 let y_pos = PositionComponent::Center;
141 return Ok(Self::new(x_pos, y_pos));
142 },
143 Ok(PositionComponent::Side(x_keyword, lp)) => {
144 if input
145 .try_parse(|i| i.expect_ident_matching("center"))
146 .is_ok()
147 {
148 let x_pos = PositionComponent::Side(x_keyword, lp);
149 let y_pos = PositionComponent::Center;
150 return Ok(Self::new(x_pos, y_pos));
151 }
152 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
153 let y_lp = input
154 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
155 .ok();
156 let x_pos = PositionComponent::Side(x_keyword, lp);
157 let y_pos = PositionComponent::Side(y_keyword, y_lp);
158 return Ok(Self::new(x_pos, y_pos));
159 }
160 let x_pos = PositionComponent::Side(x_keyword, None);
161 let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length);
162 return Ok(Self::new(x_pos, y_pos));
163 },
164 Ok(x_pos @ PositionComponent::Length(_)) => {
165 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
166 let y_pos = PositionComponent::Side(y_keyword, None);
167 return Ok(Self::new(x_pos, y_pos));
168 }
169 if let Ok(y_lp) =
170 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
171 {
172 let y_pos = PositionComponent::Length(y_lp);
173 return Ok(Self::new(x_pos, y_pos));
174 }
175 let y_pos = PositionComponent::Center;
176 let _ = input.try_parse(|i| i.expect_ident_matching("center"));
177 return Ok(Self::new(x_pos, y_pos));
178 },
179 Err(_) => {},
180 }
181 let y_keyword = VerticalPositionKeyword::parse(input)?;
182 let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| {
183 let y_lp = i
184 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
185 .ok();
186 if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
187 let x_lp = i
188 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
189 .ok();
190 let x_pos = PositionComponent::Side(x_keyword, x_lp);
191 return Ok((y_lp, x_pos));
192 };
193 i.expect_ident_matching("center")?;
194 let x_pos = PositionComponent::Center;
195 Ok((y_lp, x_pos))
196 });
197 if let Ok((y_lp, x_pos)) = lp_and_x_pos {
198 let y_pos = PositionComponent::Side(y_keyword, y_lp);
199 return Ok(Self::new(x_pos, y_pos));
200 }
201 let x_pos = PositionComponent::Center;
202 let y_pos = PositionComponent::Side(y_keyword, None);
203 Ok(Self::new(x_pos, y_pos))
204 }
205
206 #[inline]
208 pub fn center() -> Self {
209 Self::new(PositionComponent::Center, PositionComponent::Center)
210 }
211
212 #[inline]
214 fn is_three_value_syntax(&self) -> bool {
215 self.horizontal.component_count() != self.vertical.component_count()
216 }
217}
218
219impl ToCss for Position {
220 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
221 where
222 W: Write,
223 {
224 match (&self.horizontal, &self.vertical) {
225 (
226 x_pos @ &PositionComponent::Side(_, Some(_)),
227 &PositionComponent::Length(ref y_lp),
228 ) => {
229 x_pos.to_css(dest)?;
230 dest.write_str(" top ")?;
231 y_lp.to_css(dest)
232 },
233 (
234 &PositionComponent::Length(ref x_lp),
235 y_pos @ &PositionComponent::Side(_, Some(_)),
236 ) => {
237 dest.write_str("left ")?;
238 x_lp.to_css(dest)?;
239 dest.write_char(' ')?;
240 y_pos.to_css(dest)
241 },
242 (x_pos, y_pos) => {
243 x_pos.to_css(dest)?;
244 dest.write_char(' ')?;
245 y_pos.to_css(dest)
246 },
247 }
248 }
249}
250
251impl<S: Parse> Parse for PositionComponent<S> {
252 fn parse<'i, 't>(
253 context: &ParserContext,
254 input: &mut Parser<'i, 't>,
255 ) -> Result<Self, ParseError<'i>> {
256 Self::parse_quirky(context, input, AllowQuirks::No)
257 }
258}
259
260impl<S: Parse> PositionComponent<S> {
261 pub fn parse_quirky<'i, 't>(
263 context: &ParserContext,
264 input: &mut Parser<'i, 't>,
265 allow_quirks: AllowQuirks,
266 ) -> Result<Self, ParseError<'i>> {
267 if input
268 .try_parse(|i| i.expect_ident_matching("center"))
269 .is_ok()
270 {
271 return Ok(PositionComponent::Center);
272 }
273 if let Ok(lp) =
274 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
275 {
276 return Ok(PositionComponent::Length(lp));
277 }
278 let keyword = S::parse(context, input)?;
279 let lp = input
280 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
281 .ok();
282 Ok(PositionComponent::Side(keyword, lp))
283 }
284}
285
286impl<S> GenericPositionComponent for PositionComponent<S> {
287 fn is_center(&self) -> bool {
288 match *self {
289 PositionComponent::Center => true,
290 PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.0 == 0.5,
291 PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => per.0 == 0.5,
293 _ => false,
294 }
295 }
296}
297
298impl<S> PositionComponent<S> {
299 pub fn zero() -> Self {
301 PositionComponent::Length(LengthPercentage::Percentage(Percentage::zero()))
302 }
303
304 fn component_count(&self) -> usize {
306 match *self {
307 PositionComponent::Length(..) | PositionComponent::Center => 1,
308 PositionComponent::Side(_, ref lp) => {
309 if lp.is_some() {
310 2
311 } else {
312 1
313 }
314 },
315 }
316 }
317}
318
319impl<S: Side> ToComputedValue for PositionComponent<S> {
320 type ComputedValue = ComputedLengthPercentage;
321
322 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
323 match *self {
324 PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)),
325 PositionComponent::Side(ref keyword, None) => {
326 let p = Percentage(if keyword.is_start() { 0. } else { 1. });
327 ComputedLengthPercentage::new_percent(p)
328 },
329 PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
330 let length = length.to_computed_value(context);
331 ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All)
333 },
334 PositionComponent::Side(_, Some(ref length))
335 | PositionComponent::Length(ref length) => length.to_computed_value(context),
336 }
337 }
338
339 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
340 PositionComponent::Length(ToComputedValue::from_computed_value(computed))
341 }
342}
343
344impl<S: Side> PositionComponent<S> {
345 pub fn initial_specified_value() -> Self {
347 PositionComponent::Side(S::start(), None)
348 }
349}
350
351#[derive(
353 Animate,
354 Clone,
355 Debug,
356 MallocSizeOf,
357 PartialEq,
358 SpecifiedValueInfo,
359 ToComputedValue,
360 ToCss,
361 ToResolvedValue,
362 ToShmem,
363 ToTyped,
364)]
365#[css(comma)]
366#[repr(transparent)]
367pub struct AnchorNameIdent(
368 #[css(iterable, if_empty = "none")]
369 #[ignore_malloc_size_of = "Arc"]
370 #[animation(constant)]
371 pub crate::ArcSlice<DashedIdent>,
372);
373
374impl AnchorNameIdent {
375 pub fn none() -> Self {
377 Self(Default::default())
378 }
379}
380
381impl Parse for AnchorNameIdent {
382 fn parse<'i, 't>(
383 context: &ParserContext,
384 input: &mut Parser<'i, 't>,
385 ) -> Result<Self, ParseError<'i>> {
386 let location = input.current_source_location();
387 let first = input.expect_ident()?;
388 if first.eq_ignore_ascii_case("none") {
389 return Ok(Self::none());
390 }
391 let mut idents: SmallVec<[DashedIdent; 4]> =
394 smallvec![DashedIdent::from_ident(location, first,)?];
395 while input.try_parse(|input| input.expect_comma()).is_ok() {
396 idents.push(DashedIdent::parse(context, input)?);
397 }
398 Ok(AnchorNameIdent(ArcSlice::from_iter(idents.drain(..))))
399 }
400}
401
402impl IsTreeScoped for AnchorNameIdent {
403 fn is_tree_scoped(&self) -> bool {
404 !self.0.is_empty()
405 }
406}
407
408pub type AnchorName = TreeScoped<AnchorNameIdent>;
410
411impl AnchorName {
412 pub fn none() -> Self {
414 Self::with_default_level(AnchorNameIdent::none())
415 }
416}
417
418#[derive(
420 Clone,
421 Debug,
422 MallocSizeOf,
423 PartialEq,
424 SpecifiedValueInfo,
425 ToComputedValue,
426 ToCss,
427 ToResolvedValue,
428 ToShmem,
429 ToTyped,
430)]
431#[repr(transparent)]
432#[css(comma)]
433pub struct ScopedNameList(
434 #[css(iterable, if_empty = "none")]
436 #[ignore_malloc_size_of = "Arc"]
437 crate::ArcSlice<AtomIdent>,
438);
439
440impl ScopedNameList {
441 pub fn none() -> Self {
443 Self(crate::ArcSlice::default())
444 }
445
446 pub fn is_none(&self) -> bool {
448 self.0.is_empty()
449 }
450
451 pub fn all() -> Self {
453 static ALL: std::sync::LazyLock<ScopedNameList> = std::sync::LazyLock::new(|| {
454 ScopedNameList(crate::ArcSlice::from_iter_leaked(std::iter::once(
455 AtomIdent::new(atom!("all")),
456 )))
457 });
458 ALL.clone()
459 }
460}
461
462impl Parse for ScopedNameList {
463 fn parse<'i, 't>(
464 context: &ParserContext,
465 input: &mut Parser<'i, 't>,
466 ) -> Result<Self, ParseError<'i>> {
467 let location = input.current_source_location();
468 let first = input.expect_ident()?;
469 if first.eq_ignore_ascii_case("none") {
470 return Ok(Self::none());
471 }
472 if first.eq_ignore_ascii_case("all") {
473 return Ok(Self::all());
474 }
475 let mut idents = SmallVec::<[AtomIdent; 8]>::new();
478 idents.push(AtomIdent::new(DashedIdent::from_ident(location, first)?.0));
479 while input.try_parse(|input| input.expect_comma()).is_ok() {
480 idents.push(AtomIdent::new(DashedIdent::parse(context, input)?.0));
481 }
482 Ok(Self(ArcSlice::from_iter(idents.drain(..))))
483 }
484}
485
486impl IsTreeScoped for ScopedNameList {
487 fn is_tree_scoped(&self) -> bool {
488 !self.is_none()
489 }
490}
491
492pub type ScopedName = TreeScoped<ScopedNameList>;
495
496impl ScopedName {
497 pub fn none() -> Self {
499 Self::with_default_level(ScopedNameList::none())
500 }
501
502 pub fn is_none(&self) -> bool {
504 self.value.is_none()
505 }
506}
507
508#[derive(
510 Clone,
511 Debug,
512 MallocSizeOf,
513 Parse,
514 PartialEq,
515 SpecifiedValueInfo,
516 ToComputedValue,
517 ToCss,
518 ToResolvedValue,
519 ToShmem,
520 ToTyped,
521)]
522#[repr(u8)]
523pub enum PositionAnchorKeyword {
524 None,
526 Auto,
528 Ident(DashedIdent),
530}
531
532impl PositionAnchorKeyword {
533 pub fn none() -> Self {
535 Self::None
536 }
537}
538
539impl IsTreeScoped for PositionAnchorKeyword {
540 fn is_tree_scoped(&self) -> bool {
541 match *self {
542 Self::None | Self::Auto => false,
543 Self::Ident(_) => true,
544 }
545 }
546}
547
548pub type PositionAnchor = TreeScoped<PositionAnchorKeyword>;
550
551impl PositionAnchor {
552 pub fn none() -> Self {
554 Self::with_default_level(PositionAnchorKeyword::none())
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)]
742pub struct PositionTryFallbacks(
744 #[css(iterable, if_empty = "none")]
745 #[ignore_malloc_size_of = "Arc"]
746 pub crate::ArcSlice<PositionTryFallbacksItem>,
747);
748
749impl PositionTryFallbacks {
750 #[inline]
751 pub fn none() -> Self {
753 Self(Default::default())
754 }
755
756 pub fn is_none(&self) -> bool {
758 self.0.is_empty()
759 }
760}
761
762impl Parse for PositionTryFallbacks {
763 fn parse<'i, 't>(
764 context: &ParserContext,
765 input: &mut Parser<'i, 't>,
766 ) -> Result<Self, ParseError<'i>> {
767 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
768 return Ok(Self::none());
769 }
770 let mut items: SmallVec<[PositionTryFallbacksItem; 4]> =
773 smallvec![PositionTryFallbacksItem::parse(context, input)?];
774 while input.try_parse(|input| input.expect_comma()).is_ok() {
775 items.push(PositionTryFallbacksItem::parse(context, input)?);
776 }
777 Ok(Self(ArcSlice::from_iter(items.drain(..))))
778 }
779}
780
781#[derive(
783 Clone,
784 Copy,
785 Debug,
786 Default,
787 Eq,
788 MallocSizeOf,
789 Parse,
790 PartialEq,
791 SpecifiedValueInfo,
792 ToComputedValue,
793 ToCss,
794 ToResolvedValue,
795 ToShmem,
796 ToTyped,
797)]
798#[repr(u8)]
799pub enum PositionTryOrder {
800 #[default]
801 Normal,
803 MostWidth,
805 MostHeight,
807 MostBlockSize,
809 MostInlineSize,
811}
812
813impl PositionTryOrder {
814 #[inline]
815 pub fn normal() -> Self {
817 Self::Normal
818 }
819
820 pub fn is_normal(&self) -> bool {
822 *self == Self::Normal
823 }
824}
825
826#[derive(
827 Clone,
828 Copy,
829 Debug,
830 Eq,
831 MallocSizeOf,
832 Parse,
833 PartialEq,
834 Serialize,
835 SpecifiedValueInfo,
836 ToComputedValue,
837 ToCss,
838 ToResolvedValue,
839 ToShmem,
840 ToTyped,
841)]
842#[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))]
843#[repr(C)]
844pub struct PositionVisibility(u8);
846bitflags! {
847 impl PositionVisibility: u8 {
848 const ALWAYS = 0;
850 const ANCHORS_VALID = 1 << 0;
852 const ANCHORS_VISIBLE = 1 << 1;
854 const NO_OVERFLOW = 1 << 2;
856 }
857}
858
859impl Default for PositionVisibility {
860 fn default() -> Self {
861 Self::ALWAYS
862 }
863}
864
865impl PositionVisibility {
866 #[inline]
867 pub fn always() -> Self {
869 Self::ALWAYS
870 }
871}
872
873#[repr(u8)]
876#[derive(Clone, Copy, Debug, Eq, PartialEq)]
877pub enum PositionAreaType {
878 Physical,
880 Logical,
882 SelfLogical,
884 Inferred,
886 SelfInferred,
888 Common,
890 None,
892}
893
894#[repr(u8)]
901#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
902#[allow(missing_docs)]
903pub enum PositionAreaAxis {
904 Horizontal = 0b000,
905 Vertical = 0b001,
906
907 X = 0b010,
908 Y = 0b011,
909
910 Block = 0b110,
911 Inline = 0b111,
912
913 Inferred = 0b100,
914 None = 0b101,
915}
916
917impl PositionAreaAxis {
918 pub fn is_physical(self) -> bool {
920 (self as u8 & 0b100) == 0
921 }
922
923 fn is_flow_relative_direction(self) -> bool {
925 self == Self::Inferred || (self as u8 & 0b10) != 0
926 }
927
928 fn is_canonically_first(self) -> bool {
930 self != Self::Inferred && (self as u8) & 1 == 0
931 }
932
933 #[allow(unused)]
934 fn flip(self) -> Self {
935 if matches!(self, Self::Inferred | Self::None) {
936 return self;
937 }
938 Self::from_u8(self as u8 ^ 1u8).unwrap()
939 }
940
941 fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> {
942 Some(match self {
943 PositionAreaAxis::Horizontal | PositionAreaAxis::X => {
944 if wm.is_vertical() {
945 LogicalAxis::Block
946 } else {
947 LogicalAxis::Inline
948 }
949 },
950 PositionAreaAxis::Vertical | PositionAreaAxis::Y => {
951 if wm.is_vertical() {
952 LogicalAxis::Inline
953 } else {
954 LogicalAxis::Block
955 }
956 },
957 PositionAreaAxis::Block => LogicalAxis::Block,
958 PositionAreaAxis::Inline => LogicalAxis::Inline,
959 PositionAreaAxis::Inferred => inferred,
960 PositionAreaAxis::None => return None,
961 })
962 }
963}
964
965#[repr(u8)]
968#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
969pub enum PositionAreaTrack {
970 Start = 0b001,
972 SpanStart = 0b011,
974 End = 0b100,
976 SpanEnd = 0b110,
978 Center = 0b010,
980 SpanAll = 0b111,
982}
983
984impl PositionAreaTrack {
985 fn flip(self) -> Self {
986 match self {
987 Self::Start => Self::End,
988 Self::SpanStart => Self::SpanEnd,
989 Self::End => Self::Start,
990 Self::SpanEnd => Self::SpanStart,
991 Self::Center | Self::SpanAll => self,
992 }
993 }
994
995 fn start(self) -> bool {
996 self as u8 & 1 != 0
997 }
998}
999
1000pub const AXIS_SHIFT: usize = 3;
1002pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT;
1004pub const TRACK_MASK: u8 = 0b111u8;
1006pub const SELF_WM: u8 = 1u8 << 6;
1008
1009#[derive(
1010 Clone,
1011 Copy,
1012 Debug,
1013 Default,
1014 Eq,
1015 MallocSizeOf,
1016 Parse,
1017 PartialEq,
1018 SpecifiedValueInfo,
1019 ToComputedValue,
1020 ToCss,
1021 ToResolvedValue,
1022 ToShmem,
1023 FromPrimitive,
1024)]
1025#[allow(missing_docs)]
1026#[repr(u8)]
1027pub enum PositionAreaKeyword {
1032 #[default]
1033 None = (PositionAreaAxis::None as u8) << AXIS_SHIFT,
1034
1035 Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8,
1037 SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8,
1038
1039 Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1041 End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1042 SpanStart =
1043 ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1044 SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1045
1046 Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1048 Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1049 Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1050 Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1051
1052 XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1054 XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1055 YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1056 YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1057
1058 BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1060 BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1061 InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1062 InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1063
1064 SpanLeft =
1066 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1067 SpanRight =
1068 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1069 SpanTop =
1070 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1071 SpanBottom =
1072 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1073
1074 SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1076 SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1077 SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1078 SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1079
1080 SpanBlockStart =
1082 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1083 SpanBlockEnd =
1084 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1085 SpanInlineStart =
1086 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1087 SpanInlineEnd =
1088 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1089
1090 SelfStart = SELF_WM | (Self::Start as u8),
1092 SelfEnd = SELF_WM | (Self::End as u8),
1093 SpanSelfStart = SELF_WM | (Self::SpanStart as u8),
1094 SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8),
1095
1096 SelfXStart = SELF_WM | (Self::XStart as u8),
1097 SelfXEnd = SELF_WM | (Self::XEnd as u8),
1098 SelfYStart = SELF_WM | (Self::YStart as u8),
1099 SelfYEnd = SELF_WM | (Self::YEnd as u8),
1100 SelfBlockStart = SELF_WM | (Self::BlockStart as u8),
1101 SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8),
1102 SelfInlineStart = SELF_WM | (Self::InlineStart as u8),
1103 SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8),
1104
1105 SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8),
1106 SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8),
1107 SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8),
1108 SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8),
1109 SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8),
1110 SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8),
1111 SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8),
1112 SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8),
1113}
1114
1115impl PositionAreaKeyword {
1116 #[inline]
1118 pub fn none() -> Self {
1119 Self::None
1120 }
1121
1122 pub fn is_none(&self) -> bool {
1124 *self == Self::None
1125 }
1126
1127 pub fn self_wm(self) -> bool {
1129 (self as u8 & SELF_WM) != 0
1130 }
1131
1132 pub fn axis(self) -> PositionAreaAxis {
1134 PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap()
1135 }
1136
1137 pub fn with_axis(self, axis: PositionAreaAxis) -> Self {
1139 Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap()
1140 }
1141
1142 pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self {
1144 if self.axis() == PositionAreaAxis::Inferred {
1145 self.with_axis(axis)
1146 } else {
1147 self
1148 }
1149 }
1150
1151 pub fn track(self) -> Option<PositionAreaTrack> {
1153 let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK);
1154 debug_assert_eq!(
1155 result.is_none(),
1156 self.is_none(),
1157 "Only the none keyword has no track"
1158 );
1159 result
1160 }
1161
1162 fn group_type(self) -> PositionAreaType {
1163 let axis = self.axis();
1164 if axis == PositionAreaAxis::None {
1165 if self.is_none() {
1166 return PositionAreaType::None;
1167 }
1168 return PositionAreaType::Common;
1169 }
1170 if axis == PositionAreaAxis::Inferred {
1171 return if self.self_wm() {
1172 PositionAreaType::SelfInferred
1173 } else {
1174 PositionAreaType::Inferred
1175 };
1176 }
1177 if axis.is_physical() {
1178 return PositionAreaType::Physical;
1179 }
1180 if self.self_wm() {
1181 PositionAreaType::SelfLogical
1182 } else {
1183 PositionAreaType::Logical
1184 }
1185 }
1186
1187 fn to_physical(
1188 self,
1189 cb_wm: WritingMode,
1190 self_wm: WritingMode,
1191 inferred_axis: LogicalAxis,
1192 ) -> Self {
1193 let wm = if self.self_wm() { self_wm } else { cb_wm };
1194 let axis = self.axis();
1195 if !axis.is_flow_relative_direction() {
1196 return self;
1197 }
1198 let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else {
1199 return self;
1200 };
1201 let Some(track) = self.track() else {
1202 debug_assert!(false, "How did we end up with no track here? {self:?}");
1203 return self;
1204 };
1205 let start = track.start();
1206 let logical_side = match logical_axis {
1207 LogicalAxis::Block => {
1208 if start {
1209 LogicalSide::BlockStart
1210 } else {
1211 LogicalSide::BlockEnd
1212 }
1213 },
1214 LogicalAxis::Inline => {
1215 if start {
1216 LogicalSide::InlineStart
1217 } else {
1218 LogicalSide::InlineEnd
1219 }
1220 },
1221 };
1222 let physical_side = logical_side.to_physical(wm);
1223 let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left);
1224 let new_track = if physical_start != start {
1225 track.flip()
1226 } else {
1227 track
1228 };
1229 let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) {
1230 PositionAreaAxis::Vertical
1231 } else {
1232 PositionAreaAxis::Horizontal
1233 };
1234 Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap()
1235 }
1236
1237 fn flip_track(self) -> Self {
1238 let Some(old_track) = self.track() else {
1239 return self;
1240 };
1241 let new_track = old_track.flip();
1242 Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap()
1243 }
1244
1245 pub fn to_self_alignment(self, axis: LogicalAxis, cb_wm: &WritingMode) -> Option<AlignFlags> {
1253 let track = self.track()?;
1254 Some(match track {
1255 PositionAreaTrack::Center => AlignFlags::CENTER,
1257 PositionAreaTrack::SpanAll => AlignFlags::ANCHOR_CENTER,
1259 _ => {
1262 debug_assert_eq!(self.group_type(), PositionAreaType::Physical);
1263 if axis == LogicalAxis::Inline {
1264 if track.start() == cb_wm.intersects(WritingMode::INLINE_REVERSED) {
1268 AlignFlags::START
1269 } else {
1270 AlignFlags::END
1271 }
1272 } else {
1273 if track.start() == cb_wm.is_vertical_rl() {
1276 AlignFlags::START
1277 } else {
1278 AlignFlags::END
1279 }
1280 }
1281 },
1282 })
1283 }
1284}
1285
1286#[derive(
1287 Clone,
1288 Copy,
1289 Debug,
1290 Eq,
1291 MallocSizeOf,
1292 PartialEq,
1293 SpecifiedValueInfo,
1294 ToCss,
1295 ToResolvedValue,
1296 ToShmem,
1297 ToTyped,
1298)]
1299#[repr(C)]
1300pub struct PositionArea {
1302 pub first: PositionAreaKeyword,
1304 #[css(skip_if = "PositionAreaKeyword::is_none")]
1306 pub second: PositionAreaKeyword,
1307}
1308
1309impl PositionArea {
1310 #[inline]
1312 pub fn none() -> Self {
1313 Self {
1314 first: PositionAreaKeyword::None,
1315 second: PositionAreaKeyword::None,
1316 }
1317 }
1318
1319 #[inline]
1321 pub fn is_none(&self) -> bool {
1322 self.first.is_none()
1323 }
1324
1325 pub fn parse_except_none<'i, 't>(
1327 context: &ParserContext,
1328 input: &mut Parser<'i, 't>,
1329 ) -> Result<Self, ParseError<'i>> {
1330 Self::parse_internal(context, input, false)
1331 }
1332
1333 pub fn get_type(&self) -> PositionAreaType {
1335 let first = self.first.group_type();
1336 let second = self.second.group_type();
1337 if matches!(second, PositionAreaType::None | PositionAreaType::Common) {
1338 return first;
1339 }
1340 if first == PositionAreaType::Common {
1341 return second;
1342 }
1343 if first != second {
1344 return PositionAreaType::None;
1345 }
1346 let first_axis = self.first.axis();
1347 if first_axis != PositionAreaAxis::Inferred
1348 && first_axis.is_canonically_first() == self.second.axis().is_canonically_first()
1349 {
1350 return PositionAreaType::None;
1351 }
1352 first
1353 }
1354
1355 fn parse_internal<'i, 't>(
1356 _: &ParserContext,
1357 input: &mut Parser<'i, 't>,
1358 allow_none: bool,
1359 ) -> Result<Self, ParseError<'i>> {
1360 let mut location = input.current_source_location();
1361 let mut first = PositionAreaKeyword::parse(input)?;
1362 if first.is_none() {
1363 if allow_none {
1364 return Ok(Self::none());
1365 }
1366 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1367 }
1368
1369 location = input.current_source_location();
1370 let second = input.try_parse(PositionAreaKeyword::parse);
1371 if let Ok(PositionAreaKeyword::None) = second {
1372 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1374 }
1375 let mut second = second.unwrap_or(PositionAreaKeyword::None);
1376 if second.is_none() {
1377 return Ok(Self { first, second });
1383 }
1384
1385 let pair_type = Self { first, second }.get_type();
1386 if pair_type == PositionAreaType::None {
1387 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1389 }
1390 if matches!(
1393 pair_type,
1394 PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical
1395 ) {
1396 if second == PositionAreaKeyword::SpanAll {
1397 second = PositionAreaKeyword::None;
1400 } else if first == PositionAreaKeyword::SpanAll {
1401 first = second;
1402 second = PositionAreaKeyword::None;
1403 }
1404 }
1405 if first == second {
1406 second = PositionAreaKeyword::None;
1407 }
1408 let mut result = Self { first, second };
1409 result.canonicalize_order();
1410 Ok(result)
1411 }
1412
1413 fn canonicalize_order(&mut self) {
1414 let first_axis = self.first.axis();
1415 if first_axis.is_canonically_first() || self.second.is_none() {
1416 return;
1417 }
1418 let second_axis = self.second.axis();
1419 if first_axis == second_axis {
1420 return;
1422 }
1423 if second_axis.is_canonically_first()
1424 || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred)
1425 {
1426 std::mem::swap(&mut self.first, &mut self.second);
1427 }
1428 }
1429
1430 fn make_missing_second_explicit(&mut self) {
1431 if !self.second.is_none() {
1432 return;
1433 }
1434 let axis = self.first.axis();
1435 if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) {
1436 self.second = self.first;
1437 return;
1438 }
1439 self.second = PositionAreaKeyword::SpanAll;
1440 if !axis.is_canonically_first() {
1441 std::mem::swap(&mut self.first, &mut self.second);
1442 }
1443 }
1444
1445 pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self {
1447 self.make_missing_second_explicit();
1448 if self.first.axis() == PositionAreaAxis::None
1454 && self.second.axis() == PositionAreaAxis::None
1455 && !cb_wm.is_vertical()
1456 {
1457 std::mem::swap(&mut self.first, &mut self.second);
1458 } else {
1459 self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block);
1460 self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline);
1461 self.canonicalize_order();
1462 }
1463 self
1464 }
1465
1466 fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) {
1467 if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) {
1468 self.first = self.first.flip_track();
1469 } else {
1470 self.second = self.second.flip_track();
1471 }
1472 }
1473
1474 fn flip_start(&mut self) {
1475 self.first = self.first.with_axis(self.first.axis().flip());
1476 self.second = self.second.with_axis(self.second.axis().flip());
1477 }
1478
1479 pub fn with_tactic(
1481 mut self,
1482 wm: WritingMode,
1483 tactic: PositionTryFallbacksTryTacticKeyword,
1484 ) -> Self {
1485 self.make_missing_second_explicit();
1486 let axis_to_flip = match tactic {
1487 PositionTryFallbacksTryTacticKeyword::FlipStart => {
1488 self.flip_start();
1489 return self;
1490 },
1491 PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block,
1492 PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline,
1493 PositionTryFallbacksTryTacticKeyword::FlipX => {
1494 if wm.is_horizontal() {
1495 LogicalAxis::Inline
1496 } else {
1497 LogicalAxis::Block
1498 }
1499 },
1500 PositionTryFallbacksTryTacticKeyword::FlipY => {
1501 if wm.is_vertical() {
1502 LogicalAxis::Inline
1503 } else {
1504 LogicalAxis::Block
1505 }
1506 },
1507 };
1508 self.flip_logical_axis(wm, axis_to_flip);
1509 self
1510 }
1511}
1512
1513impl Parse for PositionArea {
1514 fn parse<'i, 't>(
1515 context: &ParserContext,
1516 input: &mut Parser<'i, 't>,
1517 ) -> Result<Self, ParseError<'i>> {
1518 Self::parse_internal(context, input, true)
1519 }
1520}
1521
1522pub trait Side {
1524 fn start() -> Self;
1526
1527 fn is_start(&self) -> bool;
1529}
1530
1531impl Side for HorizontalPositionKeyword {
1532 #[inline]
1533 fn start() -> Self {
1534 HorizontalPositionKeyword::Left
1535 }
1536
1537 #[inline]
1538 fn is_start(&self) -> bool {
1539 *self == Self::start()
1540 }
1541}
1542
1543impl Side for VerticalPositionKeyword {
1544 #[inline]
1545 fn start() -> Self {
1546 VerticalPositionKeyword::Top
1547 }
1548
1549 #[inline]
1550 fn is_start(&self) -> bool {
1551 *self == Self::start()
1552 }
1553}
1554
1555#[derive(
1559 Clone,
1560 Copy,
1561 Debug,
1562 Eq,
1563 MallocSizeOf,
1564 Parse,
1565 PartialEq,
1566 SpecifiedValueInfo,
1567 ToComputedValue,
1568 ToResolvedValue,
1569 ToShmem,
1570 ToTyped,
1571)]
1572#[css(bitflags(
1573 mixed = "row,column,dense",
1574 validate_mixed = "Self::validate_and_simplify"
1575))]
1576#[repr(C)]
1577pub struct GridAutoFlow(u8);
1578bitflags! {
1579 impl GridAutoFlow: u8 {
1580 const ROW = 1 << 0;
1582 const COLUMN = 1 << 1;
1584 const DENSE = 1 << 2;
1586 }
1587}
1588
1589impl GridAutoFlow {
1590 fn validate_and_simplify(&mut self) -> bool {
1592 if self.contains(Self::ROW | Self::COLUMN) {
1593 return false;
1595 }
1596 if *self == Self::DENSE {
1597 self.insert(Self::ROW);
1599 }
1600 true
1601 }
1602}
1603
1604impl ToCss for GridAutoFlow {
1605 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1606 where
1607 W: Write,
1608 {
1609 let dense = self.intersects(Self::DENSE);
1610 if self.intersects(Self::ROW) {
1611 return if dense {
1612 dest.write_str("dense")
1613 } else {
1614 dest.write_str("row")
1615 };
1616 }
1617 debug_assert!(self.intersects(Self::COLUMN));
1618 if dense {
1619 dest.write_str("column dense")
1620 } else {
1621 dest.write_str("column")
1622 }
1623 }
1624}
1625
1626#[repr(u8)]
1627#[derive(
1628 Clone,
1629 Copy,
1630 Debug,
1631 Eq,
1632 MallocSizeOf,
1633 PartialEq,
1634 SpecifiedValueInfo,
1635 ToComputedValue,
1636 ToCss,
1637 ToResolvedValue,
1638 ToShmem,
1639)]
1640pub enum MasonryPlacement {
1642 Pack,
1644 Next,
1646}
1647
1648#[repr(u8)]
1649#[derive(
1650 Clone,
1651 Copy,
1652 Debug,
1653 Eq,
1654 MallocSizeOf,
1655 PartialEq,
1656 SpecifiedValueInfo,
1657 ToComputedValue,
1658 ToCss,
1659 ToResolvedValue,
1660 ToShmem,
1661)]
1662pub enum MasonryItemOrder {
1664 DefiniteFirst,
1666 Ordered,
1668}
1669
1670#[derive(
1671 Clone,
1672 Copy,
1673 Debug,
1674 Eq,
1675 MallocSizeOf,
1676 PartialEq,
1677 SpecifiedValueInfo,
1678 ToComputedValue,
1679 ToCss,
1680 ToResolvedValue,
1681 ToShmem,
1682 ToTyped,
1683)]
1684#[repr(C)]
1685pub struct MasonryAutoFlow {
1688 #[css(contextual_skip_if = "is_pack_with_non_default_order")]
1690 pub placement: MasonryPlacement,
1691 #[css(skip_if = "is_item_order_definite_first")]
1693 pub order: MasonryItemOrder,
1694}
1695
1696#[inline]
1697fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
1698 *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
1699}
1700
1701#[inline]
1702fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
1703 *order == MasonryItemOrder::DefiniteFirst
1704}
1705
1706impl MasonryAutoFlow {
1707 #[inline]
1708 pub fn initial() -> MasonryAutoFlow {
1710 MasonryAutoFlow {
1711 placement: MasonryPlacement::Pack,
1712 order: MasonryItemOrder::DefiniteFirst,
1713 }
1714 }
1715}
1716
1717impl Parse for MasonryAutoFlow {
1718 fn parse<'i, 't>(
1720 _context: &ParserContext,
1721 input: &mut Parser<'i, 't>,
1722 ) -> Result<MasonryAutoFlow, ParseError<'i>> {
1723 let mut value = MasonryAutoFlow::initial();
1724 let mut got_placement = false;
1725 let mut got_order = false;
1726 while !input.is_exhausted() {
1727 let location = input.current_source_location();
1728 let ident = input.expect_ident()?;
1729 let success = match_ignore_ascii_case! { &ident,
1730 "pack" if !got_placement => {
1731 got_placement = true;
1732 true
1733 },
1734 "next" if !got_placement => {
1735 value.placement = MasonryPlacement::Next;
1736 got_placement = true;
1737 true
1738 },
1739 "definite-first" if !got_order => {
1740 got_order = true;
1741 true
1742 },
1743 "ordered" if !got_order => {
1744 value.order = MasonryItemOrder::Ordered;
1745 got_order = true;
1746 true
1747 },
1748 _ => false
1749 };
1750 if !success {
1751 return Err(location
1752 .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
1753 }
1754 }
1755
1756 if got_placement || got_order {
1757 Ok(value)
1758 } else {
1759 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1760 }
1761 }
1762}
1763
1764#[derive(
1765 Clone,
1766 Debug,
1767 MallocSizeOf,
1768 PartialEq,
1769 SpecifiedValueInfo,
1770 ToComputedValue,
1771 ToCss,
1772 ToResolvedValue,
1773 ToShmem,
1774)]
1775#[repr(C)]
1776pub struct TemplateAreas {
1778 #[css(skip)]
1780 pub areas: crate::OwnedSlice<NamedArea>,
1781 #[css(iterable)]
1786 pub strings: crate::OwnedSlice<crate::OwnedStr>,
1787 #[css(skip)]
1789 pub width: u32,
1790}
1791
1792#[derive(Default)]
1794pub struct TemplateAreasParser {
1795 areas: Vec<NamedArea>,
1796 area_indices: PrecomputedHashMap<Atom, usize>,
1797 strings: Vec<crate::OwnedStr>,
1798 width: u32,
1799 row: u32,
1800}
1801
1802impl TemplateAreasParser {
1803 pub fn try_parse_string<'i>(
1805 &mut self,
1806 input: &mut Parser<'i, '_>,
1807 ) -> Result<(), ParseError<'i>> {
1808 input.try_parse(|input| {
1809 self.parse_string(input.expect_string()?)
1810 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1811 })
1812 }
1813
1814 fn parse_string(&mut self, string: &str) -> Result<(), ()> {
1816 self.row += 1;
1817 let mut simplified_string = String::new();
1818 let mut current_area_index: Option<usize> = None;
1819 let mut column = 0u32;
1820 for token in TemplateAreasTokenizer(string) {
1821 column += 1;
1822 if column > 1 {
1823 simplified_string.push(' ');
1824 }
1825 let name = if let Some(token) = token? {
1826 simplified_string.push_str(token);
1827 Atom::from(token)
1828 } else {
1829 if let Some(index) = current_area_index.take() {
1830 if self.areas[index].columns.end != column {
1831 return Err(());
1832 }
1833 }
1834 simplified_string.push('.');
1835 continue;
1836 };
1837 if let Some(index) = current_area_index {
1838 if self.areas[index].name == name {
1839 if self.areas[index].rows.start == self.row {
1840 self.areas[index].columns.end += 1;
1841 }
1842 continue;
1843 }
1844 if self.areas[index].columns.end != column {
1845 return Err(());
1846 }
1847 }
1848 match self.area_indices.entry(name) {
1849 Entry::Occupied(ref e) => {
1850 let index = *e.get();
1851 if self.areas[index].columns.start != column
1852 || self.areas[index].rows.end != self.row
1853 {
1854 return Err(());
1855 }
1856 self.areas[index].rows.end += 1;
1857 current_area_index = Some(index);
1858 },
1859 Entry::Vacant(v) => {
1860 let index = self.areas.len();
1861 let name = v.key().clone();
1862 v.insert(index);
1863 self.areas.push(NamedArea {
1864 name,
1865 columns: UnsignedRange {
1866 start: column,
1867 end: column + 1,
1868 },
1869 rows: UnsignedRange {
1870 start: self.row,
1871 end: self.row + 1,
1872 },
1873 });
1874 current_area_index = Some(index);
1875 },
1876 }
1877 }
1878 if column == 0 {
1879 return Err(());
1882 }
1883 if let Some(index) = current_area_index {
1884 if self.areas[index].columns.end != column + 1 {
1885 debug_assert_ne!(self.areas[index].rows.start, self.row);
1886 return Err(());
1887 }
1888 }
1889 if self.row == 1 {
1890 self.width = column;
1891 } else if self.width != column {
1892 return Err(());
1893 }
1894
1895 self.strings.push(simplified_string.into());
1896 Ok(())
1897 }
1898
1899 pub fn finish(self) -> Result<TemplateAreas, ()> {
1901 if self.strings.is_empty() {
1902 return Err(());
1903 }
1904 Ok(TemplateAreas {
1905 areas: self.areas.into(),
1906 strings: self.strings.into(),
1907 width: self.width,
1908 })
1909 }
1910}
1911
1912impl TemplateAreas {
1913 fn parse_internal(input: &mut Parser) -> Result<Self, ()> {
1914 let mut parser = TemplateAreasParser::default();
1915 while parser.try_parse_string(input).is_ok() {}
1916 parser.finish()
1917 }
1918}
1919
1920impl Parse for TemplateAreas {
1921 fn parse<'i, 't>(
1922 _: &ParserContext,
1923 input: &mut Parser<'i, 't>,
1924 ) -> Result<Self, ParseError<'i>> {
1925 Self::parse_internal(input)
1926 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1927 }
1928}
1929
1930#[derive(
1932 Clone,
1933 Debug,
1934 MallocSizeOf,
1935 PartialEq,
1936 SpecifiedValueInfo,
1937 ToComputedValue,
1938 ToCss,
1939 ToResolvedValue,
1940 ToShmem,
1941)]
1942#[repr(transparent)]
1943pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
1944
1945impl Parse for TemplateAreasArc {
1946 fn parse<'i, 't>(
1947 context: &ParserContext,
1948 input: &mut Parser<'i, 't>,
1949 ) -> Result<Self, ParseError<'i>> {
1950 let parsed = TemplateAreas::parse(context, input)?;
1951 Ok(TemplateAreasArc(Arc::new(parsed)))
1952 }
1953}
1954
1955#[repr(C)]
1958#[derive(
1959 Clone,
1960 Debug,
1961 MallocSizeOf,
1962 PartialEq,
1963 SpecifiedValueInfo,
1964 ToComputedValue,
1965 ToResolvedValue,
1966 ToShmem,
1967)]
1968pub struct UnsignedRange {
1969 pub start: u32,
1971 pub end: u32,
1973}
1974
1975#[derive(
1976 Clone,
1977 Debug,
1978 MallocSizeOf,
1979 PartialEq,
1980 SpecifiedValueInfo,
1981 ToComputedValue,
1982 ToResolvedValue,
1983 ToShmem,
1984)]
1985#[repr(C)]
1986pub struct NamedArea {
1989 pub name: Atom,
1991 pub rows: UnsignedRange,
1993 pub columns: UnsignedRange,
1995}
1996
1997struct TemplateAreasTokenizer<'a>(&'a str);
2000
2001impl<'a> Iterator for TemplateAreasTokenizer<'a> {
2002 type Item = Result<Option<&'a str>, ()>;
2003
2004 fn next(&mut self) -> Option<Self::Item> {
2005 let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
2006 if rest.is_empty() {
2007 return None;
2008 }
2009 if rest.starts_with('.') {
2010 self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
2011 return Some(Ok(None));
2012 }
2013 if !rest.starts_with(is_name_code_point) {
2014 return Some(Err(()));
2015 }
2016 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
2017 let token = &rest[..token_len];
2018 self.0 = &rest[token_len..];
2019 Some(Ok(Some(token)))
2020 }
2021}
2022
2023fn is_name_code_point(c: char) -> bool {
2024 c >= 'A' && c <= 'Z'
2025 || c >= 'a' && c <= 'z'
2026 || c >= '\u{80}'
2027 || c == '_'
2028 || c >= '0' && c <= '9'
2029 || c == '-'
2030}
2031
2032#[repr(C, u8)]
2038#[derive(
2039 Clone,
2040 Debug,
2041 MallocSizeOf,
2042 Parse,
2043 PartialEq,
2044 SpecifiedValueInfo,
2045 ToComputedValue,
2046 ToCss,
2047 ToResolvedValue,
2048 ToShmem,
2049 ToTyped,
2050)]
2051pub enum GridTemplateAreas {
2052 None,
2054 Areas(TemplateAreasArc),
2056}
2057
2058impl GridTemplateAreas {
2059 #[inline]
2060 pub fn none() -> GridTemplateAreas {
2062 GridTemplateAreas::None
2063 }
2064}
2065
2066pub type ZIndex = GenericZIndex<Integer>;
2068
2069pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
2071
2072impl Parse for AspectRatio {
2073 fn parse<'i, 't>(
2074 context: &ParserContext,
2075 input: &mut Parser<'i, 't>,
2076 ) -> Result<Self, ParseError<'i>> {
2077 use crate::values::generics::position::PreferredRatio;
2078 use crate::values::specified::Ratio;
2079
2080 let location = input.current_source_location();
2081 let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2082 let ratio = input.try_parse(|i| Ratio::parse(context, i));
2083 if auto.is_err() {
2084 auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2085 }
2086
2087 if auto.is_err() && ratio.is_err() {
2088 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
2089 }
2090
2091 Ok(AspectRatio {
2092 auto: auto.is_ok(),
2093 ratio: match ratio {
2094 Ok(ratio) => PreferredRatio::Ratio(ratio),
2095 Err(..) => PreferredRatio::None,
2096 },
2097 })
2098 }
2099}
2100
2101impl AspectRatio {
2102 pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
2104 use crate::values::generics::position::PreferredRatio;
2105 use crate::values::generics::ratio::Ratio;
2106 AspectRatio {
2107 auto: true,
2108 ratio: PreferredRatio::Ratio(Ratio(
2109 NonNegativeNumber::new(w),
2110 NonNegativeNumber::new(h),
2111 )),
2112 }
2113 }
2114}
2115
2116pub type Inset = GenericInset<specified::Percentage, LengthPercentage>;
2118
2119impl Inset {
2120 #[inline]
2123 pub fn parse_quirky<'i, 't>(
2124 context: &ParserContext,
2125 input: &mut Parser<'i, 't>,
2126 allow_quirks: AllowQuirks,
2127 ) -> Result<Self, ParseError<'i>> {
2128 if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
2129 {
2130 return Ok(Self::LengthPercentage(l));
2131 }
2132 match input.try_parse(|i| i.expect_ident_matching("auto")) {
2133 Ok(_) => return Ok(Self::Auto),
2134 Err(e) if !static_prefs::pref!("layout.css.anchor-positioning.enabled") => {
2135 return Err(e.into());
2136 },
2137 Err(_) => (),
2138 };
2139 Self::parse_anchor_functions_quirky(context, input, allow_quirks)
2140 }
2141
2142 fn parse_as_anchor_function_fallback<'i, 't>(
2143 context: &ParserContext,
2144 input: &mut Parser<'i, 't>,
2145 ) -> Result<Self, ParseError<'i>> {
2146 if let Ok(l) =
2147 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No))
2148 {
2149 return Ok(Self::LengthPercentage(l));
2150 }
2151 Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No)
2152 }
2153
2154 fn parse_anchor_functions_quirky<'i, 't>(
2155 context: &ParserContext,
2156 input: &mut Parser<'i, 't>,
2157 allow_quirks: AllowQuirks,
2158 ) -> Result<Self, ParseError<'i>> {
2159 debug_assert!(
2160 static_prefs::pref!("layout.css.anchor-positioning.enabled"),
2161 "How are we parsing with pref off?"
2162 );
2163 if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) {
2164 return Ok(Self::AnchorFunction(Box::new(inner)));
2165 }
2166 if let Ok(inner) =
2167 input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i))
2168 {
2169 return Ok(Self::AnchorSizeFunction(Box::new(inner)));
2170 }
2171 Ok(Self::AnchorContainingCalcFunction(input.try_parse(
2172 |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks),
2173 )?))
2174 }
2175}
2176
2177impl Parse for Inset {
2178 fn parse<'i, 't>(
2179 context: &ParserContext,
2180 input: &mut Parser<'i, 't>,
2181 ) -> Result<Self, ParseError<'i>> {
2182 Self::parse_quirky(context, input, AllowQuirks::No)
2183 }
2184}
2185
2186pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>;
2188
2189impl Parse for AnchorFunction {
2190 fn parse<'i, 't>(
2191 context: &ParserContext,
2192 input: &mut Parser<'i, 't>,
2193 ) -> Result<Self, ParseError<'i>> {
2194 if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
2195 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
2196 }
2197 input.expect_function_matching("anchor")?;
2198 input.parse_nested_block(|i| {
2199 let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
2200 let side = GenericAnchorSide::parse(context, i)?;
2201 let target_element = if target_element.is_none() {
2202 i.try_parse(|i| DashedIdent::parse(context, i)).ok()
2203 } else {
2204 target_element
2205 };
2206 let fallback = i
2207 .try_parse(|i| {
2208 i.expect_comma()?;
2209 Inset::parse_as_anchor_function_fallback(context, i)
2210 })
2211 .ok();
2212 Ok(Self {
2213 target_element: TreeScoped::with_default_level(
2214 target_element.unwrap_or_else(DashedIdent::empty),
2215 ),
2216 side,
2217 fallback: fallback.into(),
2218 })
2219 })
2220 }
2221}