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::Position as GenericPosition;
19use crate::values::generics::position::PositionComponent as GenericPositionComponent;
20use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
21use crate::values::generics::position::ZIndex as GenericZIndex;
22use crate::values::generics::position::{AspectRatio as GenericAspectRatio, GenericAnchorSide};
23use crate::values::generics::position::{GenericAnchorFunction, GenericInset, TreeScoped};
24use crate::values::specified;
25use crate::values::specified::align::AlignFlags;
26use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
27use crate::values::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)]
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
402pub type AnchorName = TreeScoped<AnchorNameIdent>;
404
405impl AnchorName {
406 pub fn none() -> Self {
408 Self::with_default_level(AnchorNameIdent::none())
409 }
410}
411
412#[derive(
414 Clone,
415 Debug,
416 MallocSizeOf,
417 PartialEq,
418 SpecifiedValueInfo,
419 ToComputedValue,
420 ToCss,
421 ToResolvedValue,
422 ToShmem,
423 ToTyped,
424)]
425#[repr(u8)]
426pub enum AnchorScopeKeyword {
427 None,
429 All,
431 #[css(comma)]
433 Idents(
434 #[css(iterable)]
435 #[ignore_malloc_size_of = "Arc"]
436 crate::ArcSlice<DashedIdent>,
437 ),
438}
439
440impl AnchorScopeKeyword {
441 pub fn none() -> Self {
443 Self::None
444 }
445}
446
447impl Parse for AnchorScopeKeyword {
448 fn parse<'i, 't>(
449 context: &ParserContext,
450 input: &mut Parser<'i, 't>,
451 ) -> Result<Self, ParseError<'i>> {
452 let location = input.current_source_location();
453 let first = input.expect_ident()?;
454 if first.eq_ignore_ascii_case("none") {
455 return Ok(Self::None);
456 }
457 if first.eq_ignore_ascii_case("all") {
458 return Ok(Self::All);
459 }
460 let mut idents: SmallVec<[DashedIdent; 8]> =
463 smallvec![DashedIdent::from_ident(location, first,)?];
464 while input.try_parse(|input| input.expect_comma()).is_ok() {
465 idents.push(DashedIdent::parse(context, input)?);
466 }
467 Ok(AnchorScopeKeyword::Idents(ArcSlice::from_iter(
468 idents.drain(..),
469 )))
470 }
471}
472
473pub type AnchorScope = TreeScoped<AnchorScopeKeyword>;
475
476impl AnchorScope {
477 pub fn none() -> Self {
479 Self::with_default_level(AnchorScopeKeyword::none())
480 }
481}
482
483#[derive(
485 Clone,
486 Debug,
487 MallocSizeOf,
488 Parse,
489 PartialEq,
490 SpecifiedValueInfo,
491 ToComputedValue,
492 ToCss,
493 ToResolvedValue,
494 ToShmem,
495 ToTyped,
496)]
497#[repr(u8)]
498pub enum PositionAnchorKeyword {
499 None,
501 Auto,
503 Ident(DashedIdent),
505}
506
507impl PositionAnchorKeyword {
508 pub fn none() -> Self {
510 Self::None
511 }
512}
513
514pub type PositionAnchor = TreeScoped<PositionAnchorKeyword>;
516
517impl PositionAnchor {
518 pub fn none() -> Self {
520 Self::with_default_level(PositionAnchorKeyword::none())
521 }
522}
523
524#[derive(
525 Clone,
526 Copy,
527 Debug,
528 Eq,
529 MallocSizeOf,
530 Parse,
531 PartialEq,
532 Serialize,
533 SpecifiedValueInfo,
534 ToComputedValue,
535 ToCss,
536 ToResolvedValue,
537 ToShmem,
538)]
539#[repr(u8)]
540pub enum PositionTryFallbacksTryTacticKeyword {
542 FlipBlock,
544 FlipInline,
546 FlipStart,
548 FlipX,
550 FlipY,
552}
553
554#[derive(
555 Clone,
556 Debug,
557 Default,
558 Eq,
559 MallocSizeOf,
560 PartialEq,
561 SpecifiedValueInfo,
562 ToComputedValue,
563 ToCss,
564 ToResolvedValue,
565 ToShmem,
566)]
567#[repr(transparent)]
568pub struct PositionTryFallbacksTryTactic(
573 #[css(iterable)] pub ThinVec<PositionTryFallbacksTryTacticKeyword>,
574);
575
576impl Parse for PositionTryFallbacksTryTactic {
577 fn parse<'i, 't>(
578 _context: &ParserContext,
579 input: &mut Parser<'i, 't>,
580 ) -> Result<Self, ParseError<'i>> {
581 let mut result = ThinVec::with_capacity(5);
582 for _ in 0..5 {
584 if let Ok(kw) = input.try_parse(PositionTryFallbacksTryTacticKeyword::parse) {
585 if result.contains(&kw) {
586 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
587 }
588 result.push(kw);
589 } else {
590 break;
591 }
592 }
593 if result.is_empty() {
594 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
595 }
596 Ok(Self(result))
597 }
598}
599
600impl PositionTryFallbacksTryTactic {
601 #[inline]
603 pub fn is_empty(&self) -> bool {
604 self.0.is_empty()
605 }
606
607 #[inline]
609 pub fn iter(&self) -> impl Iterator<Item = &PositionTryFallbacksTryTacticKeyword> {
610 self.0.iter()
611 }
612}
613
614#[derive(
615 Clone,
616 Debug,
617 MallocSizeOf,
618 PartialEq,
619 SpecifiedValueInfo,
620 ToComputedValue,
621 ToCss,
622 ToResolvedValue,
623 ToShmem,
624)]
625#[repr(C)]
626pub struct DashedIdentAndOrTryTactic {
629 pub ident: DashedIdent,
631 pub try_tactic: PositionTryFallbacksTryTactic,
633}
634
635impl Parse for DashedIdentAndOrTryTactic {
636 fn parse<'i, 't>(
637 context: &ParserContext,
638 input: &mut Parser<'i, 't>,
639 ) -> Result<Self, ParseError<'i>> {
640 let mut result = Self {
641 ident: DashedIdent::empty(),
642 try_tactic: PositionTryFallbacksTryTactic::default(),
643 };
644
645 loop {
646 if result.ident.is_empty() {
647 if let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i)) {
648 result.ident = ident;
649 continue;
650 }
651 }
652 if result.try_tactic.is_empty() {
653 if let Ok(try_tactic) =
654 input.try_parse(|i| PositionTryFallbacksTryTactic::parse(context, i))
655 {
656 result.try_tactic = try_tactic;
657 continue;
658 }
659 }
660 break;
661 }
662
663 if result.ident.is_empty() && result.try_tactic.is_empty() {
664 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
665 }
666 return Ok(result);
667 }
668}
669
670#[derive(
671 Clone,
672 Debug,
673 MallocSizeOf,
674 Parse,
675 PartialEq,
676 SpecifiedValueInfo,
677 ToComputedValue,
678 ToCss,
679 ToResolvedValue,
680 ToShmem,
681)]
682#[repr(u8)]
683pub enum PositionTryFallbacksItem {
686 IdentAndOrTactic(DashedIdentAndOrTryTactic),
688 #[parse(parse_fn = "PositionArea::parse_except_none")]
689 PositionArea(PositionArea),
691}
692
693#[derive(
694 Clone,
695 Debug,
696 Default,
697 MallocSizeOf,
698 PartialEq,
699 SpecifiedValueInfo,
700 ToComputedValue,
701 ToCss,
702 ToResolvedValue,
703 ToShmem,
704 ToTyped,
705)]
706#[css(comma)]
707#[repr(C)]
708pub struct PositionTryFallbacks(
710 #[css(iterable, if_empty = "none")]
711 #[ignore_malloc_size_of = "Arc"]
712 pub crate::ArcSlice<PositionTryFallbacksItem>,
713);
714
715impl PositionTryFallbacks {
716 #[inline]
717 pub fn none() -> Self {
719 Self(Default::default())
720 }
721
722 pub fn is_none(&self) -> bool {
724 self.0.is_empty()
725 }
726}
727
728impl Parse for PositionTryFallbacks {
729 fn parse<'i, 't>(
730 context: &ParserContext,
731 input: &mut Parser<'i, 't>,
732 ) -> Result<Self, ParseError<'i>> {
733 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
734 return Ok(Self::none());
735 }
736 let mut items: SmallVec<[PositionTryFallbacksItem; 4]> =
739 smallvec![PositionTryFallbacksItem::parse(context, input)?];
740 while input.try_parse(|input| input.expect_comma()).is_ok() {
741 items.push(PositionTryFallbacksItem::parse(context, input)?);
742 }
743 Ok(Self(ArcSlice::from_iter(items.drain(..))))
744 }
745}
746
747#[derive(
749 Clone,
750 Copy,
751 Debug,
752 Default,
753 Eq,
754 MallocSizeOf,
755 Parse,
756 PartialEq,
757 SpecifiedValueInfo,
758 ToComputedValue,
759 ToCss,
760 ToResolvedValue,
761 ToShmem,
762 ToTyped,
763)]
764#[repr(u8)]
765pub enum PositionTryOrder {
766 #[default]
767 Normal,
769 MostWidth,
771 MostHeight,
773 MostBlockSize,
775 MostInlineSize,
777}
778
779impl PositionTryOrder {
780 #[inline]
781 pub fn normal() -> Self {
783 Self::Normal
784 }
785
786 pub fn is_normal(&self) -> bool {
788 *self == Self::Normal
789 }
790}
791
792#[derive(
793 Clone,
794 Copy,
795 Debug,
796 Eq,
797 MallocSizeOf,
798 Parse,
799 PartialEq,
800 Serialize,
801 SpecifiedValueInfo,
802 ToComputedValue,
803 ToCss,
804 ToResolvedValue,
805 ToShmem,
806 ToTyped,
807)]
808#[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))]
809#[repr(C)]
810pub struct PositionVisibility(u8);
812bitflags! {
813 impl PositionVisibility: u8 {
814 const ALWAYS = 0;
816 const ANCHORS_VALID = 1 << 0;
818 const ANCHORS_VISIBLE = 1 << 1;
820 const NO_OVERFLOW = 1 << 2;
822 }
823}
824
825impl Default for PositionVisibility {
826 fn default() -> Self {
827 Self::ALWAYS
828 }
829}
830
831impl PositionVisibility {
832 #[inline]
833 pub fn always() -> Self {
835 Self::ALWAYS
836 }
837}
838
839#[repr(u8)]
842#[derive(Clone, Copy, Debug, Eq, PartialEq)]
843pub enum PositionAreaType {
844 Physical,
846 Logical,
848 SelfLogical,
850 Inferred,
852 SelfInferred,
854 Common,
856 None,
858}
859
860#[repr(u8)]
867#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
868#[allow(missing_docs)]
869pub enum PositionAreaAxis {
870 Horizontal = 0b000,
871 Vertical = 0b001,
872
873 X = 0b010,
874 Y = 0b011,
875
876 Block = 0b110,
877 Inline = 0b111,
878
879 Inferred = 0b100,
880 None = 0b101,
881}
882
883impl PositionAreaAxis {
884 pub fn is_physical(self) -> bool {
886 (self as u8 & 0b100) == 0
887 }
888
889 fn is_flow_relative_direction(self) -> bool {
891 self == Self::Inferred || (self as u8 & 0b10) != 0
892 }
893
894 fn is_canonically_first(self) -> bool {
896 self != Self::Inferred && (self as u8) & 1 == 0
897 }
898
899 #[allow(unused)]
900 fn flip(self) -> Self {
901 if matches!(self, Self::Inferred | Self::None) {
902 return self;
903 }
904 Self::from_u8(self as u8 ^ 1u8).unwrap()
905 }
906
907 fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> {
908 Some(match self {
909 PositionAreaAxis::Horizontal | PositionAreaAxis::X => {
910 if wm.is_vertical() {
911 LogicalAxis::Block
912 } else {
913 LogicalAxis::Inline
914 }
915 },
916 PositionAreaAxis::Vertical | PositionAreaAxis::Y => {
917 if wm.is_vertical() {
918 LogicalAxis::Inline
919 } else {
920 LogicalAxis::Block
921 }
922 },
923 PositionAreaAxis::Block => LogicalAxis::Block,
924 PositionAreaAxis::Inline => LogicalAxis::Inline,
925 PositionAreaAxis::Inferred => inferred,
926 PositionAreaAxis::None => return None,
927 })
928 }
929}
930
931#[repr(u8)]
934#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
935pub enum PositionAreaTrack {
936 Start = 0b001,
938 SpanStart = 0b011,
940 End = 0b100,
942 SpanEnd = 0b110,
944 Center = 0b010,
946 SpanAll = 0b111,
948}
949
950impl PositionAreaTrack {
951 fn flip(self) -> Self {
952 match self {
953 Self::Start => Self::End,
954 Self::SpanStart => Self::SpanEnd,
955 Self::End => Self::Start,
956 Self::SpanEnd => Self::SpanStart,
957 Self::Center | Self::SpanAll => self,
958 }
959 }
960
961 fn start(self) -> bool {
962 self as u8 & 1 != 0
963 }
964}
965
966pub const AXIS_SHIFT: usize = 3;
968pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT;
970pub const TRACK_MASK: u8 = 0b111u8;
972pub const SELF_WM: u8 = 1u8 << 6;
974
975#[derive(
976 Clone,
977 Copy,
978 Debug,
979 Default,
980 Eq,
981 MallocSizeOf,
982 Parse,
983 PartialEq,
984 SpecifiedValueInfo,
985 ToComputedValue,
986 ToCss,
987 ToResolvedValue,
988 ToShmem,
989 FromPrimitive,
990)]
991#[allow(missing_docs)]
992#[repr(u8)]
993pub enum PositionAreaKeyword {
998 #[default]
999 None = (PositionAreaAxis::None as u8) << AXIS_SHIFT,
1000
1001 Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8,
1003 SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8,
1004
1005 Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1007 End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1008 SpanStart =
1009 ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1010 SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1011
1012 Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1014 Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1015 Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1016 Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1017
1018 XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1020 XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1021 YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1022 YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1023
1024 BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1026 BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1027 InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1028 InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1029
1030 SpanLeft =
1032 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1033 SpanRight =
1034 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1035 SpanTop =
1036 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1037 SpanBottom =
1038 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1039
1040 SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1042 SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1043 SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1044 SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1045
1046 SpanBlockStart =
1048 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1049 SpanBlockEnd =
1050 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1051 SpanInlineStart =
1052 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1053 SpanInlineEnd =
1054 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1055
1056 SelfStart = SELF_WM | (Self::Start as u8),
1058 SelfEnd = SELF_WM | (Self::End as u8),
1059 SpanSelfStart = SELF_WM | (Self::SpanStart as u8),
1060 SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8),
1061
1062 SelfXStart = SELF_WM | (Self::XStart as u8),
1063 SelfXEnd = SELF_WM | (Self::XEnd as u8),
1064 SelfYStart = SELF_WM | (Self::YStart as u8),
1065 SelfYEnd = SELF_WM | (Self::YEnd as u8),
1066 SelfBlockStart = SELF_WM | (Self::BlockStart as u8),
1067 SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8),
1068 SelfInlineStart = SELF_WM | (Self::InlineStart as u8),
1069 SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8),
1070
1071 SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8),
1072 SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8),
1073 SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8),
1074 SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8),
1075 SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8),
1076 SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8),
1077 SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8),
1078 SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8),
1079}
1080
1081impl PositionAreaKeyword {
1082 #[inline]
1084 pub fn none() -> Self {
1085 Self::None
1086 }
1087
1088 pub fn is_none(&self) -> bool {
1090 *self == Self::None
1091 }
1092
1093 pub fn self_wm(self) -> bool {
1095 (self as u8 & SELF_WM) != 0
1096 }
1097
1098 pub fn axis(self) -> PositionAreaAxis {
1100 PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap()
1101 }
1102
1103 pub fn with_axis(self, axis: PositionAreaAxis) -> Self {
1105 Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap()
1106 }
1107
1108 pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self {
1110 if self.axis() == PositionAreaAxis::Inferred {
1111 self.with_axis(axis)
1112 } else {
1113 self
1114 }
1115 }
1116
1117 pub fn track(self) -> Option<PositionAreaTrack> {
1119 let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK);
1120 debug_assert_eq!(
1121 result.is_none(),
1122 self.is_none(),
1123 "Only the none keyword has no track"
1124 );
1125 result
1126 }
1127
1128 fn group_type(self) -> PositionAreaType {
1129 let axis = self.axis();
1130 if axis == PositionAreaAxis::None {
1131 if self.is_none() {
1132 return PositionAreaType::None;
1133 }
1134 return PositionAreaType::Common;
1135 }
1136 if axis == PositionAreaAxis::Inferred {
1137 return if self.self_wm() {
1138 PositionAreaType::SelfInferred
1139 } else {
1140 PositionAreaType::Inferred
1141 };
1142 }
1143 if axis.is_physical() {
1144 return PositionAreaType::Physical;
1145 }
1146 if self.self_wm() {
1147 PositionAreaType::SelfLogical
1148 } else {
1149 PositionAreaType::Logical
1150 }
1151 }
1152
1153 fn to_physical(
1154 self,
1155 cb_wm: WritingMode,
1156 self_wm: WritingMode,
1157 inferred_axis: LogicalAxis,
1158 ) -> Self {
1159 let wm = if self.self_wm() { self_wm } else { cb_wm };
1160 let axis = self.axis();
1161 if !axis.is_flow_relative_direction() {
1162 return self;
1163 }
1164 let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else {
1165 return self;
1166 };
1167 let Some(track) = self.track() else {
1168 debug_assert!(false, "How did we end up with no track here? {self:?}");
1169 return self;
1170 };
1171 let start = track.start();
1172 let logical_side = match logical_axis {
1173 LogicalAxis::Block => {
1174 if start {
1175 LogicalSide::BlockStart
1176 } else {
1177 LogicalSide::BlockEnd
1178 }
1179 },
1180 LogicalAxis::Inline => {
1181 if start {
1182 LogicalSide::InlineStart
1183 } else {
1184 LogicalSide::InlineEnd
1185 }
1186 },
1187 };
1188 let physical_side = logical_side.to_physical(wm);
1189 let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left);
1190 let new_track = if physical_start != start {
1191 track.flip()
1192 } else {
1193 track
1194 };
1195 let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) {
1196 PositionAreaAxis::Vertical
1197 } else {
1198 PositionAreaAxis::Horizontal
1199 };
1200 Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap()
1201 }
1202
1203 fn flip_track(self) -> Self {
1204 let Some(old_track) = self.track() else {
1205 return self;
1206 };
1207 let new_track = old_track.flip();
1208 Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap()
1209 }
1210
1211 pub fn to_self_alignment(self, axis: LogicalAxis, cb_wm: &WritingMode) -> Option<AlignFlags> {
1219 let track = self.track()?;
1220 Some(match track {
1221 PositionAreaTrack::Center => AlignFlags::CENTER,
1223 PositionAreaTrack::SpanAll => AlignFlags::ANCHOR_CENTER,
1225 _ => {
1228 debug_assert_eq!(self.group_type(), PositionAreaType::Physical);
1229 if axis == LogicalAxis::Inline {
1230 if track.start() == cb_wm.intersects(WritingMode::INLINE_REVERSED) {
1234 AlignFlags::START
1235 } else {
1236 AlignFlags::END
1237 }
1238 } else {
1239 if track.start() == cb_wm.is_vertical_rl() {
1242 AlignFlags::START
1243 } else {
1244 AlignFlags::END
1245 }
1246 }
1247 },
1248 })
1249 }
1250}
1251
1252#[derive(
1253 Clone,
1254 Copy,
1255 Debug,
1256 Eq,
1257 MallocSizeOf,
1258 PartialEq,
1259 SpecifiedValueInfo,
1260 ToCss,
1261 ToResolvedValue,
1262 ToShmem,
1263 ToTyped,
1264)]
1265#[repr(C)]
1266pub struct PositionArea {
1268 pub first: PositionAreaKeyword,
1270 #[css(skip_if = "PositionAreaKeyword::is_none")]
1272 pub second: PositionAreaKeyword,
1273}
1274
1275impl PositionArea {
1276 #[inline]
1278 pub fn none() -> Self {
1279 Self {
1280 first: PositionAreaKeyword::None,
1281 second: PositionAreaKeyword::None,
1282 }
1283 }
1284
1285 #[inline]
1287 pub fn is_none(&self) -> bool {
1288 self.first.is_none()
1289 }
1290
1291 pub fn parse_except_none<'i, 't>(
1293 context: &ParserContext,
1294 input: &mut Parser<'i, 't>,
1295 ) -> Result<Self, ParseError<'i>> {
1296 Self::parse_internal(context, input, false)
1297 }
1298
1299 pub fn get_type(&self) -> PositionAreaType {
1301 let first = self.first.group_type();
1302 let second = self.second.group_type();
1303 if matches!(second, PositionAreaType::None | PositionAreaType::Common) {
1304 return first;
1305 }
1306 if first == PositionAreaType::Common {
1307 return second;
1308 }
1309 if first != second {
1310 return PositionAreaType::None;
1311 }
1312 let first_axis = self.first.axis();
1313 if first_axis != PositionAreaAxis::Inferred
1314 && first_axis.is_canonically_first() == self.second.axis().is_canonically_first()
1315 {
1316 return PositionAreaType::None;
1317 }
1318 first
1319 }
1320
1321 fn parse_internal<'i, 't>(
1322 _: &ParserContext,
1323 input: &mut Parser<'i, 't>,
1324 allow_none: bool,
1325 ) -> Result<Self, ParseError<'i>> {
1326 let mut location = input.current_source_location();
1327 let mut first = PositionAreaKeyword::parse(input)?;
1328 if first.is_none() {
1329 if allow_none {
1330 return Ok(Self::none());
1331 }
1332 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1333 }
1334
1335 location = input.current_source_location();
1336 let second = input.try_parse(PositionAreaKeyword::parse);
1337 if let Ok(PositionAreaKeyword::None) = second {
1338 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1340 }
1341 let mut second = second.unwrap_or(PositionAreaKeyword::None);
1342 if second.is_none() {
1343 return Ok(Self { first, second });
1349 }
1350
1351 let pair_type = Self { first, second }.get_type();
1352 if pair_type == PositionAreaType::None {
1353 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1355 }
1356 if matches!(
1359 pair_type,
1360 PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical
1361 ) {
1362 if second == PositionAreaKeyword::SpanAll {
1363 second = PositionAreaKeyword::None;
1366 } else if first == PositionAreaKeyword::SpanAll {
1367 first = second;
1368 second = PositionAreaKeyword::None;
1369 }
1370 }
1371 if first == second {
1372 second = PositionAreaKeyword::None;
1373 }
1374 let mut result = Self { first, second };
1375 result.canonicalize_order();
1376 Ok(result)
1377 }
1378
1379 fn canonicalize_order(&mut self) {
1380 let first_axis = self.first.axis();
1381 if first_axis.is_canonically_first() || self.second.is_none() {
1382 return;
1383 }
1384 let second_axis = self.second.axis();
1385 if first_axis == second_axis {
1386 return;
1388 }
1389 if second_axis.is_canonically_first()
1390 || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred)
1391 {
1392 std::mem::swap(&mut self.first, &mut self.second);
1393 }
1394 }
1395
1396 fn make_missing_second_explicit(&mut self) {
1397 if !self.second.is_none() {
1398 return;
1399 }
1400 let axis = self.first.axis();
1401 if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) {
1402 self.second = self.first;
1403 return;
1404 }
1405 self.second = PositionAreaKeyword::SpanAll;
1406 if !axis.is_canonically_first() {
1407 std::mem::swap(&mut self.first, &mut self.second);
1408 }
1409 }
1410
1411 pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self {
1413 self.make_missing_second_explicit();
1414 if self.first.axis() == PositionAreaAxis::None
1420 && self.second.axis() == PositionAreaAxis::None
1421 && !cb_wm.is_vertical()
1422 {
1423 std::mem::swap(&mut self.first, &mut self.second);
1424 } else {
1425 self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block);
1426 self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline);
1427 self.canonicalize_order();
1428 }
1429 self
1430 }
1431
1432 fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) {
1433 if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) {
1434 self.first = self.first.flip_track();
1435 } else {
1436 self.second = self.second.flip_track();
1437 }
1438 }
1439
1440 fn flip_start(&mut self) {
1441 self.first = self.first.with_axis(self.first.axis().flip());
1442 self.second = self.second.with_axis(self.second.axis().flip());
1443 }
1444
1445 pub fn with_tactic(
1447 mut self,
1448 wm: WritingMode,
1449 tactic: PositionTryFallbacksTryTacticKeyword,
1450 ) -> Self {
1451 self.make_missing_second_explicit();
1452 let axis_to_flip = match tactic {
1453 PositionTryFallbacksTryTacticKeyword::FlipStart => {
1454 self.flip_start();
1455 return self;
1456 },
1457 PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block,
1458 PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline,
1459 PositionTryFallbacksTryTacticKeyword::FlipX => {
1460 if wm.is_horizontal() {
1461 LogicalAxis::Inline
1462 } else {
1463 LogicalAxis::Block
1464 }
1465 },
1466 PositionTryFallbacksTryTacticKeyword::FlipY => {
1467 if wm.is_vertical() {
1468 LogicalAxis::Inline
1469 } else {
1470 LogicalAxis::Block
1471 }
1472 },
1473 };
1474 self.flip_logical_axis(wm, axis_to_flip);
1475 self
1476 }
1477}
1478
1479impl Parse for PositionArea {
1480 fn parse<'i, 't>(
1481 context: &ParserContext,
1482 input: &mut Parser<'i, 't>,
1483 ) -> Result<Self, ParseError<'i>> {
1484 Self::parse_internal(context, input, true)
1485 }
1486}
1487
1488pub trait Side {
1490 fn start() -> Self;
1492
1493 fn is_start(&self) -> bool;
1495}
1496
1497impl Side for HorizontalPositionKeyword {
1498 #[inline]
1499 fn start() -> Self {
1500 HorizontalPositionKeyword::Left
1501 }
1502
1503 #[inline]
1504 fn is_start(&self) -> bool {
1505 *self == Self::start()
1506 }
1507}
1508
1509impl Side for VerticalPositionKeyword {
1510 #[inline]
1511 fn start() -> Self {
1512 VerticalPositionKeyword::Top
1513 }
1514
1515 #[inline]
1516 fn is_start(&self) -> bool {
1517 *self == Self::start()
1518 }
1519}
1520
1521#[derive(
1525 Clone,
1526 Copy,
1527 Debug,
1528 Eq,
1529 MallocSizeOf,
1530 Parse,
1531 PartialEq,
1532 SpecifiedValueInfo,
1533 ToComputedValue,
1534 ToResolvedValue,
1535 ToShmem,
1536 ToTyped,
1537)]
1538#[css(bitflags(
1539 mixed = "row,column,dense",
1540 validate_mixed = "Self::validate_and_simplify"
1541))]
1542#[repr(C)]
1543pub struct GridAutoFlow(u8);
1544bitflags! {
1545 impl GridAutoFlow: u8 {
1546 const ROW = 1 << 0;
1548 const COLUMN = 1 << 1;
1550 const DENSE = 1 << 2;
1552 }
1553}
1554
1555impl GridAutoFlow {
1556 fn validate_and_simplify(&mut self) -> bool {
1558 if self.contains(Self::ROW | Self::COLUMN) {
1559 return false;
1561 }
1562 if *self == Self::DENSE {
1563 self.insert(Self::ROW);
1565 }
1566 true
1567 }
1568}
1569
1570impl ToCss for GridAutoFlow {
1571 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1572 where
1573 W: Write,
1574 {
1575 let dense = self.intersects(Self::DENSE);
1576 if self.intersects(Self::ROW) {
1577 return if dense {
1578 dest.write_str("dense")
1579 } else {
1580 dest.write_str("row")
1581 };
1582 }
1583 debug_assert!(self.intersects(Self::COLUMN));
1584 if dense {
1585 dest.write_str("column dense")
1586 } else {
1587 dest.write_str("column")
1588 }
1589 }
1590}
1591
1592#[repr(u8)]
1593#[derive(
1594 Clone,
1595 Copy,
1596 Debug,
1597 Eq,
1598 MallocSizeOf,
1599 PartialEq,
1600 SpecifiedValueInfo,
1601 ToComputedValue,
1602 ToCss,
1603 ToResolvedValue,
1604 ToShmem,
1605)]
1606pub enum MasonryPlacement {
1608 Pack,
1610 Next,
1612}
1613
1614#[repr(u8)]
1615#[derive(
1616 Clone,
1617 Copy,
1618 Debug,
1619 Eq,
1620 MallocSizeOf,
1621 PartialEq,
1622 SpecifiedValueInfo,
1623 ToComputedValue,
1624 ToCss,
1625 ToResolvedValue,
1626 ToShmem,
1627)]
1628pub enum MasonryItemOrder {
1630 DefiniteFirst,
1632 Ordered,
1634}
1635
1636#[derive(
1637 Clone,
1638 Copy,
1639 Debug,
1640 Eq,
1641 MallocSizeOf,
1642 PartialEq,
1643 SpecifiedValueInfo,
1644 ToComputedValue,
1645 ToCss,
1646 ToResolvedValue,
1647 ToShmem,
1648 ToTyped,
1649)]
1650#[repr(C)]
1651pub struct MasonryAutoFlow {
1654 #[css(contextual_skip_if = "is_pack_with_non_default_order")]
1656 pub placement: MasonryPlacement,
1657 #[css(skip_if = "is_item_order_definite_first")]
1659 pub order: MasonryItemOrder,
1660}
1661
1662#[inline]
1663fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
1664 *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
1665}
1666
1667#[inline]
1668fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
1669 *order == MasonryItemOrder::DefiniteFirst
1670}
1671
1672impl MasonryAutoFlow {
1673 #[inline]
1674 pub fn initial() -> MasonryAutoFlow {
1676 MasonryAutoFlow {
1677 placement: MasonryPlacement::Pack,
1678 order: MasonryItemOrder::DefiniteFirst,
1679 }
1680 }
1681}
1682
1683impl Parse for MasonryAutoFlow {
1684 fn parse<'i, 't>(
1686 _context: &ParserContext,
1687 input: &mut Parser<'i, 't>,
1688 ) -> Result<MasonryAutoFlow, ParseError<'i>> {
1689 let mut value = MasonryAutoFlow::initial();
1690 let mut got_placement = false;
1691 let mut got_order = false;
1692 while !input.is_exhausted() {
1693 let location = input.current_source_location();
1694 let ident = input.expect_ident()?;
1695 let success = match_ignore_ascii_case! { &ident,
1696 "pack" if !got_placement => {
1697 got_placement = true;
1698 true
1699 },
1700 "next" if !got_placement => {
1701 value.placement = MasonryPlacement::Next;
1702 got_placement = true;
1703 true
1704 },
1705 "definite-first" if !got_order => {
1706 got_order = true;
1707 true
1708 },
1709 "ordered" if !got_order => {
1710 value.order = MasonryItemOrder::Ordered;
1711 got_order = true;
1712 true
1713 },
1714 _ => false
1715 };
1716 if !success {
1717 return Err(location
1718 .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
1719 }
1720 }
1721
1722 if got_placement || got_order {
1723 Ok(value)
1724 } else {
1725 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1726 }
1727 }
1728}
1729
1730#[derive(
1731 Clone,
1732 Debug,
1733 MallocSizeOf,
1734 PartialEq,
1735 SpecifiedValueInfo,
1736 ToComputedValue,
1737 ToCss,
1738 ToResolvedValue,
1739 ToShmem,
1740)]
1741#[repr(C)]
1742pub struct TemplateAreas {
1744 #[css(skip)]
1746 pub areas: crate::OwnedSlice<NamedArea>,
1747 #[css(iterable)]
1752 pub strings: crate::OwnedSlice<crate::OwnedStr>,
1753 #[css(skip)]
1755 pub width: u32,
1756}
1757
1758#[derive(Default)]
1760pub struct TemplateAreasParser {
1761 areas: Vec<NamedArea>,
1762 area_indices: PrecomputedHashMap<Atom, usize>,
1763 strings: Vec<crate::OwnedStr>,
1764 width: u32,
1765 row: u32,
1766}
1767
1768impl TemplateAreasParser {
1769 pub fn try_parse_string<'i>(
1771 &mut self,
1772 input: &mut Parser<'i, '_>,
1773 ) -> Result<(), ParseError<'i>> {
1774 input.try_parse(|input| {
1775 self.parse_string(input.expect_string()?)
1776 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1777 })
1778 }
1779
1780 fn parse_string(&mut self, string: &str) -> Result<(), ()> {
1782 self.row += 1;
1783 let mut simplified_string = String::new();
1784 let mut current_area_index: Option<usize> = None;
1785 let mut column = 0u32;
1786 for token in TemplateAreasTokenizer(string) {
1787 column += 1;
1788 if column > 1 {
1789 simplified_string.push(' ');
1790 }
1791 let name = if let Some(token) = token? {
1792 simplified_string.push_str(token);
1793 Atom::from(token)
1794 } else {
1795 if let Some(index) = current_area_index.take() {
1796 if self.areas[index].columns.end != column {
1797 return Err(());
1798 }
1799 }
1800 simplified_string.push('.');
1801 continue;
1802 };
1803 if let Some(index) = current_area_index {
1804 if self.areas[index].name == name {
1805 if self.areas[index].rows.start == self.row {
1806 self.areas[index].columns.end += 1;
1807 }
1808 continue;
1809 }
1810 if self.areas[index].columns.end != column {
1811 return Err(());
1812 }
1813 }
1814 match self.area_indices.entry(name) {
1815 Entry::Occupied(ref e) => {
1816 let index = *e.get();
1817 if self.areas[index].columns.start != column
1818 || self.areas[index].rows.end != self.row
1819 {
1820 return Err(());
1821 }
1822 self.areas[index].rows.end += 1;
1823 current_area_index = Some(index);
1824 },
1825 Entry::Vacant(v) => {
1826 let index = self.areas.len();
1827 let name = v.key().clone();
1828 v.insert(index);
1829 self.areas.push(NamedArea {
1830 name,
1831 columns: UnsignedRange {
1832 start: column,
1833 end: column + 1,
1834 },
1835 rows: UnsignedRange {
1836 start: self.row,
1837 end: self.row + 1,
1838 },
1839 });
1840 current_area_index = Some(index);
1841 },
1842 }
1843 }
1844 if column == 0 {
1845 return Err(());
1848 }
1849 if let Some(index) = current_area_index {
1850 if self.areas[index].columns.end != column + 1 {
1851 debug_assert_ne!(self.areas[index].rows.start, self.row);
1852 return Err(());
1853 }
1854 }
1855 if self.row == 1 {
1856 self.width = column;
1857 } else if self.width != column {
1858 return Err(());
1859 }
1860
1861 self.strings.push(simplified_string.into());
1862 Ok(())
1863 }
1864
1865 pub fn finish(self) -> Result<TemplateAreas, ()> {
1867 if self.strings.is_empty() {
1868 return Err(());
1869 }
1870 Ok(TemplateAreas {
1871 areas: self.areas.into(),
1872 strings: self.strings.into(),
1873 width: self.width,
1874 })
1875 }
1876}
1877
1878impl TemplateAreas {
1879 fn parse_internal(input: &mut Parser) -> Result<Self, ()> {
1880 let mut parser = TemplateAreasParser::default();
1881 while parser.try_parse_string(input).is_ok() {}
1882 parser.finish()
1883 }
1884}
1885
1886impl Parse for TemplateAreas {
1887 fn parse<'i, 't>(
1888 _: &ParserContext,
1889 input: &mut Parser<'i, 't>,
1890 ) -> Result<Self, ParseError<'i>> {
1891 Self::parse_internal(input)
1892 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1893 }
1894}
1895
1896#[derive(
1898 Clone,
1899 Debug,
1900 MallocSizeOf,
1901 PartialEq,
1902 SpecifiedValueInfo,
1903 ToComputedValue,
1904 ToCss,
1905 ToResolvedValue,
1906 ToShmem,
1907)]
1908#[repr(transparent)]
1909pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
1910
1911impl Parse for TemplateAreasArc {
1912 fn parse<'i, 't>(
1913 context: &ParserContext,
1914 input: &mut Parser<'i, 't>,
1915 ) -> Result<Self, ParseError<'i>> {
1916 let parsed = TemplateAreas::parse(context, input)?;
1917 Ok(TemplateAreasArc(Arc::new(parsed)))
1918 }
1919}
1920
1921#[repr(C)]
1924#[derive(
1925 Clone,
1926 Debug,
1927 MallocSizeOf,
1928 PartialEq,
1929 SpecifiedValueInfo,
1930 ToComputedValue,
1931 ToResolvedValue,
1932 ToShmem,
1933)]
1934pub struct UnsignedRange {
1935 pub start: u32,
1937 pub end: u32,
1939}
1940
1941#[derive(
1942 Clone,
1943 Debug,
1944 MallocSizeOf,
1945 PartialEq,
1946 SpecifiedValueInfo,
1947 ToComputedValue,
1948 ToResolvedValue,
1949 ToShmem,
1950)]
1951#[repr(C)]
1952pub struct NamedArea {
1955 pub name: Atom,
1957 pub rows: UnsignedRange,
1959 pub columns: UnsignedRange,
1961}
1962
1963struct TemplateAreasTokenizer<'a>(&'a str);
1966
1967impl<'a> Iterator for TemplateAreasTokenizer<'a> {
1968 type Item = Result<Option<&'a str>, ()>;
1969
1970 fn next(&mut self) -> Option<Self::Item> {
1971 let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
1972 if rest.is_empty() {
1973 return None;
1974 }
1975 if rest.starts_with('.') {
1976 self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
1977 return Some(Ok(None));
1978 }
1979 if !rest.starts_with(is_name_code_point) {
1980 return Some(Err(()));
1981 }
1982 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
1983 let token = &rest[..token_len];
1984 self.0 = &rest[token_len..];
1985 Some(Ok(Some(token)))
1986 }
1987}
1988
1989fn is_name_code_point(c: char) -> bool {
1990 c >= 'A' && c <= 'Z'
1991 || c >= 'a' && c <= 'z'
1992 || c >= '\u{80}'
1993 || c == '_'
1994 || c >= '0' && c <= '9'
1995 || c == '-'
1996}
1997
1998#[repr(C, u8)]
2004#[derive(
2005 Clone,
2006 Debug,
2007 MallocSizeOf,
2008 Parse,
2009 PartialEq,
2010 SpecifiedValueInfo,
2011 ToComputedValue,
2012 ToCss,
2013 ToResolvedValue,
2014 ToShmem,
2015 ToTyped,
2016)]
2017pub enum GridTemplateAreas {
2018 None,
2020 Areas(TemplateAreasArc),
2022}
2023
2024impl GridTemplateAreas {
2025 #[inline]
2026 pub fn none() -> GridTemplateAreas {
2028 GridTemplateAreas::None
2029 }
2030}
2031
2032pub type ZIndex = GenericZIndex<Integer>;
2034
2035pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
2037
2038impl Parse for AspectRatio {
2039 fn parse<'i, 't>(
2040 context: &ParserContext,
2041 input: &mut Parser<'i, 't>,
2042 ) -> Result<Self, ParseError<'i>> {
2043 use crate::values::generics::position::PreferredRatio;
2044 use crate::values::specified::Ratio;
2045
2046 let location = input.current_source_location();
2047 let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2048 let ratio = input.try_parse(|i| Ratio::parse(context, i));
2049 if auto.is_err() {
2050 auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2051 }
2052
2053 if auto.is_err() && ratio.is_err() {
2054 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
2055 }
2056
2057 Ok(AspectRatio {
2058 auto: auto.is_ok(),
2059 ratio: match ratio {
2060 Ok(ratio) => PreferredRatio::Ratio(ratio),
2061 Err(..) => PreferredRatio::None,
2062 },
2063 })
2064 }
2065}
2066
2067impl AspectRatio {
2068 pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
2070 use crate::values::generics::position::PreferredRatio;
2071 use crate::values::generics::ratio::Ratio;
2072 AspectRatio {
2073 auto: true,
2074 ratio: PreferredRatio::Ratio(Ratio(
2075 NonNegativeNumber::new(w),
2076 NonNegativeNumber::new(h),
2077 )),
2078 }
2079 }
2080}
2081
2082pub type Inset = GenericInset<specified::Percentage, LengthPercentage>;
2084
2085impl Inset {
2086 #[inline]
2089 pub fn parse_quirky<'i, 't>(
2090 context: &ParserContext,
2091 input: &mut Parser<'i, 't>,
2092 allow_quirks: AllowQuirks,
2093 ) -> Result<Self, ParseError<'i>> {
2094 if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
2095 {
2096 return Ok(Self::LengthPercentage(l));
2097 }
2098 match input.try_parse(|i| i.expect_ident_matching("auto")) {
2099 Ok(_) => return Ok(Self::Auto),
2100 Err(e) if !static_prefs::pref!("layout.css.anchor-positioning.enabled") => {
2101 return Err(e.into())
2102 },
2103 Err(_) => (),
2104 };
2105 Self::parse_anchor_functions_quirky(context, input, allow_quirks)
2106 }
2107
2108 fn parse_as_anchor_function_fallback<'i, 't>(
2109 context: &ParserContext,
2110 input: &mut Parser<'i, 't>,
2111 ) -> Result<Self, ParseError<'i>> {
2112 if let Ok(l) =
2113 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No))
2114 {
2115 return Ok(Self::LengthPercentage(l));
2116 }
2117 Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No)
2118 }
2119
2120 fn parse_anchor_functions_quirky<'i, 't>(
2121 context: &ParserContext,
2122 input: &mut Parser<'i, 't>,
2123 allow_quirks: AllowQuirks,
2124 ) -> Result<Self, ParseError<'i>> {
2125 debug_assert!(
2126 static_prefs::pref!("layout.css.anchor-positioning.enabled"),
2127 "How are we parsing with pref off?"
2128 );
2129 if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) {
2130 return Ok(Self::AnchorFunction(Box::new(inner)));
2131 }
2132 if let Ok(inner) =
2133 input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i))
2134 {
2135 return Ok(Self::AnchorSizeFunction(Box::new(inner)));
2136 }
2137 Ok(Self::AnchorContainingCalcFunction(input.try_parse(
2138 |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks),
2139 )?))
2140 }
2141}
2142
2143impl Parse for Inset {
2144 fn parse<'i, 't>(
2145 context: &ParserContext,
2146 input: &mut Parser<'i, 't>,
2147 ) -> Result<Self, ParseError<'i>> {
2148 Self::parse_quirky(context, input, AllowQuirks::No)
2149 }
2150}
2151
2152pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>;
2154
2155impl Parse for AnchorFunction {
2156 fn parse<'i, 't>(
2157 context: &ParserContext,
2158 input: &mut Parser<'i, 't>,
2159 ) -> Result<Self, ParseError<'i>> {
2160 if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
2161 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
2162 }
2163 input.expect_function_matching("anchor")?;
2164 input.parse_nested_block(|i| {
2165 let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
2166 let side = GenericAnchorSide::parse(context, i)?;
2167 let target_element = if target_element.is_none() {
2168 i.try_parse(|i| DashedIdent::parse(context, i)).ok()
2169 } else {
2170 target_element
2171 };
2172 let fallback = i
2173 .try_parse(|i| {
2174 i.expect_comma()?;
2175 Inset::parse_as_anchor_function_fallback(context, i)
2176 })
2177 .ok();
2178 Ok(Self {
2179 target_element: TreeScoped::with_default_level(
2180 target_element.unwrap_or_else(DashedIdent::empty),
2181 ),
2182 side,
2183 fallback: fallback.into(),
2184 })
2185 })
2186 }
2187}