1use crate::parser::{Parse, ParserContext};
9use crate::values::specified;
10use crate::values::{CSSFloat, CustomIdent};
11use crate::{One, Zero};
12use cssparser::Parser;
13use std::fmt::{self, Write};
14use std::{cmp, usize};
15use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
16
17pub const MIN_GRID_LINE: i32 = -10000;
21pub const MAX_GRID_LINE: i32 = 10000;
23
24#[derive(
28 Clone,
29 Debug,
30 Default,
31 MallocSizeOf,
32 PartialEq,
33 SpecifiedValueInfo,
34 ToComputedValue,
35 ToResolvedValue,
36 ToShmem,
37 ToTyped,
38)]
39#[repr(C)]
40pub struct GenericGridLine<Integer> {
41 pub ident: CustomIdent,
45 pub line_num: Integer,
53 pub is_span: bool,
55}
56
57pub use self::GenericGridLine as GridLine;
58
59impl<Integer> GridLine<Integer>
60where
61 Integer: PartialEq + Zero,
62{
63 pub fn auto() -> Self {
65 Self {
66 is_span: false,
67 line_num: Zero::zero(),
68 ident: CustomIdent(atom!("")),
69 }
70 }
71
72 pub fn is_auto(&self) -> bool {
74 self.ident.0 == atom!("") && self.line_num.is_zero() && !self.is_span
75 }
76
77 pub fn is_ident_only(&self) -> bool {
79 self.ident.0 != atom!("") && self.line_num.is_zero() && !self.is_span
80 }
81
82 pub fn can_omit(&self, other: &Self) -> bool {
86 if self.is_ident_only() {
87 self == other
88 } else {
89 other.is_auto()
90 }
91 }
92}
93
94impl<Integer> ToCss for GridLine<Integer>
95where
96 Integer: ToCss + PartialEq + Zero + One,
97{
98 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
99 where
100 W: Write,
101 {
102 if self.is_auto() {
104 return dest.write_str("auto");
105 }
106
107 if self.is_ident_only() {
109 return self.ident.to_css(dest);
110 }
111
112 let has_ident = self.ident.0 != atom!("");
114 if self.is_span {
115 dest.write_str("span")?;
116 debug_assert!(!self.line_num.is_zero() || has_ident);
117
118 if !self.line_num.is_zero() && !(self.line_num.is_one() && has_ident) {
123 dest.write_char(' ')?;
124 self.line_num.to_css(dest)?;
125 }
126
127 if has_ident {
128 dest.write_char(' ')?;
129 self.ident.to_css(dest)?;
130 }
131 return Ok(());
132 }
133
134 debug_assert!(!self.line_num.is_zero());
136 self.line_num.to_css(dest)?;
137 if has_ident {
138 dest.write_char(' ')?;
139 self.ident.to_css(dest)?;
140 }
141 Ok(())
142 }
143}
144
145impl Parse for GridLine<specified::Integer> {
146 fn parse<'i, 't>(
147 context: &ParserContext,
148 input: &mut Parser<'i, 't>,
149 ) -> Result<Self, ParseError<'i>> {
150 let mut grid_line = Self::auto();
151 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
152 return Ok(grid_line);
153 }
154
155 let mut val_before_span = false;
160
161 for _ in 0..3 {
162 let location = input.current_source_location();
164 if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() {
165 if grid_line.is_span {
166 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
167 }
168
169 if !grid_line.line_num.is_zero() || grid_line.ident.0 != atom!("") {
170 val_before_span = true;
171 }
172
173 grid_line.is_span = true;
174 } else if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) {
175 let value = i.value();
177 if value == 0 || val_before_span || !grid_line.line_num.is_zero() {
178 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
179 }
180
181 grid_line.line_num = specified::Integer::new(cmp::max(
182 MIN_GRID_LINE,
183 cmp::min(value, MAX_GRID_LINE),
184 ));
185 } else if let Ok(name) = input.try_parse(|i| CustomIdent::parse(i, &["auto"])) {
186 if val_before_span || grid_line.ident.0 != atom!("") {
187 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
188 }
189 grid_line.ident = name;
192 } else {
193 break;
194 }
195 }
196
197 if grid_line.is_auto() {
198 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
199 }
200
201 if grid_line.is_span {
202 if !grid_line.line_num.is_zero() {
203 if grid_line.line_num.value() <= 0 {
204 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
206 }
207 } else if grid_line.ident.0 == atom!("") {
208 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
210 }
211 }
212
213 Ok(grid_line)
214 }
215}
216
217#[derive(
222 Animate,
223 Clone,
224 Debug,
225 MallocSizeOf,
226 PartialEq,
227 SpecifiedValueInfo,
228 ToAnimatedValue,
229 ToComputedValue,
230 ToCss,
231 ToResolvedValue,
232 ToShmem,
233)]
234#[repr(C, u8)]
235pub enum GenericTrackBreadth<L> {
236 Breadth(L),
238 #[css(dimension)]
240 Fr(CSSFloat),
241 Auto,
243 MinContent,
245 MaxContent,
247}
248
249pub use self::GenericTrackBreadth as TrackBreadth;
250
251impl<L> TrackBreadth<L> {
252 #[inline]
256 pub fn is_fixed(&self) -> bool {
257 matches!(*self, TrackBreadth::Breadth(..))
258 }
259}
260
261#[derive(
266 Clone,
267 Debug,
268 MallocSizeOf,
269 PartialEq,
270 SpecifiedValueInfo,
271 ToAnimatedValue,
272 ToComputedValue,
273 ToResolvedValue,
274 ToShmem,
275)]
276#[repr(C, u8)]
277pub enum GenericTrackSize<L> {
278 Breadth(GenericTrackBreadth<L>),
280 #[css(function)]
285 Minmax(GenericTrackBreadth<L>, GenericTrackBreadth<L>),
286 #[css(function)]
293 FitContent(GenericTrackBreadth<L>),
294}
295
296pub use self::GenericTrackSize as TrackSize;
297
298impl<L> TrackSize<L> {
299 const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto);
301
302 pub const fn initial_value() -> Self {
304 Self::INITIAL_VALUE
305 }
306
307 pub fn is_initial(&self) -> bool {
309 matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) }
311
312 pub fn is_fixed(&self) -> bool {
316 match *self {
317 TrackSize::Breadth(ref breadth) => breadth.is_fixed(),
318 TrackSize::Minmax(ref breadth_1, ref breadth_2) => {
323 if breadth_1.is_fixed() {
324 return true; }
326
327 match *breadth_1 {
328 TrackBreadth::Fr(_) => false, _ => breadth_2.is_fixed(),
330 }
331 },
332 TrackSize::FitContent(_) => false,
333 }
334 }
335}
336
337impl<L> Default for TrackSize<L> {
338 fn default() -> Self {
339 Self::initial_value()
340 }
341}
342
343impl<L: ToCss> ToCss for TrackSize<L> {
344 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
345 where
346 W: Write,
347 {
348 match *self {
349 TrackSize::Breadth(ref breadth) => breadth.to_css(dest),
350 TrackSize::Minmax(ref min, ref max) => {
351 if let TrackBreadth::Auto = *min {
354 if let TrackBreadth::Fr(_) = *max {
355 return max.to_css(dest);
356 }
357 }
358
359 dest.write_str("minmax(")?;
360 min.to_css(dest)?;
361 dest.write_str(", ")?;
362 max.to_css(dest)?;
363 dest.write_char(')')
364 },
365 TrackSize::FitContent(ref lp) => {
366 dest.write_str("fit-content(")?;
367 lp.to_css(dest)?;
368 dest.write_char(')')
369 },
370 }
371 }
372}
373
374#[derive(
378 Clone,
379 Debug,
380 Default,
381 MallocSizeOf,
382 PartialEq,
383 SpecifiedValueInfo,
384 ToComputedValue,
385 ToCss,
386 ToResolvedValue,
387 ToShmem,
388 ToTyped,
389)]
390#[repr(transparent)]
391pub struct GenericImplicitGridTracks<T>(
392 #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice<T>,
393);
394
395pub use self::GenericImplicitGridTracks as ImplicitGridTracks;
396
397impl<T: fmt::Debug + Default + PartialEq> ImplicitGridTracks<T> {
398 pub fn is_initial(&self) -> bool {
400 debug_assert_ne!(
401 *self,
402 ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()]))
403 );
404 self.0.is_empty()
405 }
406}
407
408pub fn concat_serialize_idents<W>(
411 prefix: &str,
412 suffix: &str,
413 slice: &[CustomIdent],
414 sep: &str,
415 dest: &mut CssWriter<W>,
416) -> fmt::Result
417where
418 W: Write,
419{
420 if let Some((ref first, rest)) = slice.split_first() {
421 dest.write_str(prefix)?;
422 first.to_css(dest)?;
423 for thing in rest {
424 dest.write_str(sep)?;
425 thing.to_css(dest)?;
426 }
427
428 dest.write_str(suffix)?;
429 }
430
431 Ok(())
432}
433
434#[derive(
438 Clone,
439 Copy,
440 Debug,
441 MallocSizeOf,
442 PartialEq,
443 SpecifiedValueInfo,
444 ToAnimatedValue,
445 ToComputedValue,
446 ToCss,
447 ToResolvedValue,
448 ToShmem,
449)]
450#[repr(C, u8)]
451pub enum RepeatCount<Integer> {
452 Number(Integer),
454 AutoFill,
456 AutoFit,
458}
459
460impl Parse for RepeatCount<specified::Integer> {
461 fn parse<'i, 't>(
462 context: &ParserContext,
463 input: &mut Parser<'i, 't>,
464 ) -> Result<Self, ParseError<'i>> {
465 if let Ok(mut i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) {
466 if i.value() > MAX_GRID_LINE {
467 i = specified::Integer::new(MAX_GRID_LINE);
468 }
469 return Ok(RepeatCount::Number(i));
470 }
471 try_match_ident_ignore_ascii_case! { input,
472 "auto-fill" => Ok(RepeatCount::AutoFill),
473 "auto-fit" => Ok(RepeatCount::AutoFit),
474 }
475 }
476}
477
478#[derive(
480 Clone,
481 Debug,
482 MallocSizeOf,
483 PartialEq,
484 SpecifiedValueInfo,
485 ToAnimatedValue,
486 ToComputedValue,
487 ToResolvedValue,
488 ToShmem,
489)]
490#[css(function = "repeat")]
491#[repr(C)]
492pub struct GenericTrackRepeat<L, I> {
493 pub count: RepeatCount<I>,
495 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
501 pub track_sizes: crate::OwnedSlice<GenericTrackSize<L>>,
503}
504
505pub use self::GenericTrackRepeat as TrackRepeat;
506
507impl<L: ToCss, I: ToCss> ToCss for TrackRepeat<L, I> {
508 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
509 where
510 W: Write,
511 {
512 dest.write_str("repeat(")?;
513 self.count.to_css(dest)?;
514 dest.write_str(", ")?;
515
516 let mut line_names_iter = self.line_names.iter();
517 for (i, (ref size, ref names)) in self
518 .track_sizes
519 .iter()
520 .zip(&mut line_names_iter)
521 .enumerate()
522 {
523 if i > 0 {
524 dest.write_char(' ')?;
525 }
526
527 concat_serialize_idents("[", "] ", names, " ", dest)?;
528 size.to_css(dest)?;
529 }
530
531 if let Some(line_names_last) = line_names_iter.next() {
532 concat_serialize_idents(" [", "]", line_names_last, " ", dest)?;
533 }
534
535 dest.write_char(')')?;
536
537 Ok(())
538 }
539}
540
541#[derive(
543 Animate,
544 Clone,
545 Debug,
546 MallocSizeOf,
547 PartialEq,
548 SpecifiedValueInfo,
549 ToAnimatedValue,
550 ToComputedValue,
551 ToCss,
552 ToResolvedValue,
553 ToShmem,
554)]
555#[repr(C, u8)]
556pub enum GenericTrackListValue<LengthPercentage, Integer> {
557 TrackSize(#[animation(field_bound)] GenericTrackSize<LengthPercentage>),
559 TrackRepeat(#[animation(field_bound)] GenericTrackRepeat<LengthPercentage, Integer>),
561}
562
563pub use self::GenericTrackListValue as TrackListValue;
564
565impl<L, I> TrackListValue<L, I> {
566 const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto));
568
569 fn is_repeat(&self) -> bool {
570 matches!(*self, TrackListValue::TrackRepeat(..))
571 }
572
573 pub fn is_initial(&self) -> bool {
575 matches!(
576 *self,
577 TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto))
578 ) }
580}
581
582impl<L, I> Default for TrackListValue<L, I> {
583 #[inline]
584 fn default() -> Self {
585 Self::INITIAL_VALUE
586 }
587}
588
589#[derive(
593 Clone,
594 Debug,
595 MallocSizeOf,
596 PartialEq,
597 SpecifiedValueInfo,
598 ToAnimatedValue,
599 ToComputedValue,
600 ToResolvedValue,
601 ToShmem,
602)]
603#[repr(C)]
604pub struct GenericTrackList<LengthPercentage, Integer> {
605 #[css(skip)]
607 pub auto_repeat_index: usize,
608 pub values: crate::OwnedSlice<GenericTrackListValue<LengthPercentage, Integer>>,
610 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
616}
617
618pub use self::GenericTrackList as TrackList;
619
620impl<L, I> TrackList<L, I> {
621 pub fn is_explicit(&self) -> bool {
624 !self.values.iter().any(|v| v.is_repeat())
625 }
626
627 pub fn has_auto_repeat(&self) -> bool {
629 self.auto_repeat_index < self.values.len()
630 }
631}
632
633impl<L: ToCss, I: ToCss> ToCss for TrackList<L, I> {
634 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
635 where
636 W: Write,
637 {
638 let mut values_iter = self.values.iter().peekable();
639 let mut line_names_iter = self.line_names.iter().peekable();
640
641 for idx in 0.. {
642 let names = line_names_iter.next().unwrap(); concat_serialize_idents("[", "]", names, " ", dest)?;
644
645 match values_iter.next() {
646 Some(value) => {
647 if !names.is_empty() {
648 dest.write_char(' ')?;
649 }
650
651 value.to_css(dest)?;
652 },
653 None => break,
654 }
655
656 if values_iter.peek().is_some()
657 || line_names_iter.peek().map_or(false, |v| !v.is_empty())
658 || (idx + 1 == self.auto_repeat_index)
659 {
660 dest.write_char(' ')?;
661 }
662 }
663
664 Ok(())
665 }
666}
667
668#[derive(
674 Clone,
675 Debug,
676 MallocSizeOf,
677 PartialEq,
678 SpecifiedValueInfo,
679 ToAnimatedValue,
680 ToComputedValue,
681 ToResolvedValue,
682 ToShmem,
683)]
684#[repr(C)]
685pub struct GenericNameRepeat<I> {
686 pub count: RepeatCount<I>,
689 pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
691}
692
693pub use self::GenericNameRepeat as NameRepeat;
694
695impl<I: ToCss> ToCss for NameRepeat<I> {
696 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
697 where
698 W: Write,
699 {
700 dest.write_str("repeat(")?;
701 self.count.to_css(dest)?;
702 dest.write_char(',')?;
703
704 for ref names in self.line_names.iter() {
705 if names.is_empty() {
706 dest.write_str(" []")?;
709 } else {
710 concat_serialize_idents(" [", "]", names, " ", dest)?;
711 }
712 }
713
714 dest.write_char(')')
715 }
716}
717
718impl<I> NameRepeat<I> {
719 #[inline]
721 pub fn is_auto_fill(&self) -> bool {
722 matches!(self.count, RepeatCount::AutoFill)
723 }
724}
725
726#[derive(
728 Clone,
729 Debug,
730 MallocSizeOf,
731 PartialEq,
732 SpecifiedValueInfo,
733 ToAnimatedValue,
734 ToComputedValue,
735 ToResolvedValue,
736 ToShmem,
737)]
738#[repr(C, u8)]
739pub enum GenericLineNameListValue<I> {
740 LineNames(crate::OwnedSlice<CustomIdent>),
742 Repeat(GenericNameRepeat<I>),
744}
745
746pub use self::GenericLineNameListValue as LineNameListValue;
747
748impl<I: ToCss> ToCss for LineNameListValue<I> {
749 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
750 where
751 W: Write,
752 {
753 match *self {
754 Self::Repeat(ref r) => r.to_css(dest),
755 Self::LineNames(ref names) => {
756 dest.write_char('[')?;
757
758 if let Some((ref first, rest)) = names.split_first() {
759 first.to_css(dest)?;
760 for name in rest {
761 dest.write_char(' ')?;
762 name.to_css(dest)?;
763 }
764 }
765
766 dest.write_char(']')
767 },
768 }
769 }
770}
771
772#[derive(
779 Clone,
780 Debug,
781 Default,
782 MallocSizeOf,
783 PartialEq,
784 SpecifiedValueInfo,
785 ToAnimatedValue,
786 ToComputedValue,
787 ToResolvedValue,
788 ToShmem,
789)]
790#[repr(C)]
791pub struct GenericLineNameList<I> {
792 pub expanded_line_names_length: usize,
796 pub line_names: crate::OwnedSlice<GenericLineNameListValue<I>>,
798}
799
800pub use self::GenericLineNameList as LineNameList;
801
802impl<I: ToCss> ToCss for LineNameList<I> {
803 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
804 where
805 W: Write,
806 {
807 dest.write_str("subgrid")?;
808
809 for value in self.line_names.iter() {
810 dest.write_char(' ')?;
811 value.to_css(dest)?;
812 }
813
814 Ok(())
815 }
816}
817
818#[derive(
820 Animate,
821 Clone,
822 Debug,
823 MallocSizeOf,
824 PartialEq,
825 SpecifiedValueInfo,
826 ToAnimatedValue,
827 ToComputedValue,
828 ToCss,
829 ToResolvedValue,
830 ToShmem,
831 ToTyped,
832)]
833#[value_info(other_values = "subgrid")]
834#[repr(C, u8)]
835pub enum GenericGridTemplateComponent<L, I> {
836 None,
838 TrackList(
840 #[animation(field_bound)]
841 #[compute(field_bound)]
842 #[resolve(field_bound)]
843 #[shmem(field_bound)]
844 Box<GenericTrackList<L, I>>,
845 ),
846 #[animation(error)]
849 Subgrid(Box<GenericLineNameList<I>>),
850 Masonry,
853}
854
855pub use self::GenericGridTemplateComponent as GridTemplateComponent;
856
857impl<L, I> GridTemplateComponent<L, I> {
858 const INITIAL_VALUE: Self = Self::None;
860
861 pub fn track_list_len(&self) -> usize {
863 match *self {
864 GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(),
865 _ => 0,
866 }
867 }
868
869 pub fn is_initial(&self) -> bool {
871 matches!(*self, Self::None) }
873}
874
875impl<L, I> Default for GridTemplateComponent<L, I> {
876 #[inline]
877 fn default() -> Self {
878 Self::INITIAL_VALUE
879 }
880}