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