1#![allow(non_upper_case_globals)]
4
5use crate::context::PropertyHandlerContext;
6use crate::declaration::{DeclarationBlock, DeclarationList};
7use crate::error::{Error, ErrorLocation, ParserError, PrinterError, PrinterErrorKind};
8use crate::macros::{define_shorthand, impl_shorthand};
9use crate::printer::Printer;
10use crate::properties::{Property, PropertyId};
11use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss};
12use crate::values::ident::CustomIdent;
13use crate::values::length::serialize_dimension;
14use crate::values::number::{CSSInteger, CSSNumber};
15use crate::values::{ident::CustomIdentList, length::LengthPercentage};
16#[cfg(feature = "visitor")]
17use crate::visitor::Visit;
18use bitflags::bitflags;
19use cssparser::*;
20use smallvec::SmallVec;
21
22#[cfg(feature = "serde")]
23use crate::serialization::ValueWrapper;
24
25#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
28#[cfg_attr(feature = "visitor", derive(Visit))]
29#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
30#[cfg_attr(
31 feature = "serde",
32 derive(serde::Serialize, serde::Deserialize),
33 serde(tag = "type", rename_all = "kebab-case")
34)]
35#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
36pub enum TrackSizing<'i> {
37 None,
39 #[cfg_attr(feature = "serde", serde(borrow))]
41 TrackList(TrackList<'i>),
42}
43
44#[derive(Debug, Clone, PartialEq)]
49#[cfg_attr(feature = "visitor", derive(Visit))]
50#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
51#[cfg_attr(
52 feature = "serde",
53 derive(serde::Serialize, serde::Deserialize),
54 serde(rename_all = "camelCase")
55)]
56#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
57pub struct TrackList<'i> {
58 #[cfg_attr(feature = "serde", serde(borrow))]
60 pub line_names: Vec<CustomIdentList<'i>>,
61 pub items: Vec<TrackListItem<'i>>,
63}
64
65#[derive(Debug, Clone, PartialEq)]
69#[cfg_attr(feature = "visitor", derive(Visit))]
70#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
71#[cfg_attr(
72 feature = "serde",
73 derive(serde::Serialize, serde::Deserialize),
74 serde(tag = "type", content = "value", rename_all = "kebab-case")
75)]
76#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
77pub enum TrackListItem<'i> {
78 TrackSize(TrackSize),
80 #[cfg_attr(feature = "serde", serde(borrow))]
82 TrackRepeat(TrackRepeat<'i>),
83}
84
85#[derive(Debug, Clone, PartialEq)]
90#[cfg_attr(feature = "visitor", derive(Visit))]
91#[cfg_attr(
92 feature = "serde",
93 derive(serde::Serialize, serde::Deserialize),
94 serde(tag = "type", rename_all = "kebab-case")
95)]
96#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
97#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
98pub enum TrackSize {
99 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<TrackBreadth>"))]
101 TrackBreadth(TrackBreadth),
102 MinMax {
104 min: TrackBreadth,
106 max: TrackBreadth,
108 },
109 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LengthPercentage>"))]
111 FitContent(LengthPercentage),
112}
113
114impl Default for TrackSize {
115 fn default() -> TrackSize {
116 TrackSize::TrackBreadth(TrackBreadth::Auto)
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Default)]
123#[cfg_attr(feature = "visitor", derive(Visit))]
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
125#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
126#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
127pub struct TrackSizeList(pub SmallVec<[TrackSize; 1]>);
128
129#[derive(Debug, Clone, PartialEq)]
133#[cfg_attr(feature = "visitor", derive(Visit))]
134#[cfg_attr(
135 feature = "serde",
136 derive(serde::Serialize, serde::Deserialize),
137 serde(tag = "type", content = "value", rename_all = "kebab-case")
138)]
139#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
140#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
141pub enum TrackBreadth {
142 Length(LengthPercentage),
144 Flex(CSSNumber),
146 MinContent,
148 MaxContent,
150 Auto,
152}
153
154#[derive(Debug, Clone, PartialEq)]
159#[cfg_attr(feature = "visitor", derive(Visit))]
160#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
161#[cfg_attr(
162 feature = "serde",
163 derive(serde::Serialize, serde::Deserialize),
164 serde(rename_all = "camelCase")
165)]
166#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
167pub struct TrackRepeat<'i> {
168 pub count: RepeatCount,
170 #[cfg_attr(feature = "serde", serde(borrow))]
172 pub line_names: Vec<CustomIdentList<'i>>,
173 pub track_sizes: Vec<TrackSize>,
175}
176
177#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
182#[cfg_attr(feature = "visitor", derive(Visit))]
183#[cfg_attr(
184 feature = "serde",
185 derive(serde::Serialize, serde::Deserialize),
186 serde(tag = "type", content = "value", rename_all = "kebab-case")
187)]
188#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
189#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
190pub enum RepeatCount {
191 Number(CSSInteger),
193 AutoFill,
195 AutoFit,
197}
198
199impl<'i> Parse<'i> for TrackSize {
200 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
201 if let Ok(breadth) = input.try_parse(TrackBreadth::parse) {
202 return Ok(TrackSize::TrackBreadth(breadth));
203 }
204
205 if input.try_parse(|input| input.expect_function_matching("minmax")).is_ok() {
206 return input.parse_nested_block(|input| {
207 let min = TrackBreadth::parse_internal(input, false)?;
208 input.expect_comma()?;
209 Ok(TrackSize::MinMax {
210 min,
211 max: TrackBreadth::parse(input)?,
212 })
213 });
214 }
215
216 input.expect_function_matching("fit-content")?;
217 let len = input.parse_nested_block(LengthPercentage::parse)?;
218 Ok(TrackSize::FitContent(len))
219 }
220}
221
222impl ToCss for TrackSize {
223 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
224 where
225 W: std::fmt::Write,
226 {
227 match self {
228 TrackSize::TrackBreadth(breadth) => breadth.to_css(dest),
229 TrackSize::MinMax { min, max } => {
230 dest.write_str("minmax(")?;
231 min.to_css(dest)?;
232 dest.delim(',', false)?;
233 max.to_css(dest)?;
234 dest.write_char(')')
235 }
236 TrackSize::FitContent(len) => {
237 dest.write_str("fit-content(")?;
238 len.to_css(dest)?;
239 dest.write_char(')')
240 }
241 }
242 }
243}
244
245impl<'i> Parse<'i> for TrackBreadth {
246 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
247 Self::parse_internal(input, true)
248 }
249}
250
251impl TrackBreadth {
252 fn parse_internal<'i, 't>(
253 input: &mut Parser<'i, 't>,
254 allow_flex: bool,
255 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
256 if let Ok(len) = input.try_parse(LengthPercentage::parse) {
257 return Ok(TrackBreadth::Length(len));
258 }
259
260 if allow_flex {
261 if let Ok(flex) = input.try_parse(Self::parse_flex) {
262 return Ok(TrackBreadth::Flex(flex));
263 }
264 }
265
266 let location = input.current_source_location();
267 let ident = input.expect_ident()?;
268 match_ignore_ascii_case! { &*ident,
269 "auto" => Ok(TrackBreadth::Auto),
270 "min-content" => Ok(TrackBreadth::MinContent),
271 "max-content" => Ok(TrackBreadth::MaxContent),
272 _ => Err(location.new_unexpected_token_error(
273 cssparser::Token::Ident(ident.clone())
274 ))
275 }
276 }
277
278 fn parse_flex<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CSSNumber, ParseError<'i, ParserError<'i>>> {
279 let location = input.current_source_location();
280 match *input.next()? {
281 Token::Dimension { value, ref unit, .. } if unit.eq_ignore_ascii_case("fr") && value.is_sign_positive() => {
282 Ok(value)
283 }
284 ref t => Err(location.new_unexpected_token_error(t.clone())),
285 }
286 }
287}
288
289impl ToCss for TrackBreadth {
290 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
291 where
292 W: std::fmt::Write,
293 {
294 match self {
295 TrackBreadth::Auto => dest.write_str("auto"),
296 TrackBreadth::MinContent => dest.write_str("min-content"),
297 TrackBreadth::MaxContent => dest.write_str("max-content"),
298 TrackBreadth::Length(len) => len.to_css(dest),
299 TrackBreadth::Flex(flex) => serialize_dimension(*flex, "fr", dest),
300 }
301 }
302}
303
304impl<'i> Parse<'i> for TrackRepeat<'i> {
305 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
306 input.expect_function_matching("repeat")?;
307 input.parse_nested_block(|input| {
308 let count = RepeatCount::parse(input)?;
309 input.expect_comma()?;
310
311 let mut line_names = Vec::new();
312 let mut track_sizes = Vec::new();
313
314 loop {
315 let line_name = input.try_parse(parse_line_names).unwrap_or_default();
316 line_names.push(line_name);
317
318 if let Ok(track_size) = input.try_parse(TrackSize::parse) {
319 track_sizes.push(track_size)
321 } else {
322 break;
323 }
324 }
325
326 Ok(TrackRepeat {
327 count,
328 line_names,
329 track_sizes,
330 })
331 })
332 }
333}
334
335impl<'i> ToCss for TrackRepeat<'i> {
336 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
337 where
338 W: std::fmt::Write,
339 {
340 dest.write_str("repeat(")?;
341 self.count.to_css(dest)?;
342 dest.delim(',', false)?;
343
344 let mut track_sizes_iter = self.track_sizes.iter();
345 let mut first = true;
346 for names in self.line_names.iter() {
347 if !names.is_empty() {
348 serialize_line_names(names, dest)?;
349 }
350
351 if let Some(size) = track_sizes_iter.next() {
352 if !names.is_empty() {
354 dest.whitespace()?;
355 } else if !first {
356 dest.write_char(' ')?;
357 }
358 size.to_css(dest)?;
359 }
360
361 first = false;
362 }
363
364 dest.write_char(')')
365 }
366}
367
368fn parse_line_names<'i, 't>(
369 input: &mut Parser<'i, 't>,
370) -> Result<CustomIdentList<'i>, ParseError<'i, ParserError<'i>>> {
371 input.expect_square_bracket_block()?;
372 input.parse_nested_block(|input| {
373 let mut values = SmallVec::new();
374 while let Ok(ident) = input.try_parse(CustomIdent::parse) {
375 values.push(ident)
376 }
377 Ok(values)
378 })
379}
380
381fn serialize_line_names<W>(names: &[CustomIdent], dest: &mut Printer<W>) -> Result<(), PrinterError>
382where
383 W: std::fmt::Write,
384{
385 dest.write_char('[')?;
386 let mut first = true;
387 for name in names {
388 if first {
389 first = false;
390 } else {
391 dest.write_char(' ')?;
392 }
393 write_ident(&name.0, dest)?;
394 }
395 dest.write_char(']')
396}
397
398fn write_ident<W>(name: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>
399where
400 W: std::fmt::Write,
401{
402 let css_module_grid_enabled = dest.css_module.as_ref().map_or(false, |css_module| css_module.config.grid);
403 if css_module_grid_enabled {
404 if let Some(css_module) = &mut dest.css_module {
405 if let Some(last) = css_module.config.pattern.segments.last() {
406 if !matches!(last, crate::css_modules::Segment::Local) {
407 return Err(Error {
408 kind: PrinterErrorKind::InvalidCssModulesPatternInGrid,
409 loc: Some(ErrorLocation {
410 filename: dest.filename().into(),
411 line: dest.loc.line,
412 column: dest.loc.column,
413 }),
414 });
415 }
416 }
417 }
418 }
419 dest.write_ident(name, css_module_grid_enabled)?;
420 Ok(())
421}
422
423impl<'i> Parse<'i> for TrackList<'i> {
424 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
425 let mut line_names = Vec::new();
426 let mut items = Vec::new();
427
428 loop {
429 let line_name = input.try_parse(parse_line_names).unwrap_or_default();
430 line_names.push(line_name);
431
432 if let Ok(track_size) = input.try_parse(TrackSize::parse) {
433 items.push(TrackListItem::TrackSize(track_size));
435 } else if let Ok(repeat) = input.try_parse(TrackRepeat::parse) {
436 items.push(TrackListItem::TrackRepeat(repeat))
438 } else {
439 break;
440 }
441 }
442
443 if items.is_empty() {
444 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
445 }
446
447 Ok(TrackList { line_names, items })
448 }
449}
450
451impl<'i> ToCss for TrackList<'i> {
452 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
453 where
454 W: std::fmt::Write,
455 {
456 let mut items_iter = self.items.iter();
457 let line_names_iter = self.line_names.iter();
458 let mut first = true;
459
460 for names in line_names_iter {
461 if !names.is_empty() {
462 serialize_line_names(names, dest)?;
463 }
464
465 if let Some(item) = items_iter.next() {
466 if !names.is_empty() {
468 dest.whitespace()?;
469 } else if !first {
470 dest.write_char(' ')?;
471 }
472 match item {
473 TrackListItem::TrackRepeat(repeat) => repeat.to_css(dest)?,
474 TrackListItem::TrackSize(size) => size.to_css(dest)?,
475 };
476 }
477
478 first = false;
479 }
480
481 Ok(())
482 }
483}
484
485impl<'i> TrackList<'i> {
486 fn is_explicit(&self) -> bool {
487 self.items.iter().all(|item| matches!(item, TrackListItem::TrackSize(_)))
488 }
489}
490
491impl<'i> TrackSizing<'i> {
492 fn is_explicit(&self) -> bool {
493 match self {
494 TrackSizing::None => true,
495 TrackSizing::TrackList(list) => list.is_explicit(),
496 }
497 }
498}
499
500impl<'i> Parse<'i> for TrackSizeList {
501 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
502 let mut res = SmallVec::new();
503 while let Ok(size) = input.try_parse(TrackSize::parse) {
504 res.push(size)
505 }
506 if res.len() == 1 && res[0] == TrackSize::default() {
507 res.clear();
508 }
509 Ok(TrackSizeList(res))
510 }
511}
512
513impl ToCss for TrackSizeList {
514 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
515 where
516 W: std::fmt::Write,
517 {
518 if self.0.len() == 0 {
519 return dest.write_str("auto");
520 }
521
522 let mut first = true;
523 for item in &self.0 {
524 if first {
525 first = false;
526 } else {
527 dest.write_char(' ')?;
528 }
529 item.to_css(dest)?;
530 }
531 Ok(())
532 }
533}
534
535#[derive(Debug, Clone, PartialEq)]
537#[cfg_attr(feature = "visitor", derive(Visit))]
538#[cfg_attr(
539 feature = "serde",
540 derive(serde::Serialize, serde::Deserialize),
541 serde(tag = "type", rename_all = "kebab-case")
542)]
543#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
544#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
545pub enum GridTemplateAreas {
546 None,
548 Areas {
550 columns: u32,
552 areas: Vec<Option<String>>,
555 },
556}
557
558impl<'i> Parse<'i> for GridTemplateAreas {
559 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
560 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
561 return Ok(GridTemplateAreas::None);
562 }
563
564 let mut tokens = Vec::new();
565 let mut row = 0;
566 let mut columns = 0;
567 while let Ok(s) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {
568 let parsed_columns = Self::parse_string(&s, &mut tokens)
569 .map_err(|()| input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
570
571 if row == 0 {
572 columns = parsed_columns;
573 } else if parsed_columns != columns {
574 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
575 }
576
577 row += 1;
578 }
579
580 Ok(GridTemplateAreas::Areas { columns, areas: tokens })
581 }
582}
583
584impl GridTemplateAreas {
585 fn parse_string(string: &str, tokens: &mut Vec<Option<String>>) -> Result<u32, ()> {
586 let mut string = string;
587 let mut column = 0;
588 loop {
589 let rest = string.trim_start_matches(HTML_SPACE_CHARACTERS);
590 if rest.is_empty() {
591 if column == 0 {
593 return Err(());
594 }
595 break;
596 }
597
598 column += 1;
599
600 if rest.starts_with('.') {
601 string = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
602 tokens.push(None);
603 continue;
604 }
605
606 if !rest.starts_with(is_name_code_point) {
607 return Err(());
608 }
609
610 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
611 let token = &rest[..token_len];
612 tokens.push(Some(token.into()));
613 string = &rest[token_len..];
614 }
615
616 Ok(column)
617 }
618}
619
620static HTML_SPACE_CHARACTERS: &'static [char] = &['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}'];
621
622fn is_name_code_point(c: char) -> bool {
623 c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '\u{80}' || c == '_' || c >= '0' && c <= '9' || c == '-'
624}
625
626impl ToCss for GridTemplateAreas {
627 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
628 where
629 W: std::fmt::Write,
630 {
631 match self {
632 GridTemplateAreas::None => dest.write_str("none"),
633 GridTemplateAreas::Areas { areas, .. } => {
634 let mut iter = areas.iter();
635 let mut next = iter.next();
636 let mut first = true;
637 while next.is_some() {
638 if !first && !dest.minify {
639 dest.newline()?;
640 }
641
642 self.write_string(dest, &mut iter, &mut next)?;
643
644 if first {
645 first = false;
646 if !dest.minify {
647 dest.indent_by(21);
649 }
650 }
651 }
652
653 if !dest.minify {
654 dest.dedent_by(21);
655 }
656
657 Ok(())
658 }
659 }
660 }
661}
662
663impl GridTemplateAreas {
664 fn write_string<'a, W>(
665 &self,
666 dest: &mut Printer<W>,
667 iter: &mut std::slice::Iter<'a, Option<String>>,
668 next: &mut Option<&'a Option<String>>,
669 ) -> Result<(), PrinterError>
670 where
671 W: std::fmt::Write,
672 {
673 let columns = match self {
674 GridTemplateAreas::Areas { columns, .. } => *columns,
675 _ => unreachable!(),
676 };
677
678 dest.write_char('"')?;
679
680 let mut last_was_null = false;
681 for i in 0..columns {
682 if let Some(token) = next {
683 if let Some(string) = token {
684 if i > 0 && (!last_was_null || !dest.minify) {
685 dest.write_char(' ')?;
686 }
687 write_ident(string, dest)?;
688 last_was_null = false;
689 } else {
690 if i > 0 && (last_was_null || !dest.minify) {
691 dest.write_char(' ')?;
692 }
693 dest.write_char('.')?;
694 last_was_null = true;
695 }
696 }
697
698 *next = iter.next();
699 }
700
701 dest.write_char('"')
702 }
703}
704
705#[derive(Debug, Clone, PartialEq)]
709#[cfg_attr(feature = "visitor", derive(Visit))]
710#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
711#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
712#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
713pub struct GridTemplate<'i> {
714 #[cfg_attr(feature = "serde", serde(borrow))]
716 pub rows: TrackSizing<'i>,
717 pub columns: TrackSizing<'i>,
719 pub areas: GridTemplateAreas,
721}
722
723impl<'i> Parse<'i> for GridTemplate<'i> {
724 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
725 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
726 input.expect_exhausted()?;
727 return Ok(GridTemplate {
728 rows: TrackSizing::None,
729 columns: TrackSizing::None,
730 areas: GridTemplateAreas::None,
731 });
732 }
733
734 let start = input.state();
735 let mut line_names: Vec<CustomIdentList<'i>> = Vec::new();
736 let mut items = Vec::new();
737 let mut columns = 0;
738 let mut row = 0;
739 let mut tokens = Vec::new();
740
741 loop {
742 if let Ok(first_names) = input.try_parse(parse_line_names) {
743 if let Some(last_names) = line_names.last_mut() {
744 last_names.extend(first_names);
745 } else {
746 line_names.push(first_names);
747 }
748 }
749
750 if let Ok(string) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {
751 let parsed_columns = GridTemplateAreas::parse_string(&string, &mut tokens)
752 .map_err(|()| input.new_custom_error(ParserError::InvalidDeclaration))?;
753
754 if row == 0 {
755 columns = parsed_columns;
756 } else if parsed_columns != columns {
757 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
758 }
759
760 row += 1;
761
762 let track_size = input.try_parse(TrackSize::parse).unwrap_or_default();
763 items.push(TrackListItem::TrackSize(track_size));
764
765 let last_names = input.try_parse(parse_line_names).unwrap_or_default();
766 line_names.push(last_names);
767 } else {
768 break;
769 }
770 }
771
772 if !tokens.is_empty() {
773 if line_names.len() == items.len() {
774 line_names.push(Default::default());
775 }
776
777 let areas = GridTemplateAreas::Areas { columns, areas: tokens };
778 let rows = TrackSizing::TrackList(TrackList { line_names, items });
779 let columns = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
780 let list = TrackList::parse(input)?;
781 if !list.is_explicit() {
782 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
783 }
784 TrackSizing::TrackList(list)
785 } else {
786 TrackSizing::None
787 };
788 Ok(GridTemplate { rows, columns, areas })
789 } else {
790 input.reset(&start);
791 let rows = TrackSizing::parse(input)?;
792 input.expect_delim('/')?;
793 let columns = TrackSizing::parse(input)?;
794 Ok(GridTemplate {
795 rows,
796 columns,
797 areas: GridTemplateAreas::None,
798 })
799 }
800 }
801}
802
803impl ToCss for GridTemplate<'_> {
804 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
805 where
806 W: std::fmt::Write,
807 {
808 self.to_css_with_indent(dest, 15)
809 }
810}
811
812impl GridTemplate<'_> {
813 fn to_css_with_indent<W>(&self, dest: &mut Printer<W>, indent: u8) -> Result<(), PrinterError>
814 where
815 W: std::fmt::Write,
816 {
817 match &self.areas {
818 GridTemplateAreas::None => {
819 if self.rows == TrackSizing::None && self.columns == TrackSizing::None {
820 dest.write_str("none")?;
821 } else {
822 self.rows.to_css(dest)?;
823 dest.delim('/', true)?;
824 self.columns.to_css(dest)?;
825 }
826 }
827 GridTemplateAreas::Areas { areas, .. } => {
828 let track_list = match &self.rows {
829 TrackSizing::TrackList(list) => list,
830 _ => unreachable!(),
831 };
832
833 let mut areas_iter = areas.iter();
834 let mut line_names_iter = track_list.line_names.iter();
835 let mut items_iter = track_list.items.iter();
836
837 let mut next = areas_iter.next();
838 let mut first = true;
839 let mut indented = false;
840 while next.is_some() {
841 macro_rules! newline {
842 () => {
843 if !dest.minify {
844 if !indented {
845 dest.indent_by(indent);
847 indented = true;
848 }
849 dest.newline()?;
850 }
851 };
852 }
853
854 if let Some(line_names) = line_names_iter.next() {
855 if !line_names.is_empty() {
856 if !dest.minify && line_names.len() == 2 {
857 dest.whitespace()?;
858 serialize_line_names(&line_names[0..1], dest)?;
859 newline!();
860 serialize_line_names(&line_names[1..], dest)?;
861 } else {
862 if !first {
863 newline!();
864 }
865 serialize_line_names(line_names, dest)?;
866 }
867 dest.whitespace()?;
868 } else if !first {
869 newline!();
870 }
871 } else if !first {
872 newline!();
873 }
874
875 self.areas.write_string(dest, &mut areas_iter, &mut next)?;
876
877 if let Some(item) = items_iter.next() {
878 if *item != TrackListItem::TrackSize(TrackSize::default()) {
879 dest.whitespace()?;
880 match item {
881 TrackListItem::TrackSize(size) => size.to_css(dest)?,
882 _ => unreachable!(),
883 }
884 }
885 }
886
887 first = false;
888 }
889
890 if let Some(line_names) = line_names_iter.next() {
891 if !line_names.is_empty() {
892 dest.whitespace()?;
893 serialize_line_names(line_names, dest)?;
894 }
895 }
896
897 if let TrackSizing::TrackList(track_list) = &self.columns {
898 dest.newline()?;
899 dest.delim('/', false)?;
900 track_list.to_css(dest)?;
901 }
902
903 if indented {
904 dest.dedent_by(indent);
905 }
906 }
907 }
908
909 Ok(())
910 }
911}
912
913impl<'i> GridTemplate<'i> {
914 #[inline]
915 fn is_valid(rows: &TrackSizing, columns: &TrackSizing, areas: &GridTemplateAreas) -> bool {
916 *areas == GridTemplateAreas::None
919 || (*rows != TrackSizing::None && rows.is_explicit() && columns.is_explicit())
920 }
921}
922
923impl_shorthand! {
924 GridTemplate(GridTemplate<'i>) {
925 rows: [GridTemplateRows],
926 columns: [GridTemplateColumns],
927 areas: [GridTemplateAreas],
928 }
929
930 fn is_valid(shorthand) {
931 GridTemplate::is_valid(&shorthand.rows, &shorthand.columns, &shorthand.areas)
932 }
933}
934
935bitflags! {
936 #[cfg_attr(feature = "visitor", derive(Visit))]
941 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(from = "SerializedGridAutoFlow", into = "SerializedGridAutoFlow"))]
942 #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
943 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
944 pub struct GridAutoFlow: u8 {
945 const Row = 0b00;
947 const Column = 0b01;
949 const Dense = 0b10;
951 }
952}
953
954#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
955#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
956struct SerializedGridAutoFlow {
957 direction: AutoFlowDirection,
959 dense: bool,
961}
962
963impl From<GridAutoFlow> for SerializedGridAutoFlow {
964 fn from(flow: GridAutoFlow) -> Self {
965 Self {
966 direction: if flow.contains(GridAutoFlow::Column) {
967 AutoFlowDirection::Column
968 } else {
969 AutoFlowDirection::Row
970 },
971 dense: flow.contains(GridAutoFlow::Dense),
972 }
973 }
974}
975
976impl From<SerializedGridAutoFlow> for GridAutoFlow {
977 fn from(s: SerializedGridAutoFlow) -> GridAutoFlow {
978 let mut flow = match s.direction {
979 AutoFlowDirection::Row => GridAutoFlow::Row,
980 AutoFlowDirection::Column => GridAutoFlow::Column,
981 };
982 if s.dense {
983 flow |= GridAutoFlow::Dense
984 }
985
986 flow
987 }
988}
989
990#[cfg_attr(
991 feature = "serde",
992 derive(serde::Serialize, serde::Deserialize),
993 serde(rename_all = "lowercase")
994)]
995#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
996enum AutoFlowDirection {
997 Row,
998 Column,
999}
1000
1001#[cfg(feature = "jsonschema")]
1002#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
1003impl<'a> schemars::JsonSchema for GridAutoFlow {
1004 fn is_referenceable() -> bool {
1005 true
1006 }
1007
1008 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1009 SerializedGridAutoFlow::json_schema(gen)
1010 }
1011
1012 fn schema_name() -> String {
1013 "GridAutoFlow".into()
1014 }
1015}
1016
1017impl Default for GridAutoFlow {
1018 fn default() -> GridAutoFlow {
1019 GridAutoFlow::Row
1020 }
1021}
1022
1023impl GridAutoFlow {
1024 fn direction(self) -> GridAutoFlow {
1025 self & GridAutoFlow::Column
1026 }
1027}
1028
1029impl<'i> Parse<'i> for GridAutoFlow {
1030 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1031 let mut flow = GridAutoFlow::Row;
1032
1033 macro_rules! match_dense {
1034 () => {
1035 if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1036 flow |= GridAutoFlow::Dense;
1037 }
1038 };
1039 }
1040
1041 let location = input.current_source_location();
1042 let ident = input.expect_ident()?;
1043 match_ignore_ascii_case! { &ident,
1044 "row" => {
1045 match_dense!();
1046 },
1047 "column" => {
1048 flow = GridAutoFlow::Column;
1049 match_dense!();
1050 },
1051 "dense" => {
1052 let location = input.current_source_location();
1053 input.try_parse(|input| {
1054 let ident = input.expect_ident()?;
1055 match_ignore_ascii_case! { &ident,
1056 "row" => {},
1057 "column" => {
1058 flow = GridAutoFlow::Column;
1059 },
1060 _ => return Err(location.new_unexpected_token_error(
1061 cssparser::Token::Ident(ident.clone())
1062 ))
1063 }
1064 Ok(())
1065 })?;
1066 flow |= GridAutoFlow::Dense;
1067 },
1068 _ => return Err(location.new_unexpected_token_error(
1069 cssparser::Token::Ident(ident.clone())
1070 ))
1071 }
1072
1073 Ok(flow)
1074 }
1075}
1076
1077impl ToCss for GridAutoFlow {
1078 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1079 where
1080 W: std::fmt::Write,
1081 {
1082 let s = if *self == GridAutoFlow::Row {
1083 "row"
1084 } else if *self == GridAutoFlow::Column {
1085 "column"
1086 } else if *self == GridAutoFlow::Row | GridAutoFlow::Dense {
1087 if dest.minify {
1088 "dense"
1089 } else {
1090 "row dense"
1091 }
1092 } else if *self == GridAutoFlow::Column | GridAutoFlow::Dense {
1093 "column dense"
1094 } else {
1095 unreachable!();
1096 };
1097
1098 dest.write_str(s)
1099 }
1100}
1101
1102#[derive(Debug, Clone, PartialEq)]
1106#[cfg_attr(feature = "visitor", derive(Visit))]
1107#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1108#[cfg_attr(
1109 feature = "serde",
1110 derive(serde::Serialize, serde::Deserialize),
1111 serde(rename_all = "camelCase")
1112)]
1113#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1114pub struct Grid<'i> {
1115 #[cfg_attr(feature = "serde", serde(borrow))]
1117 pub rows: TrackSizing<'i>,
1118 pub columns: TrackSizing<'i>,
1120 pub areas: GridTemplateAreas,
1122 pub auto_rows: TrackSizeList,
1124 pub auto_columns: TrackSizeList,
1126 pub auto_flow: GridAutoFlow,
1128}
1129
1130impl<'i> Parse<'i> for Grid<'i> {
1131 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1132 if let Ok(template) = input.try_parse(GridTemplate::parse) {
1134 Ok(Grid {
1135 rows: template.rows,
1136 columns: template.columns,
1137 areas: template.areas,
1138 auto_rows: TrackSizeList::default(),
1139 auto_columns: TrackSizeList::default(),
1140 auto_flow: GridAutoFlow::default(),
1141 })
1142
1143 } else if let Ok(rows) = input.try_parse(TrackSizing::parse) {
1145 input.expect_delim('/')?;
1146 let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Column)?;
1147 let auto_columns = TrackSizeList::parse(input).unwrap_or_default();
1148 Ok(Grid {
1149 rows,
1150 columns: TrackSizing::None,
1151 areas: GridTemplateAreas::None,
1152 auto_rows: TrackSizeList::default(),
1153 auto_columns,
1154 auto_flow,
1155 })
1156
1157 } else {
1159 let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Row)?;
1160 let auto_rows = input.try_parse(TrackSizeList::parse).unwrap_or_default();
1161 input.expect_delim('/')?;
1162 let columns = TrackSizing::parse(input)?;
1163 Ok(Grid {
1164 rows: TrackSizing::None,
1165 columns,
1166 areas: GridTemplateAreas::None,
1167 auto_rows,
1168 auto_columns: TrackSizeList::default(),
1169 auto_flow,
1170 })
1171 }
1172 }
1173}
1174
1175fn parse_grid_auto_flow<'i, 't>(
1176 input: &mut Parser<'i, 't>,
1177 flow: GridAutoFlow,
1178) -> Result<GridAutoFlow, ParseError<'i, ParserError<'i>>> {
1179 if input.try_parse(|input| input.expect_ident_matching("auto-flow")).is_ok() {
1180 if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1181 Ok(flow | GridAutoFlow::Dense)
1182 } else {
1183 Ok(flow)
1184 }
1185 } else if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1186 input.expect_ident_matching("auto-flow")?;
1187 Ok(flow | GridAutoFlow::Dense)
1188 } else {
1189 Err(input.new_error_for_next_token())
1190 }
1191}
1192
1193impl ToCss for Grid<'_> {
1194 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1195 where
1196 W: std::fmt::Write,
1197 {
1198 let is_auto_initial = self.auto_rows == TrackSizeList::default()
1199 && self.auto_columns == TrackSizeList::default()
1200 && self.auto_flow == GridAutoFlow::default();
1201
1202 if self.areas != GridTemplateAreas::None
1203 || (self.rows != TrackSizing::None && self.columns != TrackSizing::None)
1204 || (self.areas == GridTemplateAreas::None && is_auto_initial)
1205 {
1206 if !is_auto_initial {
1207 unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1208 }
1209 let template = GridTemplate {
1210 rows: self.rows.clone(),
1211 columns: self.columns.clone(),
1212 areas: self.areas.clone(),
1213 };
1214 template.to_css_with_indent(dest, 6)?;
1215 } else if self.auto_flow.direction() == GridAutoFlow::Column {
1216 if self.columns != TrackSizing::None || self.auto_rows != TrackSizeList::default() {
1217 unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1218 }
1219 self.rows.to_css(dest)?;
1220 dest.delim('/', true)?;
1221 dest.write_str("auto-flow")?;
1222 if self.auto_flow.contains(GridAutoFlow::Dense) {
1223 dest.write_str(" dense")?;
1224 }
1225 if self.auto_columns != TrackSizeList::default() {
1226 dest.write_char(' ')?;
1227 self.auto_columns.to_css(dest)?;
1228 }
1229 } else {
1230 if self.rows != TrackSizing::None || self.auto_columns != TrackSizeList::default() {
1231 unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1232 }
1233 dest.write_str("auto-flow")?;
1234 if self.auto_flow.contains(GridAutoFlow::Dense) {
1235 dest.write_str(" dense")?;
1236 }
1237 if self.auto_rows != TrackSizeList::default() {
1238 dest.write_char(' ')?;
1239 self.auto_rows.to_css(dest)?;
1240 }
1241 dest.delim('/', true)?;
1242 self.columns.to_css(dest)?;
1243 }
1244
1245 Ok(())
1246 }
1247}
1248
1249impl<'i> Grid<'i> {
1250 #[inline]
1251 fn is_valid(
1252 rows: &TrackSizing,
1253 columns: &TrackSizing,
1254 areas: &GridTemplateAreas,
1255 auto_rows: &TrackSizeList,
1256 auto_columns: &TrackSizeList,
1257 auto_flow: &GridAutoFlow,
1258 ) -> bool {
1259 let is_template = GridTemplate::is_valid(rows, columns, areas);
1262 let default_track_size_list = TrackSizeList::default();
1263 let is_explicit = *auto_rows == default_track_size_list
1264 && *auto_columns == default_track_size_list
1265 && *auto_flow == GridAutoFlow::default();
1266 let is_auto_rows = auto_flow.direction() == GridAutoFlow::Row
1267 && *rows == TrackSizing::None
1268 && *auto_columns == default_track_size_list;
1269 let is_auto_columns = auto_flow.direction() == GridAutoFlow::Column
1270 && *columns == TrackSizing::None
1271 && *auto_rows == default_track_size_list;
1272
1273 (is_template && is_explicit) || is_auto_rows || is_auto_columns
1274 }
1275}
1276
1277impl_shorthand! {
1278 Grid(Grid<'i>) {
1279 rows: [GridTemplateRows],
1280 columns: [GridTemplateColumns],
1281 areas: [GridTemplateAreas],
1282 auto_rows: [GridAutoRows],
1283 auto_columns: [GridAutoColumns],
1284 auto_flow: [GridAutoFlow],
1285 }
1286
1287 fn is_valid(grid) {
1288 Grid::is_valid(&grid.rows, &grid.columns, &grid.areas, &grid.auto_rows, &grid.auto_columns, &grid.auto_flow)
1289 }
1290}
1291
1292#[derive(Debug, Clone, PartialEq)]
1295#[cfg_attr(feature = "visitor", derive(Visit))]
1296#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1297#[cfg_attr(
1298 feature = "serde",
1299 derive(serde::Serialize, serde::Deserialize),
1300 serde(tag = "type", rename_all = "kebab-case")
1301)]
1302#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1303pub enum GridLine<'i> {
1304 Auto,
1306 Area {
1308 name: CustomIdent<'i>,
1310 },
1311 Line {
1313 index: CSSInteger,
1315 #[cfg_attr(feature = "serde", serde(borrow))]
1317 name: Option<CustomIdent<'i>>,
1318 },
1319 Span {
1321 index: CSSInteger,
1323 name: Option<CustomIdent<'i>>,
1325 },
1326}
1327
1328impl<'i> Parse<'i> for GridLine<'i> {
1329 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1330 if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
1331 return Ok(GridLine::Auto);
1332 }
1333
1334 if input.try_parse(|input| input.expect_ident_matching("span")).is_ok() {
1335 let (index, name) = if let Ok(line_number) = input.try_parse(CSSInteger::parse) {
1337 let ident = input.try_parse(CustomIdent::parse).ok();
1338 (line_number, ident)
1339 } else if let Ok(ident) = input.try_parse(CustomIdent::parse) {
1340 let line_number = input.try_parse(CSSInteger::parse).unwrap_or(1);
1341 (line_number, Some(ident))
1342 } else {
1343 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1344 };
1345
1346 if index == 0 {
1347 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1348 }
1349
1350 return Ok(GridLine::Span { index, name });
1351 }
1352
1353 if let Ok(index) = input.try_parse(CSSInteger::parse) {
1354 if index == 0 {
1355 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1356 }
1357 let name = input.try_parse(CustomIdent::parse).ok();
1358 return Ok(GridLine::Line { index, name });
1359 }
1360
1361 let name = CustomIdent::parse(input)?;
1362 if let Ok(index) = input.try_parse(CSSInteger::parse) {
1363 if index == 0 {
1364 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1365 }
1366 return Ok(GridLine::Line {
1367 index,
1368 name: Some(name),
1369 });
1370 }
1371
1372 Ok(GridLine::Area { name })
1373 }
1374}
1375
1376impl ToCss for GridLine<'_> {
1377 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1378 where
1379 W: std::fmt::Write,
1380 {
1381 match self {
1382 GridLine::Auto => dest.write_str("auto"),
1383 GridLine::Area { name } => write_ident(&name.0, dest),
1384 GridLine::Line { index, name } => {
1385 index.to_css(dest)?;
1386 if let Some(id) = name {
1387 dest.write_char(' ')?;
1388 write_ident(&id.0, dest)?;
1389 }
1390 Ok(())
1391 }
1392 GridLine::Span { index, name } => {
1393 dest.write_str("span ")?;
1394 if *index != 1 || name.is_none() {
1395 index.to_css(dest)?;
1396 if name.is_some() {
1397 dest.write_char(' ')?;
1398 }
1399 }
1400
1401 if let Some(id) = name {
1402 write_ident(&id.0, dest)?;
1403 }
1404 Ok(())
1405 }
1406 }
1407 }
1408}
1409
1410impl<'i> GridLine<'i> {
1411 fn default_end_value(&self) -> GridLine<'i> {
1412 if matches!(self, GridLine::Area { .. }) {
1413 self.clone()
1414 } else {
1415 GridLine::Auto
1416 }
1417 }
1418
1419 fn can_omit_end(&self, end: &GridLine) -> bool {
1420 if let GridLine::Area { name: start_id } = &self {
1421 matches!(end, GridLine::Area { name: end_id } if end_id == start_id)
1422 } else if matches!(end, GridLine::Auto) {
1423 true
1424 } else {
1425 false
1426 }
1427 }
1428}
1429
1430macro_rules! impl_grid_placement {
1431 ($name: ident) => {
1432 impl<'i> Parse<'i> for $name<'i> {
1433 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1434 let start = GridLine::parse(input)?;
1435 let end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1436 GridLine::parse(input)?
1437 } else {
1438 start.default_end_value()
1439 };
1440
1441 Ok($name { start, end })
1442 }
1443 }
1444
1445 impl ToCss for $name<'_> {
1446 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1447 where
1448 W: std::fmt::Write,
1449 {
1450 self.start.to_css(dest)?;
1451
1452 if !self.start.can_omit_end(&self.end) {
1453 dest.delim('/', true)?;
1454 self.end.to_css(dest)?;
1455 }
1456 Ok(())
1457 }
1458 }
1459 };
1460}
1461
1462define_shorthand! {
1463 pub struct GridRow<'i> {
1465 #[cfg_attr(feature = "serde", serde(borrow))]
1467 start: GridRowStart(GridLine<'i>),
1468 end: GridRowEnd(GridLine<'i>),
1470 }
1471}
1472
1473define_shorthand! {
1474 pub struct GridColumn<'i> {
1476 #[cfg_attr(feature = "serde", serde(borrow))]
1478 start: GridColumnStart(GridLine<'i>),
1479 end: GridColumnEnd(GridLine<'i>),
1481 }
1482}
1483
1484impl_grid_placement!(GridRow);
1485impl_grid_placement!(GridColumn);
1486
1487define_shorthand! {
1488 pub struct GridArea<'i> {
1490 #[cfg_attr(feature = "serde", serde(borrow))]
1492 row_start: GridRowStart(GridLine<'i>),
1493 column_start: GridColumnStart(GridLine<'i>),
1495 row_end: GridRowEnd(GridLine<'i>),
1497 column_end: GridColumnEnd(GridLine<'i>),
1499 }
1500}
1501
1502impl<'i> Parse<'i> for GridArea<'i> {
1503 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1504 let row_start = GridLine::parse(input)?;
1505 let column_start = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1506 GridLine::parse(input)?
1507 } else {
1508 let opposite = row_start.default_end_value();
1509 return Ok(GridArea {
1510 row_start,
1511 column_start: opposite.clone(),
1512 row_end: opposite.clone(),
1513 column_end: opposite,
1514 });
1515 };
1516
1517 let row_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1518 GridLine::parse(input)?
1519 } else {
1520 let row_end = row_start.default_end_value();
1521 let column_end = column_start.default_end_value();
1522 return Ok(GridArea {
1523 row_start,
1524 column_start,
1525 row_end,
1526 column_end,
1527 });
1528 };
1529
1530 let column_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1531 GridLine::parse(input)?
1532 } else {
1533 let column_end = column_start.default_end_value();
1534 return Ok(GridArea {
1535 row_start,
1536 column_start,
1537 row_end,
1538 column_end,
1539 });
1540 };
1541
1542 Ok(GridArea {
1543 row_start,
1544 column_start,
1545 row_end,
1546 column_end,
1547 })
1548 }
1549}
1550
1551impl ToCss for GridArea<'_> {
1552 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1553 where
1554 W: std::fmt::Write,
1555 {
1556 self.row_start.to_css(dest)?;
1557
1558 let can_omit_column_end = self.column_start.can_omit_end(&self.column_end);
1559 let can_omit_row_end = can_omit_column_end && self.row_start.can_omit_end(&self.row_end);
1560 let can_omit_column_start = can_omit_row_end && self.row_start.can_omit_end(&self.column_start);
1561
1562 if !can_omit_column_start {
1563 dest.delim('/', true)?;
1564 self.column_start.to_css(dest)?;
1565 }
1566
1567 if !can_omit_row_end {
1568 dest.delim('/', true)?;
1569 self.row_end.to_css(dest)?;
1570 }
1571
1572 if !can_omit_column_end {
1573 dest.delim('/', true)?;
1574 self.column_end.to_css(dest)?;
1575 }
1576
1577 Ok(())
1578 }
1579}
1580
1581#[derive(Default, Debug)]
1582pub(crate) struct GridHandler<'i> {
1583 rows: Option<TrackSizing<'i>>,
1584 columns: Option<TrackSizing<'i>>,
1585 areas: Option<GridTemplateAreas>,
1586 auto_rows: Option<TrackSizeList>,
1587 auto_columns: Option<TrackSizeList>,
1588 auto_flow: Option<GridAutoFlow>,
1589 row_start: Option<GridLine<'i>>,
1590 column_start: Option<GridLine<'i>>,
1591 row_end: Option<GridLine<'i>>,
1592 column_end: Option<GridLine<'i>>,
1593 has_any: bool,
1594}
1595
1596impl<'i> PropertyHandler<'i> for GridHandler<'i> {
1597 fn handle_property(
1598 &mut self,
1599 property: &Property<'i>,
1600 dest: &mut DeclarationList<'i>,
1601 context: &mut PropertyHandlerContext<'i, '_>,
1602 ) -> bool {
1603 use Property::*;
1604
1605 match property {
1606 GridTemplateColumns(columns) => self.columns = Some(columns.clone()),
1607 GridTemplateRows(rows) => self.rows = Some(rows.clone()),
1608 GridTemplateAreas(areas) => self.areas = Some(areas.clone()),
1609 GridAutoColumns(auto_columns) => self.auto_columns = Some(auto_columns.clone()),
1610 GridAutoRows(auto_rows) => self.auto_rows = Some(auto_rows.clone()),
1611 GridAutoFlow(auto_flow) => self.auto_flow = Some(auto_flow.clone()),
1612 GridTemplate(template) => {
1613 self.rows = Some(template.rows.clone());
1614 self.columns = Some(template.columns.clone());
1615 self.areas = Some(template.areas.clone());
1616 }
1617 Grid(grid) => {
1618 self.rows = Some(grid.rows.clone());
1619 self.columns = Some(grid.columns.clone());
1620 self.areas = Some(grid.areas.clone());
1621 self.auto_rows = Some(grid.auto_rows.clone());
1622 self.auto_columns = Some(grid.auto_columns.clone());
1623 self.auto_flow = Some(grid.auto_flow.clone());
1624 }
1625 GridRowStart(row_start) => self.row_start = Some(row_start.clone()),
1626 GridRowEnd(row_end) => self.row_end = Some(row_end.clone()),
1627 GridColumnStart(column_start) => self.column_start = Some(column_start.clone()),
1628 GridColumnEnd(column_end) => self.column_end = Some(column_end.clone()),
1629 GridRow(row) => {
1630 self.row_start = Some(row.start.clone());
1631 self.row_end = Some(row.end.clone());
1632 }
1633 GridColumn(column) => {
1634 self.column_start = Some(column.start.clone());
1635 self.column_end = Some(column.end.clone());
1636 }
1637 GridArea(area) => {
1638 self.row_start = Some(area.row_start.clone());
1639 self.row_end = Some(area.row_end.clone());
1640 self.column_start = Some(area.column_start.clone());
1641 self.column_end = Some(area.column_end.clone());
1642 }
1643 Unparsed(val) if is_grid_property(&val.property_id) => {
1644 self.finalize(dest, context);
1645 dest.push(property.clone());
1646 }
1647 _ => return false,
1648 }
1649
1650 self.has_any = true;
1651 true
1652 }
1653
1654 fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {
1655 if !self.has_any {
1656 return;
1657 }
1658
1659 self.has_any = false;
1660
1661 let mut rows = std::mem::take(&mut self.rows);
1662 let mut columns = std::mem::take(&mut self.columns);
1663 let mut areas = std::mem::take(&mut self.areas);
1664 let mut auto_rows = std::mem::take(&mut self.auto_rows);
1665 let mut auto_columns = std::mem::take(&mut self.auto_columns);
1666 let mut auto_flow = std::mem::take(&mut self.auto_flow);
1667 let mut row_start = std::mem::take(&mut self.row_start);
1668 let mut row_end = std::mem::take(&mut self.row_end);
1669 let mut column_start = std::mem::take(&mut self.column_start);
1670 let mut column_end = std::mem::take(&mut self.column_end);
1671
1672 if let (Some(rows_val), Some(columns_val), Some(areas_val)) = (&rows, &columns, &areas) {
1673 let mut has_template = true;
1674 if let (Some(auto_rows_val), Some(auto_columns_val), Some(auto_flow_val)) =
1675 (&auto_rows, &auto_columns, &auto_flow)
1676 {
1677 if Grid::is_valid(
1680 rows_val,
1681 columns_val,
1682 areas_val,
1683 auto_rows_val,
1684 auto_columns_val,
1685 auto_flow_val,
1686 ) {
1687 dest.push(Property::Grid(Grid {
1688 rows: rows_val.clone(),
1689 columns: columns_val.clone(),
1690 areas: areas_val.clone(),
1691 auto_rows: auto_rows_val.clone(),
1692 auto_columns: auto_columns_val.clone(),
1693 auto_flow: auto_flow_val.clone(),
1694 }));
1695
1696 has_template = false;
1697 auto_rows = None;
1698 auto_columns = None;
1699 auto_flow = None;
1700 }
1701 }
1702
1703 if has_template && GridTemplate::is_valid(rows_val, columns_val, areas_val) {
1706 dest.push(Property::GridTemplate(GridTemplate {
1707 rows: rows_val.clone(),
1708 columns: columns_val.clone(),
1709 areas: areas_val.clone(),
1710 }));
1711
1712 has_template = false;
1713 }
1714
1715 if !has_template {
1716 rows = None;
1717 columns = None;
1718 areas = None;
1719 }
1720 }
1721
1722 if row_start.is_some() && row_end.is_some() && column_start.is_some() && column_end.is_some() {
1723 dest.push(Property::GridArea(GridArea {
1724 row_start: std::mem::take(&mut row_start).unwrap(),
1725 row_end: std::mem::take(&mut row_end).unwrap(),
1726 column_start: std::mem::take(&mut column_start).unwrap(),
1727 column_end: std::mem::take(&mut column_end).unwrap(),
1728 }))
1729 } else {
1730 if row_start.is_some() && row_end.is_some() {
1731 dest.push(Property::GridRow(GridRow {
1732 start: std::mem::take(&mut row_start).unwrap(),
1733 end: std::mem::take(&mut row_end).unwrap(),
1734 }))
1735 }
1736
1737 if column_start.is_some() && column_end.is_some() {
1738 dest.push(Property::GridColumn(GridColumn {
1739 start: std::mem::take(&mut column_start).unwrap(),
1740 end: std::mem::take(&mut column_end).unwrap(),
1741 }))
1742 }
1743 }
1744
1745 macro_rules! single_property {
1746 ($prop: ident, $key: ident) => {
1747 if let Some(val) = $key {
1748 dest.push(Property::$prop(val))
1749 }
1750 };
1751 }
1752
1753 single_property!(GridTemplateRows, rows);
1754 single_property!(GridTemplateColumns, columns);
1755 single_property!(GridTemplateAreas, areas);
1756 single_property!(GridAutoRows, auto_rows);
1757 single_property!(GridAutoColumns, auto_columns);
1758 single_property!(GridAutoFlow, auto_flow);
1759 single_property!(GridRowStart, row_start);
1760 single_property!(GridRowEnd, row_end);
1761 single_property!(GridColumnStart, column_start);
1762 single_property!(GridColumnEnd, column_end);
1763 }
1764}
1765
1766#[inline]
1767fn is_grid_property(property_id: &PropertyId) -> bool {
1768 match property_id {
1769 PropertyId::GridTemplateColumns
1770 | PropertyId::GridTemplateRows
1771 | PropertyId::GridTemplateAreas
1772 | PropertyId::GridAutoColumns
1773 | PropertyId::GridAutoRows
1774 | PropertyId::GridAutoFlow
1775 | PropertyId::GridTemplate
1776 | PropertyId::Grid
1777 | PropertyId::GridRowStart
1778 | PropertyId::GridRowEnd
1779 | PropertyId::GridColumnStart
1780 | PropertyId::GridColumnEnd
1781 | PropertyId::GridRow
1782 | PropertyId::GridColumn
1783 | PropertyId::GridArea => true,
1784 _ => false,
1785 }
1786}