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)]
538#[cfg_attr(feature = "visitor", derive(Visit))]
539#[cfg_attr(
540 feature = "serde",
541 derive(serde::Serialize, serde::Deserialize),
542 serde(tag = "type", rename_all = "kebab-case")
543)]
544#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
545#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
546pub enum GridTemplateAreas {
547 None,
549 Areas {
551 columns: u32,
553 areas: Vec<Option<String>>,
556 },
557}
558
559impl<'i> Parse<'i> for GridTemplateAreas {
560 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
561 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
562 return Ok(GridTemplateAreas::None);
563 }
564
565 let mut tokens = Vec::new();
566 let mut row = 0;
567 let mut columns = 0;
568 while let Ok(s) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {
569 let parsed_columns = Self::parse_string(&s, &mut tokens)
570 .map_err(|()| input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
571
572 if row == 0 {
573 columns = parsed_columns;
574 } else if parsed_columns != columns {
575 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
576 }
577
578 row += 1;
579 }
580
581 Ok(GridTemplateAreas::Areas { columns, areas: tokens })
582 }
583}
584
585impl GridTemplateAreas {
586 fn parse_string(string: &str, tokens: &mut Vec<Option<String>>) -> Result<u32, ()> {
587 let mut string = string;
588 let mut column = 0;
589 loop {
590 let rest = string.trim_start_matches(HTML_SPACE_CHARACTERS);
591 if rest.is_empty() {
592 if column == 0 {
594 return Err(());
595 }
596 break;
597 }
598
599 column += 1;
600
601 if rest.starts_with('.') {
602 string = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
603 tokens.push(None);
604 continue;
605 }
606
607 if !rest.starts_with(is_name_code_point) {
608 return Err(());
609 }
610
611 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
612 let token = &rest[..token_len];
613 tokens.push(Some(token.into()));
614 string = &rest[token_len..];
615 }
616
617 Ok(column)
618 }
619}
620
621static HTML_SPACE_CHARACTERS: &'static [char] = &['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}'];
622
623fn is_name_code_point(c: char) -> bool {
624 c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '\u{80}' || c == '_' || c >= '0' && c <= '9' || c == '-'
625}
626
627impl ToCss for GridTemplateAreas {
628 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
629 where
630 W: std::fmt::Write,
631 {
632 match self {
633 GridTemplateAreas::None => dest.write_str("none"),
634 GridTemplateAreas::Areas { areas, .. } => {
635 let mut iter = areas.iter();
636 let mut next = iter.next();
637 let mut first = true;
638 while next.is_some() {
639 if !first && !dest.minify {
640 dest.newline()?;
641 }
642
643 self.write_string(dest, &mut iter, &mut next)?;
644
645 if first {
646 first = false;
647 if !dest.minify {
648 dest.indent_by(21);
650 }
651 }
652 }
653
654 if !dest.minify {
655 dest.dedent_by(21);
656 }
657
658 Ok(())
659 }
660 }
661 }
662}
663
664impl GridTemplateAreas {
665 fn write_string<'a, W>(
666 &self,
667 dest: &mut Printer<W>,
668 iter: &mut std::slice::Iter<'a, Option<String>>,
669 next: &mut Option<&'a Option<String>>,
670 ) -> Result<(), PrinterError>
671 where
672 W: std::fmt::Write,
673 {
674 let columns = match self {
675 GridTemplateAreas::Areas { columns, .. } => *columns,
676 _ => unreachable!(),
677 };
678
679 dest.write_char('"')?;
680
681 let mut last_was_null = false;
682 for i in 0..columns {
683 if let Some(token) = next {
684 if let Some(string) = token {
685 if i > 0 && (!last_was_null || !dest.minify) {
686 dest.write_char(' ')?;
687 }
688 write_ident(string, dest)?;
689 last_was_null = false;
690 } else {
691 if i > 0 && (last_was_null || !dest.minify) {
692 dest.write_char(' ')?;
693 }
694 dest.write_char('.')?;
695 last_was_null = true;
696 }
697 }
698
699 *next = iter.next();
700 }
701
702 dest.write_char('"')
703 }
704}
705
706#[derive(Debug, Clone, PartialEq)]
712#[cfg_attr(feature = "visitor", derive(Visit))]
713#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
714#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
715#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
716pub struct GridTemplate<'i> {
717 #[cfg_attr(feature = "serde", serde(borrow))]
719 pub rows: TrackSizing<'i>,
720 pub columns: TrackSizing<'i>,
722 pub areas: GridTemplateAreas,
724}
725
726impl<'i> Parse<'i> for GridTemplate<'i> {
727 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
728 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
729 input.expect_exhausted()?;
730 return Ok(GridTemplate {
731 rows: TrackSizing::None,
732 columns: TrackSizing::None,
733 areas: GridTemplateAreas::None,
734 });
735 }
736
737 let start = input.state();
738 let mut line_names: Vec<CustomIdentList<'i>> = Vec::new();
739 let mut items = Vec::new();
740 let mut columns = 0;
741 let mut row = 0;
742 let mut tokens = Vec::new();
743
744 loop {
745 if let Ok(first_names) = input.try_parse(parse_line_names) {
746 if let Some(last_names) = line_names.last_mut() {
747 last_names.extend(first_names);
748 } else {
749 line_names.push(first_names);
750 }
751 }
752
753 if let Ok(string) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {
754 let parsed_columns = GridTemplateAreas::parse_string(&string, &mut tokens)
755 .map_err(|()| input.new_custom_error(ParserError::InvalidDeclaration))?;
756
757 if row == 0 {
758 columns = parsed_columns;
759 } else if parsed_columns != columns {
760 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
761 }
762
763 row += 1;
764
765 let track_size = input.try_parse(TrackSize::parse).unwrap_or_default();
766 items.push(TrackListItem::TrackSize(track_size));
767
768 let last_names = input.try_parse(parse_line_names).unwrap_or_default();
769 line_names.push(last_names);
770 } else {
771 break;
772 }
773 }
774
775 if !tokens.is_empty() {
776 if line_names.len() == items.len() {
777 line_names.push(Default::default());
778 }
779
780 let areas = GridTemplateAreas::Areas { columns, areas: tokens };
781 let rows = TrackSizing::TrackList(TrackList { line_names, items });
782 let columns = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
783 let list = TrackList::parse(input)?;
784 if !list.is_explicit() {
785 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
786 }
787 TrackSizing::TrackList(list)
788 } else {
789 TrackSizing::None
790 };
791 Ok(GridTemplate { rows, columns, areas })
792 } else {
793 input.reset(&start);
794 let rows = TrackSizing::parse(input)?;
795 input.expect_delim('/')?;
796 let columns = TrackSizing::parse(input)?;
797 Ok(GridTemplate {
798 rows,
799 columns,
800 areas: GridTemplateAreas::None,
801 })
802 }
803 }
804}
805
806impl ToCss for GridTemplate<'_> {
807 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
808 where
809 W: std::fmt::Write,
810 {
811 self.to_css_with_indent(dest, 15)
812 }
813}
814
815impl GridTemplate<'_> {
816 fn to_css_with_indent<W>(&self, dest: &mut Printer<W>, indent: u8) -> Result<(), PrinterError>
817 where
818 W: std::fmt::Write,
819 {
820 match &self.areas {
821 GridTemplateAreas::None => {
822 if self.rows == TrackSizing::None && self.columns == TrackSizing::None {
823 dest.write_str("none")?;
824 } else {
825 self.rows.to_css(dest)?;
826 dest.delim('/', true)?;
827 self.columns.to_css(dest)?;
828 }
829 }
830 GridTemplateAreas::Areas { areas, .. } => {
831 let track_list = match &self.rows {
832 TrackSizing::TrackList(list) => list,
833 _ => unreachable!(),
834 };
835
836 let mut areas_iter = areas.iter();
837 let mut line_names_iter = track_list.line_names.iter();
838 let mut items_iter = track_list.items.iter();
839
840 let mut next = areas_iter.next();
841 let mut first = true;
842 let mut indented = false;
843 while next.is_some() {
844 macro_rules! newline {
845 () => {
846 if !dest.minify {
847 if !indented {
848 dest.indent_by(indent);
850 indented = true;
851 }
852 dest.newline()?;
853 }
854 };
855 }
856
857 if let Some(line_names) = line_names_iter.next() {
858 if !line_names.is_empty() {
859 if !dest.minify && line_names.len() == 2 {
860 dest.whitespace()?;
861 serialize_line_names(&line_names[0..1], dest)?;
862 newline!();
863 serialize_line_names(&line_names[1..], dest)?;
864 } else {
865 if !first {
866 newline!();
867 }
868 serialize_line_names(line_names, dest)?;
869 }
870 dest.whitespace()?;
871 } else if !first {
872 newline!();
873 }
874 } else if !first {
875 newline!();
876 }
877
878 self.areas.write_string(dest, &mut areas_iter, &mut next)?;
879
880 if let Some(item) = items_iter.next() {
881 if *item != TrackListItem::TrackSize(TrackSize::default()) {
882 dest.whitespace()?;
883 match item {
884 TrackListItem::TrackSize(size) => size.to_css(dest)?,
885 _ => unreachable!(),
886 }
887 }
888 }
889
890 first = false;
891 }
892
893 if let Some(line_names) = line_names_iter.next() {
894 if !line_names.is_empty() {
895 dest.whitespace()?;
896 serialize_line_names(line_names, dest)?;
897 }
898 }
899
900 if let TrackSizing::TrackList(track_list) = &self.columns {
901 dest.newline()?;
902 dest.delim('/', false)?;
903 track_list.to_css(dest)?;
904 }
905
906 if indented {
907 dest.dedent_by(indent);
908 }
909 }
910 }
911
912 Ok(())
913 }
914}
915
916impl<'i> GridTemplate<'i> {
917 #[inline]
918 fn is_valid(rows: &TrackSizing, columns: &TrackSizing, areas: &GridTemplateAreas) -> bool {
919 *areas == GridTemplateAreas::None
922 || (*rows != TrackSizing::None && rows.is_explicit() && columns.is_explicit())
923 }
924}
925
926impl_shorthand! {
927 GridTemplate(GridTemplate<'i>) {
928 rows: [GridTemplateRows],
929 columns: [GridTemplateColumns],
930 areas: [GridTemplateAreas],
931 }
932
933 fn is_valid(shorthand) {
934 GridTemplate::is_valid(&shorthand.rows, &shorthand.columns, &shorthand.areas)
935 }
936}
937
938bitflags! {
939 #[cfg_attr(feature = "visitor", derive(Visit))]
946 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(from = "SerializedGridAutoFlow", into = "SerializedGridAutoFlow"))]
947 #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
948 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
949 pub struct GridAutoFlow: u8 {
950 const Row = 0b00;
952 const Column = 0b01;
954 const Dense = 0b10;
956 }
957}
958
959#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
960#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
961struct SerializedGridAutoFlow {
962 direction: AutoFlowDirection,
964 dense: bool,
966}
967
968impl From<GridAutoFlow> for SerializedGridAutoFlow {
969 fn from(flow: GridAutoFlow) -> Self {
970 Self {
971 direction: if flow.contains(GridAutoFlow::Column) {
972 AutoFlowDirection::Column
973 } else {
974 AutoFlowDirection::Row
975 },
976 dense: flow.contains(GridAutoFlow::Dense),
977 }
978 }
979}
980
981impl From<SerializedGridAutoFlow> for GridAutoFlow {
982 fn from(s: SerializedGridAutoFlow) -> GridAutoFlow {
983 let mut flow = match s.direction {
984 AutoFlowDirection::Row => GridAutoFlow::Row,
985 AutoFlowDirection::Column => GridAutoFlow::Column,
986 };
987 if s.dense {
988 flow |= GridAutoFlow::Dense
989 }
990
991 flow
992 }
993}
994
995#[cfg_attr(
996 feature = "serde",
997 derive(serde::Serialize, serde::Deserialize),
998 serde(rename_all = "lowercase")
999)]
1000#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1001enum AutoFlowDirection {
1002 Row,
1003 Column,
1004}
1005
1006#[cfg(feature = "jsonschema")]
1007#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
1008impl<'a> schemars::JsonSchema for GridAutoFlow {
1009 fn is_referenceable() -> bool {
1010 true
1011 }
1012
1013 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1014 SerializedGridAutoFlow::json_schema(gen)
1015 }
1016
1017 fn schema_name() -> String {
1018 "GridAutoFlow".into()
1019 }
1020}
1021
1022impl Default for GridAutoFlow {
1023 fn default() -> GridAutoFlow {
1024 GridAutoFlow::Row
1025 }
1026}
1027
1028impl GridAutoFlow {
1029 fn direction(self) -> GridAutoFlow {
1030 self & GridAutoFlow::Column
1031 }
1032}
1033
1034impl<'i> Parse<'i> for GridAutoFlow {
1035 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1036 let mut flow = GridAutoFlow::Row;
1037
1038 macro_rules! match_dense {
1039 () => {
1040 if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1041 flow |= GridAutoFlow::Dense;
1042 }
1043 };
1044 }
1045
1046 let location = input.current_source_location();
1047 let ident = input.expect_ident()?;
1048 match_ignore_ascii_case! { &ident,
1049 "row" => {
1050 match_dense!();
1051 },
1052 "column" => {
1053 flow = GridAutoFlow::Column;
1054 match_dense!();
1055 },
1056 "dense" => {
1057 let location = input.current_source_location();
1058 input.try_parse(|input| {
1059 let ident = input.expect_ident()?;
1060 match_ignore_ascii_case! { &ident,
1061 "row" => {},
1062 "column" => {
1063 flow = GridAutoFlow::Column;
1064 },
1065 _ => return Err(location.new_unexpected_token_error(
1066 cssparser::Token::Ident(ident.clone())
1067 ))
1068 }
1069 Ok(())
1070 })?;
1071 flow |= GridAutoFlow::Dense;
1072 },
1073 _ => return Err(location.new_unexpected_token_error(
1074 cssparser::Token::Ident(ident.clone())
1075 ))
1076 }
1077
1078 Ok(flow)
1079 }
1080}
1081
1082impl ToCss for GridAutoFlow {
1083 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1084 where
1085 W: std::fmt::Write,
1086 {
1087 let s = if *self == GridAutoFlow::Row {
1088 "row"
1089 } else if *self == GridAutoFlow::Column {
1090 "column"
1091 } else if *self == GridAutoFlow::Row | GridAutoFlow::Dense {
1092 if dest.minify {
1093 "dense"
1094 } else {
1095 "row dense"
1096 }
1097 } else if *self == GridAutoFlow::Column | GridAutoFlow::Dense {
1098 "column dense"
1099 } else {
1100 unreachable!();
1101 };
1102
1103 dest.write_str(s)
1104 }
1105}
1106
1107#[derive(Debug, Clone, PartialEq)]
1113#[cfg_attr(feature = "visitor", derive(Visit))]
1114#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1115#[cfg_attr(
1116 feature = "serde",
1117 derive(serde::Serialize, serde::Deserialize),
1118 serde(rename_all = "camelCase")
1119)]
1120#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1121pub struct Grid<'i> {
1122 #[cfg_attr(feature = "serde", serde(borrow))]
1124 pub rows: TrackSizing<'i>,
1125 pub columns: TrackSizing<'i>,
1127 pub areas: GridTemplateAreas,
1129 pub auto_rows: TrackSizeList,
1131 pub auto_columns: TrackSizeList,
1133 pub auto_flow: GridAutoFlow,
1135}
1136
1137impl<'i> Parse<'i> for Grid<'i> {
1138 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1139 if let Ok(template) = input.try_parse(GridTemplate::parse) {
1141 Ok(Grid {
1142 rows: template.rows,
1143 columns: template.columns,
1144 areas: template.areas,
1145 auto_rows: TrackSizeList::default(),
1146 auto_columns: TrackSizeList::default(),
1147 auto_flow: GridAutoFlow::default(),
1148 })
1149
1150 } else if let Ok(rows) = input.try_parse(TrackSizing::parse) {
1152 input.expect_delim('/')?;
1153 let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Column)?;
1154 let auto_columns = TrackSizeList::parse(input).unwrap_or_default();
1155 Ok(Grid {
1156 rows,
1157 columns: TrackSizing::None,
1158 areas: GridTemplateAreas::None,
1159 auto_rows: TrackSizeList::default(),
1160 auto_columns,
1161 auto_flow,
1162 })
1163
1164 } else {
1166 let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Row)?;
1167 let auto_rows = input.try_parse(TrackSizeList::parse).unwrap_or_default();
1168 input.expect_delim('/')?;
1169 let columns = TrackSizing::parse(input)?;
1170 Ok(Grid {
1171 rows: TrackSizing::None,
1172 columns,
1173 areas: GridTemplateAreas::None,
1174 auto_rows,
1175 auto_columns: TrackSizeList::default(),
1176 auto_flow,
1177 })
1178 }
1179 }
1180}
1181
1182fn parse_grid_auto_flow<'i, 't>(
1183 input: &mut Parser<'i, 't>,
1184 flow: GridAutoFlow,
1185) -> Result<GridAutoFlow, ParseError<'i, ParserError<'i>>> {
1186 if input.try_parse(|input| input.expect_ident_matching("auto-flow")).is_ok() {
1187 if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1188 Ok(flow | GridAutoFlow::Dense)
1189 } else {
1190 Ok(flow)
1191 }
1192 } else if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1193 input.expect_ident_matching("auto-flow")?;
1194 Ok(flow | GridAutoFlow::Dense)
1195 } else {
1196 Err(input.new_error_for_next_token())
1197 }
1198}
1199
1200impl ToCss for Grid<'_> {
1201 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1202 where
1203 W: std::fmt::Write,
1204 {
1205 let is_auto_initial = self.auto_rows == TrackSizeList::default()
1206 && self.auto_columns == TrackSizeList::default()
1207 && self.auto_flow == GridAutoFlow::default();
1208
1209 if self.areas != GridTemplateAreas::None && self.rows == TrackSizing::None {
1212 dest.write_str("auto-flow")?;
1213 if self.auto_flow.contains(GridAutoFlow::Dense) {
1214 dest.write_str(" dense")?;
1215 }
1216 if self.auto_rows != TrackSizeList::default() {
1217 dest.write_char(' ')?;
1218 self.auto_rows.to_css(dest)?;
1219 }
1220 dest.delim('/', true)?;
1221 self.columns.to_css(dest)?;
1222 return Ok(());
1223 }
1224
1225 if self.areas != GridTemplateAreas::None
1228 && self.columns == TrackSizing::None
1229 && self.auto_flow.direction() == GridAutoFlow::Column
1230 {
1231 self.rows.to_css(dest)?;
1232 dest.delim('/', true)?;
1233 dest.write_str("auto-flow")?;
1234 if self.auto_flow.contains(GridAutoFlow::Dense) {
1235 dest.write_str(" dense")?;
1236 }
1237 if self.auto_columns != TrackSizeList::default() {
1238 dest.write_char(' ')?;
1239 self.auto_columns.to_css(dest)?;
1240 }
1241 return Ok(());
1242 }
1243
1244 if self.areas != GridTemplateAreas::None
1245 || (self.rows != TrackSizing::None && self.columns != TrackSizing::None)
1246 || (self.areas == GridTemplateAreas::None && is_auto_initial)
1247 {
1248 if !is_auto_initial {
1249 unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1250 }
1251 let template = GridTemplate {
1252 rows: self.rows.clone(),
1253 columns: self.columns.clone(),
1254 areas: self.areas.clone(),
1255 };
1256 template.to_css_with_indent(dest, 6)?;
1257 } else if self.auto_flow.direction() == GridAutoFlow::Column {
1258 if self.columns != TrackSizing::None || self.auto_rows != TrackSizeList::default() {
1259 unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1260 }
1261 self.rows.to_css(dest)?;
1262 dest.delim('/', true)?;
1263 dest.write_str("auto-flow")?;
1264 if self.auto_flow.contains(GridAutoFlow::Dense) {
1265 dest.write_str(" dense")?;
1266 }
1267 if self.auto_columns != TrackSizeList::default() {
1268 dest.write_char(' ')?;
1269 self.auto_columns.to_css(dest)?;
1270 }
1271 } else {
1272 if self.rows != TrackSizing::None || self.auto_columns != TrackSizeList::default() {
1273 unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1274 }
1275 dest.write_str("auto-flow")?;
1276 if self.auto_flow.contains(GridAutoFlow::Dense) {
1277 dest.write_str(" dense")?;
1278 }
1279 if self.auto_rows != TrackSizeList::default() {
1280 dest.write_char(' ')?;
1281 self.auto_rows.to_css(dest)?;
1282 }
1283 dest.delim('/', true)?;
1284 self.columns.to_css(dest)?;
1285 }
1286
1287 Ok(())
1288 }
1289}
1290
1291impl<'i> Grid<'i> {
1292 #[inline]
1293 fn is_valid(
1294 rows: &TrackSizing,
1295 columns: &TrackSizing,
1296 areas: &GridTemplateAreas,
1297 auto_rows: &TrackSizeList,
1298 auto_columns: &TrackSizeList,
1299 auto_flow: &GridAutoFlow,
1300 ) -> bool {
1301 let default_track_size_list = TrackSizeList::default();
1302
1303 if *areas != GridTemplateAreas::None && *rows == TrackSizing::None {
1307 return auto_flow.direction() == GridAutoFlow::Row;
1308 }
1309
1310 let is_template = GridTemplate::is_valid(rows, columns, areas);
1313 let is_explicit = *auto_rows == default_track_size_list
1314 && *auto_columns == default_track_size_list
1315 && *auto_flow == GridAutoFlow::default();
1316 let is_auto_rows = auto_flow.direction() == GridAutoFlow::Row
1319 && *rows == TrackSizing::None
1320 && *auto_columns == default_track_size_list;
1321 let is_auto_columns = auto_flow.direction() == GridAutoFlow::Column
1324 && *columns == TrackSizing::None
1325 && *auto_rows == default_track_size_list;
1326
1327 (is_template && is_explicit) || is_auto_rows || is_auto_columns
1328 }
1329}
1330
1331impl_shorthand! {
1333 Grid(Grid<'i>) {
1334 rows: [GridTemplateRows],
1335 columns: [GridTemplateColumns],
1336 areas: [GridTemplateAreas],
1337 auto_rows: [GridAutoRows],
1338 auto_columns: [GridAutoColumns],
1339 auto_flow: [GridAutoFlow],
1340 }
1341
1342 fn is_valid(grid) {
1343 Grid::is_valid(&grid.rows, &grid.columns, &grid.areas, &grid.auto_rows, &grid.auto_columns, &grid.auto_flow)
1344 }
1345}
1346
1347#[derive(Debug, Clone, PartialEq)]
1350#[cfg_attr(feature = "visitor", derive(Visit))]
1351#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1352#[cfg_attr(
1353 feature = "serde",
1354 derive(serde::Serialize, serde::Deserialize),
1355 serde(tag = "type", rename_all = "kebab-case")
1356)]
1357#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1358pub enum GridLine<'i> {
1359 Auto,
1361 Area {
1363 name: CustomIdent<'i>,
1365 },
1366 Line {
1368 index: CSSInteger,
1370 #[cfg_attr(feature = "serde", serde(borrow))]
1372 name: Option<CustomIdent<'i>>,
1373 },
1374 Span {
1376 index: CSSInteger,
1378 name: Option<CustomIdent<'i>>,
1380 },
1381}
1382
1383impl<'i> Parse<'i> for GridLine<'i> {
1384 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1385 if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
1386 return Ok(GridLine::Auto);
1387 }
1388
1389 if input.try_parse(|input| input.expect_ident_matching("span")).is_ok() {
1390 let (index, name) = if let Ok(line_number) = input.try_parse(CSSInteger::parse) {
1392 let ident = input.try_parse(CustomIdent::parse).ok();
1393 (line_number, ident)
1394 } else if let Ok(ident) = input.try_parse(CustomIdent::parse) {
1395 let line_number = input.try_parse(CSSInteger::parse).unwrap_or(1);
1396 (line_number, Some(ident))
1397 } else {
1398 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1399 };
1400
1401 if index == 0 {
1402 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1403 }
1404
1405 return Ok(GridLine::Span { index, name });
1406 }
1407
1408 if let Ok(index) = input.try_parse(CSSInteger::parse) {
1409 if index == 0 {
1410 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1411 }
1412 let name = input.try_parse(CustomIdent::parse).ok();
1413 return Ok(GridLine::Line { index, name });
1414 }
1415
1416 let name = CustomIdent::parse(input)?;
1417 if let Ok(index) = input.try_parse(CSSInteger::parse) {
1418 if index == 0 {
1419 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1420 }
1421 return Ok(GridLine::Line {
1422 index,
1423 name: Some(name),
1424 });
1425 }
1426
1427 Ok(GridLine::Area { name })
1428 }
1429}
1430
1431impl ToCss for GridLine<'_> {
1432 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1433 where
1434 W: std::fmt::Write,
1435 {
1436 match self {
1437 GridLine::Auto => dest.write_str("auto"),
1438 GridLine::Area { name } => write_ident(&name.0, dest),
1439 GridLine::Line { index, name } => {
1440 index.to_css(dest)?;
1441 if let Some(id) = name {
1442 dest.write_char(' ')?;
1443 write_ident(&id.0, dest)?;
1444 }
1445 Ok(())
1446 }
1447 GridLine::Span { index, name } => {
1448 dest.write_str("span ")?;
1449 if *index != 1 || name.is_none() {
1450 index.to_css(dest)?;
1451 if name.is_some() {
1452 dest.write_char(' ')?;
1453 }
1454 }
1455
1456 if let Some(id) = name {
1457 write_ident(&id.0, dest)?;
1458 }
1459 Ok(())
1460 }
1461 }
1462 }
1463}
1464
1465impl<'i> GridLine<'i> {
1466 fn default_end_value(&self) -> GridLine<'i> {
1467 if matches!(self, GridLine::Area { .. }) {
1468 self.clone()
1469 } else {
1470 GridLine::Auto
1471 }
1472 }
1473
1474 fn can_omit_end(&self, end: &GridLine) -> bool {
1475 if let GridLine::Area { name: start_id } = &self {
1476 matches!(end, GridLine::Area { name: end_id } if end_id == start_id)
1477 } else if matches!(end, GridLine::Auto) {
1478 true
1479 } else {
1480 false
1481 }
1482 }
1483}
1484
1485macro_rules! impl_grid_placement {
1486 ($name: ident) => {
1487 impl<'i> Parse<'i> for $name<'i> {
1488 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1489 let start = GridLine::parse(input)?;
1490 let end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1491 GridLine::parse(input)?
1492 } else {
1493 start.default_end_value()
1494 };
1495
1496 Ok($name { start, end })
1497 }
1498 }
1499
1500 impl ToCss for $name<'_> {
1501 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1502 where
1503 W: std::fmt::Write,
1504 {
1505 self.start.to_css(dest)?;
1506
1507 if !self.start.can_omit_end(&self.end) {
1508 dest.delim('/', true)?;
1509 self.end.to_css(dest)?;
1510 }
1511 Ok(())
1512 }
1513 }
1514 };
1515}
1516
1517define_shorthand! {
1518 pub struct GridRow<'i> {
1521 #[cfg_attr(feature = "serde", serde(borrow))]
1523 start: GridRowStart(GridLine<'i>),
1524 end: GridRowEnd(GridLine<'i>),
1526 }
1527}
1528
1529define_shorthand! {
1530 pub struct GridColumn<'i> {
1533 #[cfg_attr(feature = "serde", serde(borrow))]
1535 start: GridColumnStart(GridLine<'i>),
1536 end: GridColumnEnd(GridLine<'i>),
1538 }
1539}
1540
1541impl_grid_placement!(GridRow);
1542impl_grid_placement!(GridColumn);
1543
1544define_shorthand! {
1545 pub struct GridArea<'i> {
1548 #[cfg_attr(feature = "serde", serde(borrow))]
1550 row_start: GridRowStart(GridLine<'i>),
1551 column_start: GridColumnStart(GridLine<'i>),
1553 row_end: GridRowEnd(GridLine<'i>),
1555 column_end: GridColumnEnd(GridLine<'i>),
1557 }
1558}
1559
1560impl<'i> Parse<'i> for GridArea<'i> {
1561 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1562 let row_start = GridLine::parse(input)?;
1563 let column_start = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1564 GridLine::parse(input)?
1565 } else {
1566 let opposite = row_start.default_end_value();
1567 return Ok(GridArea {
1568 row_start,
1569 column_start: opposite.clone(),
1570 row_end: opposite.clone(),
1571 column_end: opposite,
1572 });
1573 };
1574
1575 let row_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1576 GridLine::parse(input)?
1577 } else {
1578 let row_end = row_start.default_end_value();
1579 let column_end = column_start.default_end_value();
1580 return Ok(GridArea {
1581 row_start,
1582 column_start,
1583 row_end,
1584 column_end,
1585 });
1586 };
1587
1588 let column_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1589 GridLine::parse(input)?
1590 } else {
1591 let column_end = column_start.default_end_value();
1592 return Ok(GridArea {
1593 row_start,
1594 column_start,
1595 row_end,
1596 column_end,
1597 });
1598 };
1599
1600 Ok(GridArea {
1601 row_start,
1602 column_start,
1603 row_end,
1604 column_end,
1605 })
1606 }
1607}
1608
1609impl ToCss for GridArea<'_> {
1610 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1611 where
1612 W: std::fmt::Write,
1613 {
1614 self.row_start.to_css(dest)?;
1615
1616 let can_omit_column_end = self.column_start.can_omit_end(&self.column_end);
1617 let can_omit_row_end = can_omit_column_end && self.row_start.can_omit_end(&self.row_end);
1618 let can_omit_column_start = can_omit_row_end && self.row_start.can_omit_end(&self.column_start);
1619
1620 if !can_omit_column_start {
1621 dest.delim('/', true)?;
1622 self.column_start.to_css(dest)?;
1623 }
1624
1625 if !can_omit_row_end {
1626 dest.delim('/', true)?;
1627 self.row_end.to_css(dest)?;
1628 }
1629
1630 if !can_omit_column_end {
1631 dest.delim('/', true)?;
1632 self.column_end.to_css(dest)?;
1633 }
1634
1635 Ok(())
1636 }
1637}
1638
1639#[derive(Default, Debug)]
1640pub(crate) struct GridHandler<'i> {
1641 rows: Option<TrackSizing<'i>>,
1642 columns: Option<TrackSizing<'i>>,
1643 areas: Option<GridTemplateAreas>,
1644 auto_rows: Option<TrackSizeList>,
1645 auto_columns: Option<TrackSizeList>,
1646 auto_flow: Option<GridAutoFlow>,
1647 row_start: Option<GridLine<'i>>,
1648 column_start: Option<GridLine<'i>>,
1649 row_end: Option<GridLine<'i>>,
1650 column_end: Option<GridLine<'i>>,
1651 has_any: bool,
1652}
1653
1654impl<'i> PropertyHandler<'i> for GridHandler<'i> {
1655 fn handle_property(
1656 &mut self,
1657 property: &Property<'i>,
1658 dest: &mut DeclarationList<'i>,
1659 context: &mut PropertyHandlerContext<'i, '_>,
1660 ) -> bool {
1661 use Property::*;
1662
1663 match property {
1664 GridTemplateColumns(columns) => self.columns = Some(columns.clone()),
1665 GridTemplateRows(rows) => self.rows = Some(rows.clone()),
1666 GridTemplateAreas(areas) => self.areas = Some(areas.clone()),
1667 GridAutoColumns(auto_columns) => self.auto_columns = Some(auto_columns.clone()),
1668 GridAutoRows(auto_rows) => self.auto_rows = Some(auto_rows.clone()),
1669 GridAutoFlow(auto_flow) => self.auto_flow = Some(auto_flow.clone()),
1670 GridTemplate(template) => {
1671 self.rows = Some(template.rows.clone());
1672 self.columns = Some(template.columns.clone());
1673 self.areas = Some(template.areas.clone());
1674 }
1675 Grid(grid) => {
1676 self.rows = Some(grid.rows.clone());
1677 self.columns = Some(grid.columns.clone());
1678 self.areas = Some(grid.areas.clone());
1679 self.auto_rows = Some(grid.auto_rows.clone());
1680 self.auto_columns = Some(grid.auto_columns.clone());
1681 self.auto_flow = Some(grid.auto_flow.clone());
1682 }
1683 GridRowStart(row_start) => self.row_start = Some(row_start.clone()),
1684 GridRowEnd(row_end) => self.row_end = Some(row_end.clone()),
1685 GridColumnStart(column_start) => self.column_start = Some(column_start.clone()),
1686 GridColumnEnd(column_end) => self.column_end = Some(column_end.clone()),
1687 GridRow(row) => {
1688 self.row_start = Some(row.start.clone());
1689 self.row_end = Some(row.end.clone());
1690 }
1691 GridColumn(column) => {
1692 self.column_start = Some(column.start.clone());
1693 self.column_end = Some(column.end.clone());
1694 }
1695 GridArea(area) => {
1696 self.row_start = Some(area.row_start.clone());
1697 self.row_end = Some(area.row_end.clone());
1698 self.column_start = Some(area.column_start.clone());
1699 self.column_end = Some(area.column_end.clone());
1700 }
1701 Unparsed(val) if is_grid_property(&val.property_id) => {
1702 self.finalize(dest, context);
1703 dest.push(property.clone());
1704 }
1705 _ => return false,
1706 }
1707
1708 self.has_any = true;
1709 true
1710 }
1711
1712 fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {
1713 if !self.has_any {
1714 return;
1715 }
1716
1717 self.has_any = false;
1718
1719 let mut rows = std::mem::take(&mut self.rows);
1720 let mut columns = std::mem::take(&mut self.columns);
1721 let mut areas = std::mem::take(&mut self.areas);
1722 let mut auto_rows = std::mem::take(&mut self.auto_rows);
1723 let mut auto_columns = std::mem::take(&mut self.auto_columns);
1724 let mut auto_flow = std::mem::take(&mut self.auto_flow);
1725 let mut row_start = std::mem::take(&mut self.row_start);
1726 let mut row_end = std::mem::take(&mut self.row_end);
1727 let mut column_start = std::mem::take(&mut self.column_start);
1728 let mut column_end = std::mem::take(&mut self.column_end);
1729
1730 if let (Some(rows_val), Some(columns_val), Some(areas_val)) = (&rows, &columns, &areas) {
1731 let mut has_template = true;
1732 if let (Some(auto_rows_val), Some(auto_columns_val), Some(auto_flow_val)) =
1733 (&auto_rows, &auto_columns, &auto_flow)
1734 {
1735 if Grid::is_valid(
1738 rows_val,
1739 columns_val,
1740 areas_val,
1741 auto_rows_val,
1742 auto_columns_val,
1743 auto_flow_val,
1744 ) {
1745 let needs_separate_areas = *areas_val != GridTemplateAreas::None
1746 && ((*rows_val == TrackSizing::None && auto_flow_val.direction() == GridAutoFlow::Row)
1747 || (*columns_val == TrackSizing::None && auto_flow_val.direction() == GridAutoFlow::Column));
1748
1749 let (areas_for_grid, areas_for_output) = if needs_separate_areas {
1753 (areas_val.clone(), Some(areas_val.clone()))
1755 } else {
1756 (GridHandler::pad_grid_template_areas(rows_val, areas_val.clone()), None)
1757 };
1758
1759 dest.push(Property::Grid(Grid {
1760 rows: rows_val.clone(),
1761 columns: columns_val.clone(),
1762 areas: areas_for_grid,
1763 auto_rows: auto_rows_val.clone(),
1764 auto_columns: auto_columns_val.clone(),
1765 auto_flow: auto_flow_val.clone(),
1766 }));
1767
1768 has_template = false;
1769 auto_rows = None;
1770 auto_columns = None;
1771 auto_flow = None;
1772
1773 if let Some(areas) = areas_for_output {
1776 dest.push(Property::GridTemplateAreas(areas));
1777 }
1778 }
1779 }
1780
1781 if has_template && GridTemplate::is_valid(rows_val, columns_val, areas_val) {
1784 let padded_areas = GridHandler::pad_grid_template_areas(rows_val, areas_val.clone());
1786
1787 dest.push(Property::GridTemplate(GridTemplate {
1788 rows: rows_val.clone(),
1789 columns: columns_val.clone(),
1790 areas: padded_areas,
1791 }));
1792
1793 has_template = false;
1794 }
1795
1796 if !has_template {
1797 rows = None;
1798 columns = None;
1799 areas = None;
1800 }
1801 }
1802
1803 if row_start.is_some() && row_end.is_some() && column_start.is_some() && column_end.is_some() {
1804 dest.push(Property::GridArea(GridArea {
1805 row_start: std::mem::take(&mut row_start).unwrap(),
1806 row_end: std::mem::take(&mut row_end).unwrap(),
1807 column_start: std::mem::take(&mut column_start).unwrap(),
1808 column_end: std::mem::take(&mut column_end).unwrap(),
1809 }))
1810 } else {
1811 if row_start.is_some() && row_end.is_some() {
1812 dest.push(Property::GridRow(GridRow {
1813 start: std::mem::take(&mut row_start).unwrap(),
1814 end: std::mem::take(&mut row_end).unwrap(),
1815 }))
1816 }
1817
1818 if column_start.is_some() && column_end.is_some() {
1819 dest.push(Property::GridColumn(GridColumn {
1820 start: std::mem::take(&mut column_start).unwrap(),
1821 end: std::mem::take(&mut column_end).unwrap(),
1822 }))
1823 }
1824 }
1825
1826 macro_rules! single_property {
1827 ($prop: ident, $key: ident) => {
1828 if let Some(val) = $key {
1829 dest.push(Property::$prop(val))
1830 }
1831 };
1832 }
1833
1834 single_property!(GridTemplateRows, rows);
1835 single_property!(GridTemplateColumns, columns);
1836 single_property!(GridTemplateAreas, areas);
1837 single_property!(GridAutoRows, auto_rows);
1838 single_property!(GridAutoColumns, auto_columns);
1839 single_property!(GridAutoFlow, auto_flow);
1840 single_property!(GridRowStart, row_start);
1841 single_property!(GridRowEnd, row_end);
1842 single_property!(GridColumnStart, column_start);
1843 single_property!(GridColumnEnd, column_end);
1844 }
1845}
1846
1847impl GridHandler<'_> {
1854 fn pad_grid_template_areas(rows: &TrackSizing, areas: GridTemplateAreas) -> GridTemplateAreas {
1855 match (rows, areas) {
1856 (TrackSizing::TrackList(rows_list), GridTemplateAreas::Areas { columns, areas }) => {
1857 let rows_count = rows_list.items.len();
1858 let areas_rows_count = areas.len() / columns as usize;
1859 if areas_rows_count < rows_count {
1860 let mut padded_areas = areas;
1861 for _ in areas_rows_count..rows_count {
1863 for _ in 0..columns {
1864 padded_areas.push(None);
1865 }
1866 }
1867 GridTemplateAreas::Areas {
1868 columns,
1869 areas: padded_areas,
1870 }
1871 } else {
1872 GridTemplateAreas::Areas { columns, areas }
1873 }
1874 }
1875 (_, areas) => areas,
1876 }
1877 }
1878}
1879
1880#[inline]
1881fn is_grid_property(property_id: &PropertyId) -> bool {
1882 match property_id {
1883 PropertyId::GridTemplateColumns
1884 | PropertyId::GridTemplateRows
1885 | PropertyId::GridTemplateAreas
1886 | PropertyId::GridAutoColumns
1887 | PropertyId::GridAutoRows
1888 | PropertyId::GridAutoFlow
1889 | PropertyId::GridTemplate
1890 | PropertyId::Grid
1891 | PropertyId::GridRowStart
1892 | PropertyId::GridRowEnd
1893 | PropertyId::GridColumnStart
1894 | PropertyId::GridColumnEnd
1895 | PropertyId::GridRow
1896 | PropertyId::GridColumn
1897 | PropertyId::GridArea => true,
1898 _ => false,
1899 }
1900}