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};
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#[repr(transparent)]
353#[derive(
354 Clone,
355 Debug,
356 MallocSizeOf,
357 PartialEq,
358 SpecifiedValueInfo,
359 ToComputedValue,
360 ToCss,
361 ToResolvedValue,
362 ToShmem,
363 ToTyped,
364)]
365#[css(comma)]
366pub struct AnchorName(
367 #[css(iterable, if_empty = "none")]
368 #[ignore_malloc_size_of = "Arc"]
369 pub crate::ArcSlice<DashedIdent>,
370);
371
372impl AnchorName {
373 pub fn none() -> Self {
375 Self(Default::default())
376 }
377
378 pub fn is_none(&self) -> bool {
380 self.0.is_empty()
381 }
382}
383
384impl Parse for AnchorName {
385 fn parse<'i, 't>(
386 context: &ParserContext,
387 input: &mut Parser<'i, 't>,
388 ) -> Result<Self, ParseError<'i>> {
389 let location = input.current_source_location();
390 let first = input.expect_ident()?;
391 if first.eq_ignore_ascii_case("none") {
392 return Ok(Self::none());
393 }
394 let mut idents: SmallVec<[DashedIdent; 4]> =
397 smallvec![DashedIdent::from_ident(location, first,)?];
398 while input.try_parse(|input| input.expect_comma()).is_ok() {
399 idents.push(DashedIdent::parse(context, input)?);
400 }
401 Ok(AnchorName(ArcSlice::from_iter(idents.drain(..))))
402 }
403}
404
405#[derive(
407 Clone,
408 Debug,
409 MallocSizeOf,
410 PartialEq,
411 SpecifiedValueInfo,
412 ToComputedValue,
413 ToCss,
414 ToResolvedValue,
415 ToShmem,
416 ToTyped,
417)]
418#[repr(u8)]
419pub enum AnchorScope {
420 None,
422 All,
424 #[css(comma)]
426 Idents(
427 #[css(iterable)]
428 #[ignore_malloc_size_of = "Arc"]
429 crate::ArcSlice<DashedIdent>,
430 ),
431}
432
433impl AnchorScope {
434 pub fn none() -> Self {
436 Self::None
437 }
438
439 pub fn is_none(&self) -> bool {
441 *self == Self::None
442 }
443}
444
445impl Parse for AnchorScope {
446 fn parse<'i, 't>(
447 context: &ParserContext,
448 input: &mut Parser<'i, 't>,
449 ) -> Result<Self, ParseError<'i>> {
450 let location = input.current_source_location();
451 let first = input.expect_ident()?;
452 if first.eq_ignore_ascii_case("none") {
453 return Ok(Self::None);
454 }
455 if first.eq_ignore_ascii_case("all") {
456 return Ok(Self::All);
457 }
458 let mut idents: SmallVec<[DashedIdent; 8]> =
461 smallvec![DashedIdent::from_ident(location, first,)?];
462 while input.try_parse(|input| input.expect_comma()).is_ok() {
463 idents.push(DashedIdent::parse(context, input)?);
464 }
465 Ok(AnchorScope::Idents(ArcSlice::from_iter(idents.drain(..))))
466 }
467}
468
469#[derive(
471 Clone,
472 Debug,
473 MallocSizeOf,
474 Parse,
475 PartialEq,
476 SpecifiedValueInfo,
477 ToComputedValue,
478 ToCss,
479 ToResolvedValue,
480 ToShmem,
481 ToTyped,
482)]
483#[repr(u8)]
484pub enum PositionAnchor {
485 None,
487 Auto,
489 Ident(DashedIdent),
491}
492
493#[derive(
494 Clone,
495 Copy,
496 Debug,
497 Eq,
498 MallocSizeOf,
499 Parse,
500 PartialEq,
501 Serialize,
502 SpecifiedValueInfo,
503 ToComputedValue,
504 ToCss,
505 ToResolvedValue,
506 ToShmem,
507)]
508#[repr(u8)]
509pub enum PositionTryFallbacksTryTacticKeyword {
511 FlipBlock,
513 FlipInline,
515 FlipStart,
517 FlipX,
519 FlipY,
521}
522
523#[derive(
524 Clone,
525 Debug,
526 Default,
527 Eq,
528 MallocSizeOf,
529 PartialEq,
530 SpecifiedValueInfo,
531 ToComputedValue,
532 ToCss,
533 ToResolvedValue,
534 ToShmem,
535)]
536#[repr(transparent)]
537pub struct PositionTryFallbacksTryTactic(
542 #[css(iterable)] pub ThinVec<PositionTryFallbacksTryTacticKeyword>,
543);
544
545impl Parse for PositionTryFallbacksTryTactic {
546 fn parse<'i, 't>(
547 _context: &ParserContext,
548 input: &mut Parser<'i, 't>,
549 ) -> Result<Self, ParseError<'i>> {
550 let mut result = ThinVec::with_capacity(5);
551 for _ in 0..5 {
553 if let Ok(kw) = input.try_parse(PositionTryFallbacksTryTacticKeyword::parse) {
554 if result.contains(&kw) {
555 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
556 }
557 result.push(kw);
558 } else {
559 break;
560 }
561 }
562 if result.is_empty() {
563 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
564 }
565 Ok(Self(result))
566 }
567}
568
569impl PositionTryFallbacksTryTactic {
570 #[inline]
572 pub fn is_empty(&self) -> bool {
573 self.0.is_empty()
574 }
575
576 #[inline]
578 pub fn iter(&self) -> impl Iterator<Item = &PositionTryFallbacksTryTacticKeyword> {
579 self.0.iter()
580 }
581}
582
583#[derive(
584 Clone,
585 Debug,
586 MallocSizeOf,
587 PartialEq,
588 SpecifiedValueInfo,
589 ToComputedValue,
590 ToCss,
591 ToResolvedValue,
592 ToShmem,
593)]
594#[repr(C)]
595pub struct DashedIdentAndOrTryTactic {
598 pub ident: DashedIdent,
600 pub try_tactic: PositionTryFallbacksTryTactic,
602}
603
604impl Parse for DashedIdentAndOrTryTactic {
605 fn parse<'i, 't>(
606 context: &ParserContext,
607 input: &mut Parser<'i, 't>,
608 ) -> Result<Self, ParseError<'i>> {
609 let mut result = Self {
610 ident: DashedIdent::empty(),
611 try_tactic: PositionTryFallbacksTryTactic::default(),
612 };
613
614 loop {
615 if result.ident.is_empty() {
616 if let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i)) {
617 result.ident = ident;
618 continue;
619 }
620 }
621 if result.try_tactic.is_empty() {
622 if let Ok(try_tactic) =
623 input.try_parse(|i| PositionTryFallbacksTryTactic::parse(context, i))
624 {
625 result.try_tactic = try_tactic;
626 continue;
627 }
628 }
629 break;
630 }
631
632 if result.ident.is_empty() && result.try_tactic.is_empty() {
633 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
634 }
635 return Ok(result);
636 }
637}
638
639#[derive(
640 Clone,
641 Debug,
642 MallocSizeOf,
643 Parse,
644 PartialEq,
645 SpecifiedValueInfo,
646 ToComputedValue,
647 ToCss,
648 ToResolvedValue,
649 ToShmem,
650)]
651#[repr(u8)]
652pub enum PositionTryFallbacksItem {
655 IdentAndOrTactic(DashedIdentAndOrTryTactic),
657 #[parse(parse_fn = "PositionArea::parse_except_none")]
658 PositionArea(PositionArea),
660}
661
662#[derive(
663 Clone,
664 Debug,
665 Default,
666 MallocSizeOf,
667 PartialEq,
668 SpecifiedValueInfo,
669 ToComputedValue,
670 ToCss,
671 ToResolvedValue,
672 ToShmem,
673 ToTyped,
674)]
675#[css(comma)]
676#[repr(C)]
677pub struct PositionTryFallbacks(
679 #[css(iterable, if_empty = "none")]
680 #[ignore_malloc_size_of = "Arc"]
681 pub crate::ArcSlice<PositionTryFallbacksItem>,
682);
683
684impl PositionTryFallbacks {
685 #[inline]
686 pub fn none() -> Self {
688 Self(Default::default())
689 }
690
691 pub fn is_none(&self) -> bool {
693 self.0.is_empty()
694 }
695}
696
697impl Parse for PositionTryFallbacks {
698 fn parse<'i, 't>(
699 context: &ParserContext,
700 input: &mut Parser<'i, 't>,
701 ) -> Result<Self, ParseError<'i>> {
702 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
703 return Ok(Self::none());
704 }
705 let mut items: SmallVec<[PositionTryFallbacksItem; 4]> =
708 smallvec![PositionTryFallbacksItem::parse(context, input)?];
709 while input.try_parse(|input| input.expect_comma()).is_ok() {
710 items.push(PositionTryFallbacksItem::parse(context, input)?);
711 }
712 Ok(Self(ArcSlice::from_iter(items.drain(..))))
713 }
714}
715
716#[derive(
718 Clone,
719 Copy,
720 Debug,
721 Default,
722 Eq,
723 MallocSizeOf,
724 Parse,
725 PartialEq,
726 SpecifiedValueInfo,
727 ToComputedValue,
728 ToCss,
729 ToResolvedValue,
730 ToShmem,
731 ToTyped,
732)]
733#[repr(u8)]
734pub enum PositionTryOrder {
735 #[default]
736 Normal,
738 MostWidth,
740 MostHeight,
742 MostBlockSize,
744 MostInlineSize,
746}
747
748impl PositionTryOrder {
749 #[inline]
750 pub fn normal() -> Self {
752 Self::Normal
753 }
754
755 pub fn is_normal(&self) -> bool {
757 *self == Self::Normal
758 }
759}
760
761#[derive(
762 Clone,
763 Copy,
764 Debug,
765 Eq,
766 MallocSizeOf,
767 Parse,
768 PartialEq,
769 Serialize,
770 SpecifiedValueInfo,
771 ToComputedValue,
772 ToCss,
773 ToResolvedValue,
774 ToShmem,
775 ToTyped,
776)]
777#[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))]
778#[repr(C)]
779pub struct PositionVisibility(u8);
781bitflags! {
782 impl PositionVisibility: u8 {
783 const ALWAYS = 0;
785 const ANCHORS_VALID = 1 << 0;
787 const ANCHORS_VISIBLE = 1 << 1;
789 const NO_OVERFLOW = 1 << 2;
791 }
792}
793
794impl Default for PositionVisibility {
795 fn default() -> Self {
796 Self::ALWAYS
797 }
798}
799
800impl PositionVisibility {
801 #[inline]
802 pub fn always() -> Self {
804 Self::ALWAYS
805 }
806}
807
808#[repr(u8)]
811#[derive(Clone, Copy, Debug, Eq, PartialEq)]
812pub enum PositionAreaType {
813 Physical,
815 Logical,
817 SelfLogical,
819 Inferred,
821 SelfInferred,
823 Common,
825 None,
827}
828
829#[repr(u8)]
836#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
837#[allow(missing_docs)]
838pub enum PositionAreaAxis {
839 Horizontal = 0b000,
840 Vertical = 0b001,
841
842 X = 0b010,
843 Y = 0b011,
844
845 Block = 0b110,
846 Inline = 0b111,
847
848 Inferred = 0b100,
849 None = 0b101,
850}
851
852impl PositionAreaAxis {
853 pub fn is_physical(self) -> bool {
855 (self as u8 & 0b100) == 0
856 }
857
858 fn is_flow_relative_direction(self) -> bool {
860 self == Self::Inferred || (self as u8 & 0b10) != 0
861 }
862
863 fn is_canonically_first(self) -> bool {
865 self != Self::Inferred && (self as u8) & 1 == 0
866 }
867
868 #[allow(unused)]
869 fn flip(self) -> Self {
870 if matches!(self, Self::Inferred | Self::None) {
871 return self;
872 }
873 Self::from_u8(self as u8 ^ 1u8).unwrap()
874 }
875
876 fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> {
877 Some(match self {
878 PositionAreaAxis::Horizontal | PositionAreaAxis::X => {
879 if wm.is_vertical() {
880 LogicalAxis::Block
881 } else {
882 LogicalAxis::Inline
883 }
884 },
885 PositionAreaAxis::Vertical | PositionAreaAxis::Y => {
886 if wm.is_vertical() {
887 LogicalAxis::Inline
888 } else {
889 LogicalAxis::Block
890 }
891 },
892 PositionAreaAxis::Block => LogicalAxis::Block,
893 PositionAreaAxis::Inline => LogicalAxis::Inline,
894 PositionAreaAxis::Inferred => inferred,
895 PositionAreaAxis::None => return None,
896 })
897 }
898}
899
900#[repr(u8)]
903#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
904pub enum PositionAreaTrack {
905 Start = 0b001,
907 SpanStart = 0b011,
909 End = 0b100,
911 SpanEnd = 0b110,
913 Center = 0b010,
915 SpanAll = 0b111,
917}
918
919impl PositionAreaTrack {
920 fn flip(self) -> Self {
921 match self {
922 Self::Start => Self::End,
923 Self::SpanStart => Self::SpanEnd,
924 Self::End => Self::Start,
925 Self::SpanEnd => Self::SpanStart,
926 Self::Center | Self::SpanAll => self,
927 }
928 }
929
930 fn start(self) -> bool {
931 self as u8 & 1 != 0
932 }
933}
934
935pub const AXIS_SHIFT: usize = 3;
937pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT;
939pub const TRACK_MASK: u8 = 0b111u8;
941pub const SELF_WM: u8 = 1u8 << 6;
943
944#[derive(
945 Clone,
946 Copy,
947 Debug,
948 Default,
949 Eq,
950 MallocSizeOf,
951 Parse,
952 PartialEq,
953 SpecifiedValueInfo,
954 ToComputedValue,
955 ToCss,
956 ToResolvedValue,
957 ToShmem,
958 FromPrimitive,
959)]
960#[allow(missing_docs)]
961#[repr(u8)]
962pub enum PositionAreaKeyword {
967 #[default]
968 None = (PositionAreaAxis::None as u8) << AXIS_SHIFT,
969
970 Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8,
972 SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8,
973
974 Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
976 End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
977 SpanStart =
978 ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
979 SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
980
981 Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
983 Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
984 Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
985 Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
986
987 XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
989 XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
990 YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
991 YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
992
993 BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
995 BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
996 InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
997 InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
998
999 SpanLeft =
1001 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1002 SpanRight =
1003 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1004 SpanTop =
1005 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1006 SpanBottom =
1007 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1008
1009 SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1011 SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1012 SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1013 SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1014
1015 SpanBlockStart =
1017 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1018 SpanBlockEnd =
1019 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1020 SpanInlineStart =
1021 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1022 SpanInlineEnd =
1023 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1024
1025 SelfStart = SELF_WM | (Self::Start as u8),
1027 SelfEnd = SELF_WM | (Self::End as u8),
1028 SpanSelfStart = SELF_WM | (Self::SpanStart as u8),
1029 SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8),
1030
1031 SelfXStart = SELF_WM | (Self::XStart as u8),
1032 SelfXEnd = SELF_WM | (Self::XEnd as u8),
1033 SelfYStart = SELF_WM | (Self::YStart as u8),
1034 SelfYEnd = SELF_WM | (Self::YEnd as u8),
1035 SelfBlockStart = SELF_WM | (Self::BlockStart as u8),
1036 SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8),
1037 SelfInlineStart = SELF_WM | (Self::InlineStart as u8),
1038 SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8),
1039
1040 SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8),
1041 SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8),
1042 SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8),
1043 SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8),
1044 SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8),
1045 SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8),
1046 SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8),
1047 SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8),
1048}
1049
1050impl PositionAreaKeyword {
1051 #[inline]
1053 pub fn none() -> Self {
1054 Self::None
1055 }
1056
1057 pub fn is_none(&self) -> bool {
1059 *self == Self::None
1060 }
1061
1062 pub fn self_wm(self) -> bool {
1064 (self as u8 & SELF_WM) != 0
1065 }
1066
1067 pub fn axis(self) -> PositionAreaAxis {
1069 PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap()
1070 }
1071
1072 pub fn with_axis(self, axis: PositionAreaAxis) -> Self {
1074 Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap()
1075 }
1076
1077 pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self {
1079 if self.axis() == PositionAreaAxis::Inferred {
1080 self.with_axis(axis)
1081 } else {
1082 self
1083 }
1084 }
1085
1086 pub fn track(self) -> Option<PositionAreaTrack> {
1088 let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK);
1089 debug_assert_eq!(
1090 result.is_none(),
1091 self.is_none(),
1092 "Only the none keyword has no track"
1093 );
1094 result
1095 }
1096
1097 fn group_type(self) -> PositionAreaType {
1098 let axis = self.axis();
1099 if axis == PositionAreaAxis::None {
1100 if self.is_none() {
1101 return PositionAreaType::None;
1102 }
1103 return PositionAreaType::Common;
1104 }
1105 if axis == PositionAreaAxis::Inferred {
1106 return if self.self_wm() {
1107 PositionAreaType::SelfInferred
1108 } else {
1109 PositionAreaType::Inferred
1110 };
1111 }
1112 if axis.is_physical() {
1113 return PositionAreaType::Physical;
1114 }
1115 if self.self_wm() {
1116 PositionAreaType::SelfLogical
1117 } else {
1118 PositionAreaType::Logical
1119 }
1120 }
1121
1122 fn to_physical(
1123 self,
1124 cb_wm: WritingMode,
1125 self_wm: WritingMode,
1126 inferred_axis: LogicalAxis,
1127 ) -> Self {
1128 let wm = if self.self_wm() { self_wm } else { cb_wm };
1129 let axis = self.axis();
1130 if !axis.is_flow_relative_direction() {
1131 return self;
1132 }
1133 let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else {
1134 return self;
1135 };
1136 let Some(track) = self.track() else {
1137 debug_assert!(false, "How did we end up with no track here? {self:?}");
1138 return self;
1139 };
1140 let start = track.start();
1141 let logical_side = match logical_axis {
1142 LogicalAxis::Block => {
1143 if start {
1144 LogicalSide::BlockStart
1145 } else {
1146 LogicalSide::BlockEnd
1147 }
1148 },
1149 LogicalAxis::Inline => {
1150 if start {
1151 LogicalSide::InlineStart
1152 } else {
1153 LogicalSide::InlineEnd
1154 }
1155 },
1156 };
1157 let physical_side = logical_side.to_physical(wm);
1158 let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left);
1159 let new_track = if physical_start != start {
1160 track.flip()
1161 } else {
1162 track
1163 };
1164 let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) {
1165 PositionAreaAxis::Vertical
1166 } else {
1167 PositionAreaAxis::Horizontal
1168 };
1169 Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap()
1170 }
1171
1172 fn flip_track(self) -> Self {
1173 let Some(old_track) = self.track() else {
1174 return self;
1175 };
1176 let new_track = old_track.flip();
1177 Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap()
1178 }
1179
1180 pub fn to_self_alignment(self, axis: LogicalAxis, cb_wm: &WritingMode) -> Option<AlignFlags> {
1188 let track = self.track()?;
1189 Some(match track {
1190 PositionAreaTrack::Center => AlignFlags::CENTER,
1192 PositionAreaTrack::SpanAll => AlignFlags::ANCHOR_CENTER,
1194 _ => {
1197 debug_assert_eq!(self.group_type(), PositionAreaType::Physical);
1198 if axis == LogicalAxis::Inline {
1199 if track.start() == cb_wm.intersects(WritingMode::INLINE_REVERSED) {
1203 AlignFlags::START
1204 } else {
1205 AlignFlags::END
1206 }
1207 } else {
1208 if track.start() == cb_wm.is_vertical_rl() {
1211 AlignFlags::START
1212 } else {
1213 AlignFlags::END
1214 }
1215 }
1216 },
1217 })
1218 }
1219}
1220
1221#[derive(
1222 Clone,
1223 Copy,
1224 Debug,
1225 Eq,
1226 MallocSizeOf,
1227 PartialEq,
1228 SpecifiedValueInfo,
1229 ToCss,
1230 ToResolvedValue,
1231 ToShmem,
1232 ToTyped,
1233)]
1234#[repr(C)]
1235pub struct PositionArea {
1237 pub first: PositionAreaKeyword,
1239 #[css(skip_if = "PositionAreaKeyword::is_none")]
1241 pub second: PositionAreaKeyword,
1242}
1243
1244impl PositionArea {
1245 #[inline]
1247 pub fn none() -> Self {
1248 Self {
1249 first: PositionAreaKeyword::None,
1250 second: PositionAreaKeyword::None,
1251 }
1252 }
1253
1254 #[inline]
1256 pub fn is_none(&self) -> bool {
1257 self.first.is_none()
1258 }
1259
1260 pub fn parse_except_none<'i, 't>(
1262 context: &ParserContext,
1263 input: &mut Parser<'i, 't>,
1264 ) -> Result<Self, ParseError<'i>> {
1265 Self::parse_internal(context, input, false)
1266 }
1267
1268 pub fn get_type(&self) -> PositionAreaType {
1270 let first = self.first.group_type();
1271 let second = self.second.group_type();
1272 if matches!(second, PositionAreaType::None | PositionAreaType::Common) {
1273 return first;
1274 }
1275 if first == PositionAreaType::Common {
1276 return second;
1277 }
1278 if first != second {
1279 return PositionAreaType::None;
1280 }
1281 let first_axis = self.first.axis();
1282 if first_axis != PositionAreaAxis::Inferred
1283 && first_axis.is_canonically_first() == self.second.axis().is_canonically_first()
1284 {
1285 return PositionAreaType::None;
1286 }
1287 first
1288 }
1289
1290 fn parse_internal<'i, 't>(
1291 _: &ParserContext,
1292 input: &mut Parser<'i, 't>,
1293 allow_none: bool,
1294 ) -> Result<Self, ParseError<'i>> {
1295 let mut location = input.current_source_location();
1296 let mut first = PositionAreaKeyword::parse(input)?;
1297 if first.is_none() {
1298 if allow_none {
1299 return Ok(Self::none());
1300 }
1301 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1302 }
1303
1304 location = input.current_source_location();
1305 let second = input.try_parse(PositionAreaKeyword::parse);
1306 if let Ok(PositionAreaKeyword::None) = second {
1307 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1309 }
1310 let mut second = second.unwrap_or(PositionAreaKeyword::None);
1311 if second.is_none() {
1312 return Ok(Self { first, second });
1318 }
1319
1320 let pair_type = Self { first, second }.get_type();
1321 if pair_type == PositionAreaType::None {
1322 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1324 }
1325 if matches!(
1328 pair_type,
1329 PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical
1330 ) {
1331 if second == PositionAreaKeyword::SpanAll {
1332 second = PositionAreaKeyword::None;
1335 } else if first == PositionAreaKeyword::SpanAll {
1336 first = second;
1337 second = PositionAreaKeyword::None;
1338 }
1339 }
1340 if first == second {
1341 second = PositionAreaKeyword::None;
1342 }
1343 let mut result = Self { first, second };
1344 result.canonicalize_order();
1345 Ok(result)
1346 }
1347
1348 fn canonicalize_order(&mut self) {
1349 let first_axis = self.first.axis();
1350 if first_axis.is_canonically_first() || self.second.is_none() {
1351 return;
1352 }
1353 let second_axis = self.second.axis();
1354 if first_axis == second_axis {
1355 return;
1357 }
1358 if second_axis.is_canonically_first()
1359 || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred)
1360 {
1361 std::mem::swap(&mut self.first, &mut self.second);
1362 }
1363 }
1364
1365 fn make_missing_second_explicit(&mut self) {
1366 if !self.second.is_none() {
1367 return;
1368 }
1369 let axis = self.first.axis();
1370 if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) {
1371 self.second = self.first;
1372 return;
1373 }
1374 self.second = PositionAreaKeyword::SpanAll;
1375 if !axis.is_canonically_first() {
1376 std::mem::swap(&mut self.first, &mut self.second);
1377 }
1378 }
1379
1380 pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self {
1382 self.make_missing_second_explicit();
1383 self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block);
1384 self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline);
1385 self.canonicalize_order();
1386 self
1387 }
1388
1389 fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) {
1390 if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) {
1391 self.first = self.first.flip_track();
1392 } else {
1393 self.second = self.second.flip_track();
1394 }
1395 }
1396
1397 fn flip_start(&mut self) {
1398 self.first = self.first.with_axis(self.first.axis().flip());
1399 self.second = self.second.with_axis(self.second.axis().flip());
1400 }
1401
1402 pub fn with_tactic(
1404 mut self,
1405 wm: WritingMode,
1406 tactic: PositionTryFallbacksTryTacticKeyword,
1407 ) -> Self {
1408 self.make_missing_second_explicit();
1409 let axis_to_flip = match tactic {
1410 PositionTryFallbacksTryTacticKeyword::FlipStart => {
1411 self.flip_start();
1412 return self;
1413 },
1414 PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block,
1415 PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline,
1416 PositionTryFallbacksTryTacticKeyword::FlipX => {
1417 if wm.is_horizontal() {
1418 LogicalAxis::Inline
1419 } else {
1420 LogicalAxis::Block
1421 }
1422 },
1423 PositionTryFallbacksTryTacticKeyword::FlipY => {
1424 if wm.is_vertical() {
1425 LogicalAxis::Inline
1426 } else {
1427 LogicalAxis::Block
1428 }
1429 },
1430 };
1431 self.flip_logical_axis(wm, axis_to_flip);
1432 self
1433 }
1434}
1435
1436impl Parse for PositionArea {
1437 fn parse<'i, 't>(
1438 context: &ParserContext,
1439 input: &mut Parser<'i, 't>,
1440 ) -> Result<Self, ParseError<'i>> {
1441 Self::parse_internal(context, input, true)
1442 }
1443}
1444
1445pub trait Side {
1447 fn start() -> Self;
1449
1450 fn is_start(&self) -> bool;
1452}
1453
1454impl Side for HorizontalPositionKeyword {
1455 #[inline]
1456 fn start() -> Self {
1457 HorizontalPositionKeyword::Left
1458 }
1459
1460 #[inline]
1461 fn is_start(&self) -> bool {
1462 *self == Self::start()
1463 }
1464}
1465
1466impl Side for VerticalPositionKeyword {
1467 #[inline]
1468 fn start() -> Self {
1469 VerticalPositionKeyword::Top
1470 }
1471
1472 #[inline]
1473 fn is_start(&self) -> bool {
1474 *self == Self::start()
1475 }
1476}
1477
1478#[derive(
1482 Clone,
1483 Copy,
1484 Debug,
1485 Eq,
1486 MallocSizeOf,
1487 Parse,
1488 PartialEq,
1489 SpecifiedValueInfo,
1490 ToComputedValue,
1491 ToResolvedValue,
1492 ToShmem,
1493 ToTyped,
1494)]
1495#[css(bitflags(
1496 mixed = "row,column,dense",
1497 validate_mixed = "Self::validate_and_simplify"
1498))]
1499#[repr(C)]
1500pub struct GridAutoFlow(u8);
1501bitflags! {
1502 impl GridAutoFlow: u8 {
1503 const ROW = 1 << 0;
1505 const COLUMN = 1 << 1;
1507 const DENSE = 1 << 2;
1509 }
1510}
1511
1512impl GridAutoFlow {
1513 fn validate_and_simplify(&mut self) -> bool {
1515 if self.contains(Self::ROW | Self::COLUMN) {
1516 return false;
1518 }
1519 if *self == Self::DENSE {
1520 self.insert(Self::ROW);
1522 }
1523 true
1524 }
1525}
1526
1527impl ToCss for GridAutoFlow {
1528 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1529 where
1530 W: Write,
1531 {
1532 let dense = self.intersects(Self::DENSE);
1533 if self.intersects(Self::ROW) {
1534 return if dense {
1535 dest.write_str("dense")
1536 } else {
1537 dest.write_str("row")
1538 };
1539 }
1540 debug_assert!(self.intersects(Self::COLUMN));
1541 if dense {
1542 dest.write_str("column dense")
1543 } else {
1544 dest.write_str("column")
1545 }
1546 }
1547}
1548
1549#[repr(u8)]
1550#[derive(
1551 Clone,
1552 Copy,
1553 Debug,
1554 Eq,
1555 MallocSizeOf,
1556 PartialEq,
1557 SpecifiedValueInfo,
1558 ToComputedValue,
1559 ToCss,
1560 ToResolvedValue,
1561 ToShmem,
1562)]
1563pub enum MasonryPlacement {
1565 Pack,
1567 Next,
1569}
1570
1571#[repr(u8)]
1572#[derive(
1573 Clone,
1574 Copy,
1575 Debug,
1576 Eq,
1577 MallocSizeOf,
1578 PartialEq,
1579 SpecifiedValueInfo,
1580 ToComputedValue,
1581 ToCss,
1582 ToResolvedValue,
1583 ToShmem,
1584)]
1585pub enum MasonryItemOrder {
1587 DefiniteFirst,
1589 Ordered,
1591}
1592
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 ToTyped,
1606)]
1607#[repr(C)]
1608pub struct MasonryAutoFlow {
1611 #[css(contextual_skip_if = "is_pack_with_non_default_order")]
1613 pub placement: MasonryPlacement,
1614 #[css(skip_if = "is_item_order_definite_first")]
1616 pub order: MasonryItemOrder,
1617}
1618
1619#[inline]
1620fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
1621 *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
1622}
1623
1624#[inline]
1625fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
1626 *order == MasonryItemOrder::DefiniteFirst
1627}
1628
1629impl MasonryAutoFlow {
1630 #[inline]
1631 pub fn initial() -> MasonryAutoFlow {
1633 MasonryAutoFlow {
1634 placement: MasonryPlacement::Pack,
1635 order: MasonryItemOrder::DefiniteFirst,
1636 }
1637 }
1638}
1639
1640impl Parse for MasonryAutoFlow {
1641 fn parse<'i, 't>(
1643 _context: &ParserContext,
1644 input: &mut Parser<'i, 't>,
1645 ) -> Result<MasonryAutoFlow, ParseError<'i>> {
1646 let mut value = MasonryAutoFlow::initial();
1647 let mut got_placement = false;
1648 let mut got_order = false;
1649 while !input.is_exhausted() {
1650 let location = input.current_source_location();
1651 let ident = input.expect_ident()?;
1652 let success = match_ignore_ascii_case! { &ident,
1653 "pack" if !got_placement => {
1654 got_placement = true;
1655 true
1656 },
1657 "next" if !got_placement => {
1658 value.placement = MasonryPlacement::Next;
1659 got_placement = true;
1660 true
1661 },
1662 "definite-first" if !got_order => {
1663 got_order = true;
1664 true
1665 },
1666 "ordered" if !got_order => {
1667 value.order = MasonryItemOrder::Ordered;
1668 got_order = true;
1669 true
1670 },
1671 _ => false
1672 };
1673 if !success {
1674 return Err(location
1675 .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
1676 }
1677 }
1678
1679 if got_placement || got_order {
1680 Ok(value)
1681 } else {
1682 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1683 }
1684 }
1685}
1686
1687#[derive(
1688 Clone,
1689 Debug,
1690 MallocSizeOf,
1691 PartialEq,
1692 SpecifiedValueInfo,
1693 ToComputedValue,
1694 ToCss,
1695 ToResolvedValue,
1696 ToShmem,
1697)]
1698#[repr(C)]
1699pub struct TemplateAreas {
1701 #[css(skip)]
1703 pub areas: crate::OwnedSlice<NamedArea>,
1704 #[css(iterable)]
1709 pub strings: crate::OwnedSlice<crate::OwnedStr>,
1710 #[css(skip)]
1712 pub width: u32,
1713}
1714
1715#[derive(Default)]
1717pub struct TemplateAreasParser {
1718 areas: Vec<NamedArea>,
1719 area_indices: PrecomputedHashMap<Atom, usize>,
1720 strings: Vec<crate::OwnedStr>,
1721 width: u32,
1722 row: u32,
1723}
1724
1725impl TemplateAreasParser {
1726 pub fn try_parse_string<'i>(
1728 &mut self,
1729 input: &mut Parser<'i, '_>,
1730 ) -> Result<(), ParseError<'i>> {
1731 input.try_parse(|input| {
1732 self.parse_string(input.expect_string()?)
1733 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1734 })
1735 }
1736
1737 fn parse_string(&mut self, string: &str) -> Result<(), ()> {
1739 self.row += 1;
1740 let mut simplified_string = String::new();
1741 let mut current_area_index: Option<usize> = None;
1742 let mut column = 0u32;
1743 for token in TemplateAreasTokenizer(string) {
1744 column += 1;
1745 if column > 1 {
1746 simplified_string.push(' ');
1747 }
1748 let name = if let Some(token) = token? {
1749 simplified_string.push_str(token);
1750 Atom::from(token)
1751 } else {
1752 if let Some(index) = current_area_index.take() {
1753 if self.areas[index].columns.end != column {
1754 return Err(());
1755 }
1756 }
1757 simplified_string.push('.');
1758 continue;
1759 };
1760 if let Some(index) = current_area_index {
1761 if self.areas[index].name == name {
1762 if self.areas[index].rows.start == self.row {
1763 self.areas[index].columns.end += 1;
1764 }
1765 continue;
1766 }
1767 if self.areas[index].columns.end != column {
1768 return Err(());
1769 }
1770 }
1771 match self.area_indices.entry(name) {
1772 Entry::Occupied(ref e) => {
1773 let index = *e.get();
1774 if self.areas[index].columns.start != column
1775 || self.areas[index].rows.end != self.row
1776 {
1777 return Err(());
1778 }
1779 self.areas[index].rows.end += 1;
1780 current_area_index = Some(index);
1781 },
1782 Entry::Vacant(v) => {
1783 let index = self.areas.len();
1784 let name = v.key().clone();
1785 v.insert(index);
1786 self.areas.push(NamedArea {
1787 name,
1788 columns: UnsignedRange {
1789 start: column,
1790 end: column + 1,
1791 },
1792 rows: UnsignedRange {
1793 start: self.row,
1794 end: self.row + 1,
1795 },
1796 });
1797 current_area_index = Some(index);
1798 },
1799 }
1800 }
1801 if column == 0 {
1802 return Err(());
1805 }
1806 if let Some(index) = current_area_index {
1807 if self.areas[index].columns.end != column + 1 {
1808 debug_assert_ne!(self.areas[index].rows.start, self.row);
1809 return Err(());
1810 }
1811 }
1812 if self.row == 1 {
1813 self.width = column;
1814 } else if self.width != column {
1815 return Err(());
1816 }
1817
1818 self.strings.push(simplified_string.into());
1819 Ok(())
1820 }
1821
1822 pub fn finish(self) -> Result<TemplateAreas, ()> {
1824 if self.strings.is_empty() {
1825 return Err(());
1826 }
1827 Ok(TemplateAreas {
1828 areas: self.areas.into(),
1829 strings: self.strings.into(),
1830 width: self.width,
1831 })
1832 }
1833}
1834
1835impl TemplateAreas {
1836 fn parse_internal(input: &mut Parser) -> Result<Self, ()> {
1837 let mut parser = TemplateAreasParser::default();
1838 while parser.try_parse_string(input).is_ok() {}
1839 parser.finish()
1840 }
1841}
1842
1843impl Parse for TemplateAreas {
1844 fn parse<'i, 't>(
1845 _: &ParserContext,
1846 input: &mut Parser<'i, 't>,
1847 ) -> Result<Self, ParseError<'i>> {
1848 Self::parse_internal(input)
1849 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1850 }
1851}
1852
1853#[derive(
1855 Clone,
1856 Debug,
1857 MallocSizeOf,
1858 PartialEq,
1859 SpecifiedValueInfo,
1860 ToComputedValue,
1861 ToCss,
1862 ToResolvedValue,
1863 ToShmem,
1864)]
1865#[repr(transparent)]
1866pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
1867
1868impl Parse for TemplateAreasArc {
1869 fn parse<'i, 't>(
1870 context: &ParserContext,
1871 input: &mut Parser<'i, 't>,
1872 ) -> Result<Self, ParseError<'i>> {
1873 let parsed = TemplateAreas::parse(context, input)?;
1874 Ok(TemplateAreasArc(Arc::new(parsed)))
1875 }
1876}
1877
1878#[repr(C)]
1881#[derive(
1882 Clone,
1883 Debug,
1884 MallocSizeOf,
1885 PartialEq,
1886 SpecifiedValueInfo,
1887 ToComputedValue,
1888 ToResolvedValue,
1889 ToShmem,
1890)]
1891pub struct UnsignedRange {
1892 pub start: u32,
1894 pub end: u32,
1896}
1897
1898#[derive(
1899 Clone,
1900 Debug,
1901 MallocSizeOf,
1902 PartialEq,
1903 SpecifiedValueInfo,
1904 ToComputedValue,
1905 ToResolvedValue,
1906 ToShmem,
1907)]
1908#[repr(C)]
1909pub struct NamedArea {
1912 pub name: Atom,
1914 pub rows: UnsignedRange,
1916 pub columns: UnsignedRange,
1918}
1919
1920struct TemplateAreasTokenizer<'a>(&'a str);
1923
1924impl<'a> Iterator for TemplateAreasTokenizer<'a> {
1925 type Item = Result<Option<&'a str>, ()>;
1926
1927 fn next(&mut self) -> Option<Self::Item> {
1928 let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
1929 if rest.is_empty() {
1930 return None;
1931 }
1932 if rest.starts_with('.') {
1933 self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
1934 return Some(Ok(None));
1935 }
1936 if !rest.starts_with(is_name_code_point) {
1937 return Some(Err(()));
1938 }
1939 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
1940 let token = &rest[..token_len];
1941 self.0 = &rest[token_len..];
1942 Some(Ok(Some(token)))
1943 }
1944}
1945
1946fn is_name_code_point(c: char) -> bool {
1947 c >= 'A' && c <= 'Z'
1948 || c >= 'a' && c <= 'z'
1949 || c >= '\u{80}'
1950 || c == '_'
1951 || c >= '0' && c <= '9'
1952 || c == '-'
1953}
1954
1955#[repr(C, u8)]
1961#[derive(
1962 Clone,
1963 Debug,
1964 MallocSizeOf,
1965 Parse,
1966 PartialEq,
1967 SpecifiedValueInfo,
1968 ToComputedValue,
1969 ToCss,
1970 ToResolvedValue,
1971 ToShmem,
1972 ToTyped,
1973)]
1974pub enum GridTemplateAreas {
1975 None,
1977 Areas(TemplateAreasArc),
1979}
1980
1981impl GridTemplateAreas {
1982 #[inline]
1983 pub fn none() -> GridTemplateAreas {
1985 GridTemplateAreas::None
1986 }
1987}
1988
1989pub type ZIndex = GenericZIndex<Integer>;
1991
1992pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
1994
1995impl Parse for AspectRatio {
1996 fn parse<'i, 't>(
1997 context: &ParserContext,
1998 input: &mut Parser<'i, 't>,
1999 ) -> Result<Self, ParseError<'i>> {
2000 use crate::values::generics::position::PreferredRatio;
2001 use crate::values::specified::Ratio;
2002
2003 let location = input.current_source_location();
2004 let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2005 let ratio = input.try_parse(|i| Ratio::parse(context, i));
2006 if auto.is_err() {
2007 auto = input.try_parse(|i| i.expect_ident_matching("auto"));
2008 }
2009
2010 if auto.is_err() && ratio.is_err() {
2011 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
2012 }
2013
2014 Ok(AspectRatio {
2015 auto: auto.is_ok(),
2016 ratio: match ratio {
2017 Ok(ratio) => PreferredRatio::Ratio(ratio),
2018 Err(..) => PreferredRatio::None,
2019 },
2020 })
2021 }
2022}
2023
2024impl AspectRatio {
2025 pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
2027 use crate::values::generics::position::PreferredRatio;
2028 use crate::values::generics::ratio::Ratio;
2029 AspectRatio {
2030 auto: true,
2031 ratio: PreferredRatio::Ratio(Ratio(
2032 NonNegativeNumber::new(w),
2033 NonNegativeNumber::new(h),
2034 )),
2035 }
2036 }
2037}
2038
2039pub type Inset = GenericInset<specified::Percentage, LengthPercentage>;
2041
2042impl Inset {
2043 #[inline]
2046 pub fn parse_quirky<'i, 't>(
2047 context: &ParserContext,
2048 input: &mut Parser<'i, 't>,
2049 allow_quirks: AllowQuirks,
2050 ) -> Result<Self, ParseError<'i>> {
2051 if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
2052 {
2053 return Ok(Self::LengthPercentage(l));
2054 }
2055 match input.try_parse(|i| i.expect_ident_matching("auto")) {
2056 Ok(_) => return Ok(Self::Auto),
2057 Err(e) if !static_prefs::pref!("layout.css.anchor-positioning.enabled") => {
2058 return Err(e.into())
2059 },
2060 Err(_) => (),
2061 };
2062 Self::parse_anchor_functions_quirky(context, input, allow_quirks)
2063 }
2064
2065 fn parse_as_anchor_function_fallback<'i, 't>(
2066 context: &ParserContext,
2067 input: &mut Parser<'i, 't>,
2068 ) -> Result<Self, ParseError<'i>> {
2069 if let Ok(l) =
2070 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No))
2071 {
2072 return Ok(Self::LengthPercentage(l));
2073 }
2074 Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No)
2075 }
2076
2077 fn parse_anchor_functions_quirky<'i, 't>(
2078 context: &ParserContext,
2079 input: &mut Parser<'i, 't>,
2080 allow_quirks: AllowQuirks,
2081 ) -> Result<Self, ParseError<'i>> {
2082 debug_assert!(
2083 static_prefs::pref!("layout.css.anchor-positioning.enabled"),
2084 "How are we parsing with pref off?"
2085 );
2086 if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) {
2087 return Ok(Self::AnchorFunction(Box::new(inner)));
2088 }
2089 if let Ok(inner) =
2090 input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i))
2091 {
2092 return Ok(Self::AnchorSizeFunction(Box::new(inner)));
2093 }
2094 Ok(Self::AnchorContainingCalcFunction(input.try_parse(
2095 |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks),
2096 )?))
2097 }
2098}
2099
2100impl Parse for Inset {
2101 fn parse<'i, 't>(
2102 context: &ParserContext,
2103 input: &mut Parser<'i, 't>,
2104 ) -> Result<Self, ParseError<'i>> {
2105 Self::parse_quirky(context, input, AllowQuirks::No)
2106 }
2107}
2108
2109pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>;
2111
2112impl Parse for AnchorFunction {
2113 fn parse<'i, 't>(
2114 context: &ParserContext,
2115 input: &mut Parser<'i, 't>,
2116 ) -> Result<Self, ParseError<'i>> {
2117 if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
2118 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
2119 }
2120 input.expect_function_matching("anchor")?;
2121 input.parse_nested_block(|i| {
2122 let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
2123 let side = GenericAnchorSide::parse(context, i)?;
2124 let target_element = if target_element.is_none() {
2125 i.try_parse(|i| DashedIdent::parse(context, i)).ok()
2126 } else {
2127 target_element
2128 };
2129 let fallback = i
2130 .try_parse(|i| {
2131 i.expect_comma()?;
2132 Inset::parse_as_anchor_function_fallback(context, i)
2133 })
2134 .ok();
2135 Ok(Self {
2136 target_element: target_element.unwrap_or_else(DashedIdent::empty),
2137 side,
2138 fallback: fallback.into(),
2139 })
2140 })
2141 }
2142}