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