Skip to main content

azul_css/props/layout/
grid.rs

1//! CSS properties for CSS Grid layout.
2
3use alloc::{
4    boxed::Box,
5    string::{String, ToString},
6    vec::Vec,
7};
8
9use crate::{
10    corety::AzString,
11    codegen::format::FormatAsRustCode,
12    impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_eq, impl_vec_hash, impl_vec_mut,
13    impl_vec_ord, impl_vec_partialeq, impl_vec_partialord,
14    props::{basic::pixel::PixelValue, formatter::PrintAsCssValue},
15};
16
17// --- grid-template-columns / grid-template-rows ---
18
19/// Wrapper for minmax(min, max) to satisfy repr(C) (enum variants can only have 1 field)
20#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[repr(C)]
22pub struct GridMinMax {
23    pub min: Box<GridTrackSizing>,
24    pub max: Box<GridTrackSizing>,
25}
26
27impl core::fmt::Debug for GridMinMax {
28    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29        write!(
30            f,
31            "minmax({}, {})",
32            self.min.print_as_css_value(),
33            self.max.print_as_css_value()
34        )
35    }
36}
37
38/// Represents a single track sizing function for grid
39#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[repr(C, u8)]
41#[derive(Default)]
42pub enum GridTrackSizing {
43    /// Fixed pixel/percent size
44    Fixed(PixelValue),
45    /// fr units (value multiplied by `FR_SCALING_FACTOR` to allow fractional
46    /// values while satisfying Eq/Ord/Hash — e.g. `1fr` = `Fr(100)`, `0.5fr` = `Fr(50)`)
47    Fr(i32),
48    /// min-content
49    MinContent,
50    /// max-content
51    MaxContent,
52    /// auto
53    #[default]
54    Auto,
55    /// minmax(min, max) - uses `GridMinMax` which contains Box<GridTrackSizing> for each bound
56    MinMax(GridMinMax),
57    /// fit-content(size)
58    FitContent(PixelValue),
59}
60
61impl_option!(
62    GridTrackSizing,
63    OptionGridTrackSizing,
64    copy = false,
65    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
66);
67
68impl core::fmt::Debug for GridTrackSizing {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        write!(f, "{}", self.print_as_css_value())
71    }
72}
73
74
75impl PrintAsCssValue for GridTrackSizing {
76    fn print_as_css_value(&self) -> String {
77        match self {
78            Self::Fixed(px) => px.print_as_css_value(),
79            Self::Fr(f) => format!("{f}fr"),
80            Self::MinContent => "min-content".to_string(),
81            Self::MaxContent => "max-content".to_string(),
82            Self::Auto => "auto".to_string(),
83            Self::MinMax(minmax) => {
84                format!(
85                    "minmax({}, {})",
86                    minmax.min.print_as_css_value(),
87                    minmax.max.print_as_css_value()
88                )
89            }
90            Self::FitContent(size) => {
91                format!("fit-content({})", size.print_as_css_value())
92            }
93        }
94    }
95}
96
97// C-compatible Vec for GridTrackSizing
98impl_vec!(GridTrackSizing, GridTrackSizingVec, GridTrackSizingVecDestructor, GridTrackSizingVecDestructorType, GridTrackSizingVecSlice, OptionGridTrackSizing);
99impl_vec_clone!(
100    GridTrackSizing,
101    GridTrackSizingVec,
102    GridTrackSizingVecDestructor
103);
104impl_vec_debug!(GridTrackSizing, GridTrackSizingVec);
105impl_vec_partialeq!(GridTrackSizing, GridTrackSizingVec);
106impl_vec_eq!(GridTrackSizing, GridTrackSizingVec);
107impl_vec_partialord!(GridTrackSizing, GridTrackSizingVec);
108impl_vec_ord!(GridTrackSizing, GridTrackSizingVec);
109impl_vec_hash!(GridTrackSizing, GridTrackSizingVec);
110impl_vec_mut!(GridTrackSizing, GridTrackSizingVec);
111
112/// Represents `grid-template-columns` or `grid-template-rows`
113#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
114#[repr(C)]
115pub struct GridTemplate {
116    pub tracks: GridTrackSizingVec,
117}
118
119impl core::fmt::Debug for GridTemplate {
120    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121        write!(f, "{}", self.print_as_css_value())
122    }
123}
124
125impl Default for GridTemplate {
126    fn default() -> Self {
127        Self {
128            tracks: GridTrackSizingVec::from_vec(Vec::new()),
129        }
130    }
131}
132
133impl PrintAsCssValue for GridTemplate {
134    fn print_as_css_value(&self) -> String {
135        let tracks_slice = self.tracks.as_ref();
136        if tracks_slice.is_empty() {
137            "none".to_string()
138        } else {
139            tracks_slice
140                .iter()
141                .map(PrintAsCssValue::print_as_css_value)
142                .collect::<Vec<_>>()
143                .join(" ")
144        }
145    }
146}
147
148// --- grid-auto-columns / grid-auto-rows ---
149
150/// Represents `grid-auto-columns` or `grid-auto-rows`
151/// Structurally identical to `GridTemplate` but semantically different
152#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
153#[repr(C)]
154pub struct GridAutoTracks {
155    pub tracks: GridTrackSizingVec,
156}
157
158impl core::fmt::Debug for GridAutoTracks {
159    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
160        write!(f, "{}", self.print_as_css_value())
161    }
162}
163
164impl Default for GridAutoTracks {
165    fn default() -> Self {
166        Self {
167            tracks: GridTrackSizingVec::from_vec(Vec::new()),
168        }
169    }
170}
171
172impl PrintAsCssValue for GridAutoTracks {
173    fn print_as_css_value(&self) -> String {
174        let tracks_slice = self.tracks.as_ref();
175        if tracks_slice.is_empty() {
176            "auto".to_string()
177        } else {
178            tracks_slice
179                .iter()
180                .map(PrintAsCssValue::print_as_css_value)
181                .collect::<Vec<_>>()
182                .join(" ")
183        }
184    }
185}
186
187impl From<GridTemplate> for GridAutoTracks {
188    fn from(template: GridTemplate) -> Self {
189        Self {
190            tracks: template.tracks,
191        }
192    }
193}
194
195// --- grid-row / grid-column (grid line placement) ---
196
197/// Named grid line with optional span count (FFI-safe wrapper)
198#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
199#[repr(C)]
200pub struct NamedGridLine {
201    pub grid_line_name: AzString,
202    /// Span count, 0 means no span specified
203    pub span_count: i32,
204}
205
206impl NamedGridLine {
207    #[must_use] pub fn create(name: AzString, span: Option<i32>) -> Self {
208        Self {
209            grid_line_name: name,
210            span_count: span.unwrap_or(0),
211        }
212    }
213
214    #[must_use] pub const fn span(&self) -> Option<i32> {
215        if self.span_count == 0 {
216            None
217        } else {
218            Some(self.span_count)
219        }
220    }
221}
222#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
223/// Represents a grid line position (start or end)
224#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
225#[repr(C, u8)]
226#[derive(Default)]
227pub enum GridLine {
228    /// auto
229    #[default]
230    Auto,
231    /// Line number (1-based, negative for counting from end)
232    Line(i32),
233    /// Named line with optional span count
234    Named(NamedGridLine),
235    /// span N
236    Span(i32),
237}
238
239impl core::fmt::Debug for GridLine {
240    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241        write!(f, "{}", self.print_as_css_value())
242    }
243}
244
245
246impl PrintAsCssValue for GridLine {
247    fn print_as_css_value(&self) -> String {
248        match self {
249            Self::Auto => "auto".to_string(),
250            Self::Line(n) => n.to_string(),
251            Self::Named(named) => {
252                if named.span_count == 0 {
253                    named.grid_line_name.as_str().to_string()
254                } else {
255                    format!("{} {}", named.grid_line_name.as_str(), named.span_count)
256                }
257            }
258            Self::Span(n) => format!("span {n}"),
259        }
260    }
261}
262
263/// Represents `grid-row` or `grid-column` (start / end)
264#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
265#[repr(C)]
266pub struct GridPlacement {
267    pub grid_start: GridLine,
268    pub grid_end: GridLine,
269}
270
271impl core::fmt::Debug for GridPlacement {
272    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273        write!(f, "{}", self.print_as_css_value())
274    }
275}
276
277impl Default for GridPlacement {
278    fn default() -> Self {
279        Self {
280            grid_start: GridLine::Auto,
281            grid_end: GridLine::Auto,
282        }
283    }
284}
285
286impl PrintAsCssValue for GridPlacement {
287    fn print_as_css_value(&self) -> String {
288        if self.grid_end == GridLine::Auto {
289            self.grid_start.print_as_css_value()
290        } else {
291            format!(
292                "{} / {}",
293                self.grid_start.print_as_css_value(),
294                self.grid_end.print_as_css_value()
295            )
296        }
297    }
298}
299
300#[cfg(feature = "parser")]
301#[derive(Clone, PartialEq, Eq)]
302pub enum GridParseError<'a> {
303    InvalidValue(&'a str),
304}
305
306#[cfg(feature = "parser")]
307impl_debug_as_display!(GridParseError<'a>);
308#[cfg(feature = "parser")]
309impl_display! { GridParseError<'a>, {
310    InvalidValue(e) => format!("Invalid grid value: \"{}\"", e),
311}}
312
313#[cfg(feature = "parser")]
314#[derive(Debug, Clone, PartialEq, Eq)]
315#[repr(C, u8)]
316pub enum GridParseErrorOwned {
317    InvalidValue(AzString),
318}
319
320#[cfg(feature = "parser")]
321impl GridParseError<'_> {
322    #[must_use] pub fn to_contained(&self) -> GridParseErrorOwned {
323        match self {
324            GridParseError::InvalidValue(s) => GridParseErrorOwned::InvalidValue((*s).to_string().into()),
325        }
326    }
327}
328
329#[cfg(feature = "parser")]
330impl GridParseErrorOwned {
331    #[must_use] pub fn to_shared(&self) -> GridParseError<'_> {
332        match self {
333            Self::InvalidValue(s) => GridParseError::InvalidValue(s.as_str()),
334        }
335    }
336}
337
338#[cfg(feature = "parser")]
339fn split_respecting_parens(input: &str) -> Result<Vec<String>, ()> {
340    let mut parts = Vec::new();
341    let mut current = String::new();
342    let mut paren_depth: i32 = 0;
343
344    for ch in input.chars() {
345        match ch {
346            '(' => { paren_depth += 1; current.push(ch); }
347            ')' => { paren_depth -= 1; if paren_depth < 0 { return Err(()); } current.push(ch); }
348            ' ' if paren_depth == 0 => {
349                if !current.trim().is_empty() {
350                    parts.push(current.trim().to_string());
351                    current.clear();
352                }
353            }
354            _ => current.push(ch),
355        }
356    }
357    if !current.trim().is_empty() {
358        parts.push(current.trim().to_string());
359    }
360    Ok(parts)
361}
362
363#[cfg(feature = "parser")]
364/// # Errors
365///
366/// Returns an error if `input` is not a valid CSS `grid-template` value.
367pub fn parse_grid_template(input: &str) -> Result<GridTemplate, GridParseError<'_>> {
368    use crate::props::basic::pixel::parse_pixel_value;
369
370    let input = input.trim();
371
372    if input == "none" {
373        return Ok(GridTemplate::default());
374    }
375
376    let parts = split_respecting_parens(input)
377        .map_err(|()| GridParseError::InvalidValue(input))?;
378
379    let mut tracks = Vec::new();
380    for part in &parts {
381        parse_grid_track_or_repeat(part, &mut tracks)
382            .map_err(|()| GridParseError::InvalidValue(input))?;
383    }
384
385    Ok(GridTemplate {
386        tracks: GridTrackSizingVec::from_vec(tracks),
387    })
388}
389
390/// Parse a single grid track token, which may be `repeat(N, track)` or a plain track.
391/// For `repeat(N, track_list)`, the tracks are expanded inline.
392#[cfg(feature = "parser")]
393fn parse_grid_track_or_repeat(input: &str, tracks: &mut Vec<GridTrackSizing>) -> Result<(), ()> {
394    // Maximum repeat count accepted in `repeat(N, …)` to bound expansion.
395    const MAX_GRID_REPEAT_COUNT: usize = 10_000;
396    let input = input.trim();
397
398    // Handle repeat(N, track_list)
399    if input.starts_with("repeat(") && input.ends_with(')') {
400        let content = &input[7..input.len() - 1];
401        // Find the first comma that separates the count from the track list
402        let comma_pos = content.find(',').ok_or(())?;
403        let count_str = content[..comma_pos].trim();
404        let track_list_str = content[comma_pos + 1..].trim();
405
406        let count: usize = count_str.parse().map_err(|_| ())?;
407        if count == 0 || count > MAX_GRID_REPEAT_COUNT {
408            return Err(());
409        }
410
411        // Parse the track list (may contain multiple space-separated tracks)
412        let parts = split_respecting_parens(track_list_str)?;
413        let repeat_tracks: Vec<GridTrackSizing> = parts
414            .iter()
415            .map(|p| parse_grid_track_owned(p))
416            .collect::<Result<Vec<_>, _>>()?;
417
418        // Expand: repeat N times
419        for _ in 0..count {
420            tracks.extend(repeat_tracks.iter().cloned());
421        }
422        return Ok(());
423    }
424
425    // Plain single track
426    tracks.push(parse_grid_track_owned(input)?);
427    Ok(())
428}
429
430#[cfg(feature = "parser")]
431fn parse_grid_track_owned(input: &str) -> Result<GridTrackSizing, ()> {
432    use crate::props::basic::pixel::parse_pixel_value;
433
434    let input = input.trim();
435
436    if input == "auto" {
437        return Ok(GridTrackSizing::Auto);
438    }
439
440    if input == "min-content" {
441        return Ok(GridTrackSizing::MinContent);
442    }
443
444    if input == "max-content" {
445        return Ok(GridTrackSizing::MaxContent);
446    }
447
448    if let Some(num_str) = input.strip_suffix("fr") {
449        /// Fr values are stored as integers scaled by this factor (e.g. `1fr` = 100, `0.5fr` = 50).
450        const FR_SCALING_FACTOR: f32 = 100.0;
451        let num_str = num_str.trim();
452        if let Ok(num) = num_str.parse::<f32>() {
453            let scaled = num * FR_SCALING_FACTOR;
454            if scaled.is_nan() || scaled < crate::cast::i32_to_f32(i32::MIN) || scaled > crate::cast::i32_to_f32(i32::MAX) {
455                return Err(());
456            }
457            return Ok(GridTrackSizing::Fr(crate::cast::f32_to_i32(scaled)));
458        }
459        return Err(());
460    }
461
462    if input.starts_with("minmax(") && input.ends_with(')') {
463        let content = &input[7..input.len() - 1];
464        let parts: Vec<&str> = content.split(',').collect();
465        if parts.len() == 2 {
466            let min = parse_grid_track_owned(parts[0].trim())?;
467            let max = parse_grid_track_owned(parts[1].trim())?;
468            return Ok(GridTrackSizing::MinMax(GridMinMax {
469                min: Box::new(min),
470                max: Box::new(max),
471            }));
472        }
473        return Err(());
474    }
475
476    if input.starts_with("fit-content(") && input.ends_with(')') {
477        let size_str = &input[12..input.len() - 1].trim();
478        if let Ok(size) = parse_pixel_value(size_str) {
479            return Ok(GridTrackSizing::FitContent(size));
480        }
481        return Err(());
482    }
483
484    // Try to parse as pixel value
485    if let Ok(px) = parse_pixel_value(input) {
486        return Ok(GridTrackSizing::Fixed(px));
487    }
488
489    Err(())
490}
491
492#[cfg(feature = "parser")]
493/// # Errors
494///
495/// Returns an error if `input` is not a valid CSS `grid-placement` value.
496pub fn parse_grid_placement(input: &str) -> Result<GridPlacement, GridParseError<'_>> {
497    let input = input.trim();
498
499    if input == "auto" {
500        return Ok(GridPlacement::default());
501    }
502
503    // Split by "/"
504    let parts: Vec<&str> = input.split('/').map(str::trim).collect();
505
506    let grid_start =
507        parse_grid_line_owned(parts[0]).map_err(|()| GridParseError::InvalidValue(input))?;
508    let grid_end = if parts.len() > 1 {
509        parse_grid_line_owned(parts[1]).map_err(|()| GridParseError::InvalidValue(input))?
510    } else {
511        GridLine::Auto
512    };
513
514    Ok(GridPlacement {
515        grid_start,
516        grid_end,
517    })
518}
519
520// --- grid-auto-flow ---
521
522/// Represents the `grid-auto-flow` property
523#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
524#[repr(C)]
525#[derive(Default)]
526pub enum LayoutGridAutoFlow {
527    #[default]
528    Row,
529    Column,
530    RowDense,
531    ColumnDense,
532}
533
534
535impl PrintAsCssValue for LayoutGridAutoFlow {
536    fn print_as_css_value(&self) -> alloc::string::String {
537        match self {
538            Self::Row => "row".to_string(),
539            Self::Column => "column".to_string(),
540            Self::RowDense => "row dense".to_string(),
541            Self::ColumnDense => "column dense".to_string(),
542        }
543    }
544}
545
546#[cfg(feature = "parser")]
547#[derive(Clone, PartialEq, Eq)]
548pub enum GridAutoFlowParseError<'a> {
549    InvalidValue(&'a str),
550}
551
552#[cfg(feature = "parser")]
553impl_debug_as_display!(GridAutoFlowParseError<'a>);
554#[cfg(feature = "parser")]
555impl_display! { GridAutoFlowParseError<'a>, {
556    InvalidValue(e) => format!("Invalid grid-auto-flow value: \"{}\"", e),
557}}
558
559#[cfg(feature = "parser")]
560#[derive(Debug, Clone, PartialEq, Eq)]
561#[repr(C, u8)]
562pub enum GridAutoFlowParseErrorOwned {
563    InvalidValue(AzString),
564}
565
566#[cfg(feature = "parser")]
567impl GridAutoFlowParseError<'_> {
568    #[must_use] pub fn to_contained(&self) -> GridAutoFlowParseErrorOwned {
569        match self {
570            GridAutoFlowParseError::InvalidValue(s) => {
571                GridAutoFlowParseErrorOwned::InvalidValue((*s).to_string().into())
572            }
573        }
574    }
575}
576
577#[cfg(feature = "parser")]
578impl GridAutoFlowParseErrorOwned {
579    #[must_use] pub fn to_shared(&self) -> GridAutoFlowParseError<'_> {
580        match self {
581            Self::InvalidValue(s) => {
582                GridAutoFlowParseError::InvalidValue(s.as_str())
583            }
584        }
585    }
586}
587
588#[cfg(feature = "parser")]
589/// # Errors
590///
591/// Returns an error if `input` is not a valid CSS `grid-auto-flow` value.
592pub fn parse_layout_grid_auto_flow(
593    input: &str,
594) -> Result<LayoutGridAutoFlow, GridAutoFlowParseError<'_>> {
595    match input.trim() {
596        "row" => Ok(LayoutGridAutoFlow::Row),
597        "column" => Ok(LayoutGridAutoFlow::Column),
598        "row dense" | "dense" => Ok(LayoutGridAutoFlow::RowDense),
599        "column dense" => Ok(LayoutGridAutoFlow::ColumnDense),
600        _ => Err(GridAutoFlowParseError::InvalidValue(input)),
601    }
602}
603
604// --- justify-self / justify-items ---
605
606/// Represents `justify-self` for grid items
607#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
608#[repr(C)]
609#[derive(Default)]
610pub enum LayoutJustifySelf {
611    #[default]
612    Auto,
613    Start,
614    End,
615    Center,
616    Stretch,
617}
618
619
620impl PrintAsCssValue for LayoutJustifySelf {
621    fn print_as_css_value(&self) -> alloc::string::String {
622        match self {
623            Self::Auto => "auto".to_string(),
624            Self::Start => "start".to_string(),
625            Self::End => "end".to_string(),
626            Self::Center => "center".to_string(),
627            Self::Stretch => "stretch".to_string(),
628        }
629    }
630}
631
632#[cfg(feature = "parser")]
633#[derive(Clone, PartialEq, Eq)]
634pub enum JustifySelfParseError<'a> {
635    InvalidValue(&'a str),
636}
637
638#[cfg(feature = "parser")]
639#[derive(Debug, Clone, PartialEq, Eq)]
640#[repr(C, u8)]
641pub enum JustifySelfParseErrorOwned {
642    InvalidValue(AzString),
643}
644
645#[cfg(feature = "parser")]
646impl JustifySelfParseError<'_> {
647    #[must_use] pub fn to_contained(&self) -> JustifySelfParseErrorOwned {
648        match self {
649            JustifySelfParseError::InvalidValue(s) => {
650                JustifySelfParseErrorOwned::InvalidValue((*s).to_string().into())
651            }
652        }
653    }
654}
655
656#[cfg(feature = "parser")]
657impl JustifySelfParseErrorOwned {
658    #[must_use] pub fn to_shared(&self) -> JustifySelfParseError<'_> {
659        match self {
660            Self::InvalidValue(s) => {
661                JustifySelfParseError::InvalidValue(s.as_str())
662            }
663        }
664    }
665}
666
667#[cfg(feature = "parser")]
668impl_debug_as_display!(JustifySelfParseError<'a>);
669#[cfg(feature = "parser")]
670impl_display! { JustifySelfParseError<'a>, {
671    InvalidValue(e) => format!("Invalid justify-self value: \"{}\"", e),
672}}
673
674#[cfg(feature = "parser")]
675/// # Errors
676///
677/// Returns an error if `input` is not a valid CSS `justify-self` value.
678pub fn parse_layout_justify_self(
679    input: &str,
680) -> Result<LayoutJustifySelf, JustifySelfParseError<'_>> {
681    match input.trim() {
682        "auto" => Ok(LayoutJustifySelf::Auto),
683        "start" | "flex-start" => Ok(LayoutJustifySelf::Start),
684        "end" | "flex-end" => Ok(LayoutJustifySelf::End),
685        "center" => Ok(LayoutJustifySelf::Center),
686        "stretch" => Ok(LayoutJustifySelf::Stretch),
687        _ => Err(JustifySelfParseError::InvalidValue(input)),
688    }
689}
690
691/// Represents `justify-items` for grid containers
692#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
693#[repr(C)]
694#[derive(Default)]
695pub enum LayoutJustifyItems {
696    Start,
697    End,
698    Center,
699    #[default]
700    Stretch,
701}
702
703
704impl PrintAsCssValue for LayoutJustifyItems {
705    fn print_as_css_value(&self) -> alloc::string::String {
706        match self {
707            Self::Start => "start".to_string(),
708            Self::End => "end".to_string(),
709            Self::Center => "center".to_string(),
710            Self::Stretch => "stretch".to_string(),
711        }
712    }
713}
714
715#[cfg(feature = "parser")]
716#[derive(Clone, PartialEq, Eq)]
717pub enum JustifyItemsParseError<'a> {
718    InvalidValue(&'a str),
719}
720
721#[cfg(feature = "parser")]
722#[derive(Debug, Clone, PartialEq, Eq)]
723#[repr(C, u8)]
724pub enum JustifyItemsParseErrorOwned {
725    InvalidValue(AzString),
726}
727
728#[cfg(feature = "parser")]
729impl JustifyItemsParseError<'_> {
730    #[must_use] pub fn to_contained(&self) -> JustifyItemsParseErrorOwned {
731        match self {
732            JustifyItemsParseError::InvalidValue(s) => {
733                JustifyItemsParseErrorOwned::InvalidValue((*s).to_string().into())
734            }
735        }
736    }
737}
738
739#[cfg(feature = "parser")]
740impl JustifyItemsParseErrorOwned {
741    #[must_use] pub fn to_shared(&self) -> JustifyItemsParseError<'_> {
742        match self {
743            Self::InvalidValue(s) => {
744                JustifyItemsParseError::InvalidValue(s.as_str())
745            }
746        }
747    }
748}
749
750#[cfg(feature = "parser")]
751impl_debug_as_display!(JustifyItemsParseError<'a>);
752#[cfg(feature = "parser")]
753impl_display! { JustifyItemsParseError<'a>, {
754    InvalidValue(e) => format!("Invalid justify-items value: \"{}\"", e),
755}}
756
757#[cfg(feature = "parser")]
758/// # Errors
759///
760/// Returns an error if `input` is not a valid CSS `justify-items` value.
761pub fn parse_layout_justify_items(
762    input: &str,
763) -> Result<LayoutJustifyItems, JustifyItemsParseError<'_>> {
764    match input.trim() {
765        "start" => Ok(LayoutJustifyItems::Start),
766        "end" => Ok(LayoutJustifyItems::End),
767        "center" => Ok(LayoutJustifyItems::Center),
768        "stretch" => Ok(LayoutJustifyItems::Stretch),
769        _ => Err(JustifyItemsParseError::InvalidValue(input)),
770    }
771}
772
773// --- gap (single value type) ---
774
775#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
776#[repr(C)]
777pub struct LayoutGap {
778    pub inner: PixelValue,
779}
780
781impl core::fmt::Debug for LayoutGap {
782    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
783        write!(f, "{}", self.inner)
784    }
785}
786
787impl PrintAsCssValue for LayoutGap {
788    fn print_as_css_value(&self) -> alloc::string::String {
789        self.inner.print_as_css_value()
790    }
791}
792
793// Implement FormatAsRustCode for the new types so they can be emitted by the
794// code generator.
795impl FormatAsRustCode for LayoutGridAutoFlow {
796    fn format_as_rust_code(&self, _tabs: usize) -> String {
797        format!(
798            "LayoutGridAutoFlow::{}",
799            match self {
800                Self::Row => "Row",
801                Self::Column => "Column",
802                Self::RowDense => "RowDense",
803                Self::ColumnDense => "ColumnDense",
804            }
805        )
806    }
807}
808
809impl FormatAsRustCode for LayoutJustifySelf {
810    fn format_as_rust_code(&self, _tabs: usize) -> String {
811        format!(
812            "LayoutJustifySelf::{}",
813            match self {
814                Self::Auto => "Auto",
815                Self::Start => "Start",
816                Self::End => "End",
817                Self::Center => "Center",
818                Self::Stretch => "Stretch",
819            }
820        )
821    }
822}
823
824impl FormatAsRustCode for LayoutJustifyItems {
825    fn format_as_rust_code(&self, _tabs: usize) -> String {
826        format!(
827            "LayoutJustifyItems::{}",
828            match self {
829                Self::Start => "Start",
830                Self::End => "End",
831                Self::Center => "Center",
832                Self::Stretch => "Stretch",
833            }
834        )
835    }
836}
837
838impl FormatAsRustCode for LayoutGap {
839    fn format_as_rust_code(&self, _tabs: usize) -> String {
840        use crate::codegen::format::format_pixel_value;
841        format!("LayoutGap {{ inner: {} }}", format_pixel_value(&self.inner))
842    }
843}
844
845impl FormatAsRustCode for GridTrackSizing {
846    // `tabs` is required by the FormatAsRustCode trait signature; this variant only
847    // threads it through to nested MinMax children, never reading it locally.
848    #[allow(clippy::only_used_in_recursion)]
849    fn format_as_rust_code(&self, tabs: usize) -> String {
850        use crate::codegen::format::format_pixel_value;
851        match self {
852            Self::Fixed(pv) => {
853                format!("GridTrackSizing::Fixed({})", format_pixel_value(pv))
854            }
855            Self::Fr(f) => format!("GridTrackSizing::Fr({f})"),
856            Self::MinContent => "GridTrackSizing::MinContent".to_string(),
857            Self::MaxContent => "GridTrackSizing::MaxContent".to_string(),
858            Self::Auto => "GridTrackSizing::Auto".to_string(),
859            Self::MinMax(minmax) => {
860                format!(
861                    "GridTrackSizing::MinMax(GridMinMax {{ min: Box::new({}), max: Box::new({}) }})",
862                    minmax.min.format_as_rust_code(tabs),
863                    minmax.max.format_as_rust_code(tabs)
864                )
865            }
866            Self::FitContent(pv) => {
867                format!("GridTrackSizing::FitContent({})", format_pixel_value(pv))
868            }
869        }
870    }
871}
872
873impl FormatAsRustCode for GridAutoTracks {
874    fn format_as_rust_code(&self, tabs: usize) -> String {
875        let tracks: Vec<String> = self
876            .tracks
877            .as_ref()
878            .iter()
879            .map(|t| t.format_as_rust_code(tabs))
880            .collect();
881        format!(
882            "GridAutoTracks {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
883            tracks.join(", ")
884        )
885    }
886}
887
888impl FormatAsRustCode for GridTemplateAreas {
889    fn format_as_rust_code(&self, _tabs: usize) -> String {
890        format!("GridTemplateAreas {{ areas: GridAreaDefinitionVec::from_vec(vec!{:?}) }}", self.areas.as_ref())
891    }
892}
893
894#[cfg(feature = "parser")]
895/// # Errors
896///
897/// Returns an error if `input` is not a valid CSS `gap` value.
898pub fn parse_layout_gap(
899    input: &str,
900) -> Result<LayoutGap, crate::props::basic::pixel::CssPixelValueParseError<'_>> {
901    crate::props::basic::pixel::parse_pixel_value(input).map(|p| LayoutGap { inner: p })
902}
903
904#[cfg(feature = "parser")]
905/// # Errors
906///
907/// Returns an error if `input` is not a valid CSS `grid-line-owned` value.
908pub fn parse_grid_line_owned(input: &str) -> Result<GridLine, ()> {
909    let input = input.trim();
910
911    if input == "auto" {
912        return Ok(GridLine::Auto);
913    }
914
915    if let Some(num_str) = input.strip_prefix("span ") {
916        let num_str = num_str.trim();
917        if let Ok(num) = num_str.parse::<i32>() {
918            return Ok(GridLine::Span(num));
919        }
920        return Err(());
921    }
922
923    // Try to parse as line number
924    if let Ok(num) = input.parse::<i32>() {
925        return Ok(GridLine::Line(num));
926    }
927
928    // Otherwise treat as named line
929    Ok(GridLine::Named(NamedGridLine::create(
930        input.to_string().into(),
931        None,
932    )))
933}
934
935#[cfg(all(test, feature = "parser"))]
936mod tests {
937    use super::*;
938
939    // Grid template tests
940    #[test]
941    fn test_parse_grid_template_none() {
942        let result = parse_grid_template("none").unwrap();
943        assert_eq!(result.tracks.len(), 0);
944    }
945
946    #[test]
947    fn test_parse_grid_template_single_px() {
948        let result = parse_grid_template("100px").unwrap();
949        assert_eq!(result.tracks.len(), 1);
950        assert!(matches!(
951            result.tracks.as_ref()[0],
952            GridTrackSizing::Fixed(_)
953        ));
954    }
955
956    #[test]
957    fn test_parse_grid_template_multiple_tracks() {
958        let result = parse_grid_template("100px 200px 1fr").unwrap();
959        assert_eq!(result.tracks.len(), 3);
960    }
961
962    #[test]
963    fn test_parse_grid_template_fr_units() {
964        let result = parse_grid_template("1fr 2fr 1fr").unwrap();
965        assert_eq!(result.tracks.len(), 3);
966        assert!(matches!(
967            result.tracks.as_ref()[0],
968            GridTrackSizing::Fr(100)
969        ));
970        assert!(matches!(
971            result.tracks.as_ref()[1],
972            GridTrackSizing::Fr(200)
973        ));
974    }
975
976    #[test]
977    fn test_parse_grid_template_fractional_fr() {
978        let result = parse_grid_template("0.5fr 1.5fr").unwrap();
979        assert_eq!(result.tracks.len(), 2);
980        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(50)));
981        assert!(matches!(
982            result.tracks.as_ref()[1],
983            GridTrackSizing::Fr(150)
984        ));
985    }
986
987    #[test]
988    fn test_parse_grid_template_auto() {
989        let result = parse_grid_template("auto 100px auto").unwrap();
990        assert_eq!(result.tracks.len(), 3);
991        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Auto));
992        assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Auto));
993    }
994
995    #[test]
996    fn test_parse_grid_template_min_max_content() {
997        let result = parse_grid_template("min-content max-content auto").unwrap();
998        assert_eq!(result.tracks.len(), 3);
999        assert!(matches!(
1000            result.tracks.as_ref()[0],
1001            GridTrackSizing::MinContent
1002        ));
1003        assert!(matches!(
1004            result.tracks.as_ref()[1],
1005            GridTrackSizing::MaxContent
1006        ));
1007    }
1008
1009    #[test]
1010    fn test_parse_grid_template_minmax() {
1011        let result = parse_grid_template("minmax(100px, 1fr)").unwrap();
1012        assert_eq!(result.tracks.len(), 1);
1013        assert!(matches!(
1014            result.tracks.as_ref()[0],
1015            GridTrackSizing::MinMax(_)
1016        ));
1017    }
1018
1019    #[test]
1020    fn test_parse_grid_template_minmax_complex() {
1021        let result = parse_grid_template("minmax(min-content, max-content)").unwrap();
1022        assert_eq!(result.tracks.len(), 1);
1023    }
1024
1025    #[test]
1026    fn test_parse_grid_template_fit_content() {
1027        let result = parse_grid_template("fit-content(200px)").unwrap();
1028        assert_eq!(result.tracks.len(), 1);
1029        assert!(matches!(
1030            result.tracks.as_ref()[0],
1031            GridTrackSizing::FitContent(_)
1032        ));
1033    }
1034
1035    #[test]
1036    fn test_parse_grid_template_mixed() {
1037        let result = parse_grid_template("100px minmax(100px, 1fr) auto 2fr").unwrap();
1038        assert_eq!(result.tracks.len(), 4);
1039    }
1040
1041    #[test]
1042    fn test_parse_grid_template_percent() {
1043        let result = parse_grid_template("25% 50% 25%").unwrap();
1044        assert_eq!(result.tracks.len(), 3);
1045    }
1046
1047    #[test]
1048    fn test_parse_grid_template_em_units() {
1049        let result = parse_grid_template("10em 20em 1fr").unwrap();
1050        assert_eq!(result.tracks.len(), 3);
1051    }
1052
1053    // Grid placement tests
1054    #[test]
1055    fn test_parse_grid_placement_auto() {
1056        let result = parse_grid_placement("auto").unwrap();
1057        assert!(matches!(result.grid_start, GridLine::Auto));
1058        assert!(matches!(result.grid_end, GridLine::Auto));
1059    }
1060
1061    #[test]
1062    fn test_parse_grid_placement_line_number() {
1063        let result = parse_grid_placement("1").unwrap();
1064        assert!(matches!(result.grid_start, GridLine::Line(1)));
1065        assert!(matches!(result.grid_end, GridLine::Auto));
1066    }
1067
1068    #[test]
1069    fn test_parse_grid_placement_negative_line() {
1070        let result = parse_grid_placement("-1").unwrap();
1071        assert!(matches!(result.grid_start, GridLine::Line(-1)));
1072    }
1073
1074    #[test]
1075    fn test_parse_grid_placement_span() {
1076        let result = parse_grid_placement("span 2").unwrap();
1077        assert!(matches!(result.grid_start, GridLine::Span(2)));
1078    }
1079
1080    #[test]
1081    fn test_parse_grid_placement_start_end() {
1082        let result = parse_grid_placement("1 / 3").unwrap();
1083        assert!(matches!(result.grid_start, GridLine::Line(1)));
1084        assert!(matches!(result.grid_end, GridLine::Line(3)));
1085    }
1086
1087    #[test]
1088    fn test_parse_grid_placement_span_end() {
1089        let result = parse_grid_placement("1 / span 2").unwrap();
1090        assert!(matches!(result.grid_start, GridLine::Line(1)));
1091        assert!(matches!(result.grid_end, GridLine::Span(2)));
1092    }
1093
1094    #[test]
1095    fn test_parse_grid_placement_named_line() {
1096        let result = parse_grid_placement("header-start").unwrap();
1097        assert!(matches!(result.grid_start, GridLine::Named(_)));
1098    }
1099
1100    #[test]
1101    fn test_parse_grid_placement_named_start_end() {
1102        let result = parse_grid_placement("header-start / header-end").unwrap();
1103        assert!(matches!(result.grid_start, GridLine::Named(_)));
1104        assert!(matches!(result.grid_end, GridLine::Named(_)));
1105    }
1106
1107    // Edge cases
1108    #[test]
1109    fn test_parse_grid_template_whitespace() {
1110        let result = parse_grid_template("  100px   200px  ").unwrap();
1111        assert_eq!(result.tracks.len(), 2);
1112    }
1113
1114    #[test]
1115    fn test_parse_grid_placement_whitespace() {
1116        let result = parse_grid_placement("  1  /  3  ").unwrap();
1117        assert!(matches!(result.grid_start, GridLine::Line(1)));
1118        assert!(matches!(result.grid_end, GridLine::Line(3)));
1119    }
1120
1121    #[test]
1122    fn test_parse_grid_template_zero_fr() {
1123        let result = parse_grid_template("0fr").unwrap();
1124        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(0)));
1125    }
1126
1127    #[test]
1128    fn test_parse_grid_placement_zero_line() {
1129        let result = parse_grid_placement("0").unwrap();
1130        assert!(matches!(result.grid_start, GridLine::Line(0)));
1131    }
1132
1133    // repeat() tests
1134    #[test]
1135    fn test_parse_grid_template_repeat_fr() {
1136        let result = parse_grid_template("repeat(3, 1fr)").unwrap();
1137        assert_eq!(result.tracks.len(), 3);
1138        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(100)));
1139        assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1140        assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1141    }
1142
1143    #[test]
1144    fn test_parse_grid_template_repeat_px() {
1145        let result = parse_grid_template("repeat(2, 100px)").unwrap();
1146        assert_eq!(result.tracks.len(), 2);
1147        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1148        assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fixed(_)));
1149    }
1150
1151    #[test]
1152    fn test_parse_grid_template_repeat_multiple_tracks() {
1153        // repeat(2, 100px 1fr) should expand to [100px, 1fr, 100px, 1fr]
1154        let result = parse_grid_template("repeat(2, 100px 1fr)").unwrap();
1155        assert_eq!(result.tracks.len(), 4);
1156        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1157        assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1158        assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fixed(_)));
1159        assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Fr(100)));
1160    }
1161
1162    #[test]
1163    fn test_parse_grid_template_repeat_with_other_tracks() {
1164        // "100px repeat(2, 1fr) auto" should produce [100px, 1fr, 1fr, auto]
1165        let result = parse_grid_template("100px repeat(2, 1fr) auto").unwrap();
1166        assert_eq!(result.tracks.len(), 4);
1167        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1168        assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1169        assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1170        assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Auto));
1171    }
1172
1173    #[test]
1174    fn test_parse_grid_template_repeat_minmax() {
1175        let result = parse_grid_template("repeat(3, minmax(100px, 1fr))").unwrap();
1176        assert_eq!(result.tracks.len(), 3);
1177        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::MinMax(_)));
1178        assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::MinMax(_)));
1179        assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::MinMax(_)));
1180    }
1181}
1182
1183// --- grid-template-areas ---
1184
1185/// A single named grid area with its row/column bounds (1-based grid line numbers).
1186/// This matches taffy's `GridTemplateArea<String>`.
1187#[repr(C)]
1188#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1189pub struct GridAreaDefinition {
1190    pub name: AzString,
1191    pub row_start: u16,
1192    pub row_end: u16,
1193    pub column_start: u16,
1194    pub column_end: u16,
1195}
1196
1197impl_option!(
1198    GridAreaDefinition,
1199    OptionGridAreaDefinition,
1200    copy = false,
1201    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1202);
1203
1204impl_vec!(GridAreaDefinition, GridAreaDefinitionVec, GridAreaDefinitionVecDestructor, GridAreaDefinitionVecDestructorType, GridAreaDefinitionVecSlice, OptionGridAreaDefinition);
1205impl_vec_clone!(
1206    GridAreaDefinition,
1207    GridAreaDefinitionVec,
1208    GridAreaDefinitionVecDestructor
1209);
1210impl_vec_debug!(GridAreaDefinition, GridAreaDefinitionVec);
1211impl_vec_partialeq!(GridAreaDefinition, GridAreaDefinitionVec);
1212impl_vec_eq!(GridAreaDefinition, GridAreaDefinitionVec);
1213impl_vec_partialord!(GridAreaDefinition, GridAreaDefinitionVec);
1214impl_vec_ord!(GridAreaDefinition, GridAreaDefinitionVec);
1215impl_vec_hash!(GridAreaDefinition, GridAreaDefinitionVec);
1216impl_vec_mut!(GridAreaDefinition, GridAreaDefinitionVec);
1217
1218/// Represents the parsed value of `grid-template-areas`.
1219///
1220/// Example CSS:
1221/// ```css
1222/// grid-template-areas:
1223///     "header header header"
1224///     "sidebar main aside"
1225///     "footer footer footer";
1226/// ```
1227#[repr(C)]
1228#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1229pub struct GridTemplateAreas {
1230    pub areas: GridAreaDefinitionVec,
1231}
1232
1233impl Default for GridTemplateAreas {
1234    fn default() -> Self {
1235        Self { areas: GridAreaDefinitionVec::from_vec(Vec::new()) }
1236    }
1237}
1238
1239impl PrintAsCssValue for GridTemplateAreas {
1240    fn print_as_css_value(&self) -> String {
1241        let areas_slice = self.areas.as_ref();
1242        if areas_slice.is_empty() {
1243            return "none".to_string();
1244        }
1245        // Reconstruct the row strings from the area definitions
1246        let max_row = areas_slice.iter().map(|a| a.row_end).max().unwrap_or(1);
1247        let max_col = areas_slice.iter().map(|a| a.column_end).max().unwrap_or(1);
1248        let num_rows = (max_row - 1) as usize;
1249        let num_cols = (max_col - 1) as usize;
1250        let mut grid: Vec<Vec<String>> = vec![vec![".".to_string(); num_cols]; num_rows];
1251        for area in areas_slice {
1252            let row_start = area.row_start as usize - 1;
1253            let row_end = area.row_end as usize - 1;
1254            let col_start = area.column_start as usize - 1;
1255            let col_end = area.column_end as usize - 1;
1256            for row in grid.iter_mut().take(row_end).skip(row_start) {
1257                for cell in row.iter_mut().take(col_end).skip(col_start) {
1258                    *cell = area.name.as_str().to_string();
1259                }
1260            }
1261        }
1262        grid.iter()
1263            .map(|row| format!("\"{}\"", row.join(" ")))
1264            .collect::<Vec<_>>()
1265            .join(" ")
1266    }
1267}
1268
1269/// Parse `grid-template-areas` CSS value.
1270///
1271/// Accepts quoted row strings like:
1272///   `"header header header" "sidebar main aside" "footer footer footer"`
1273///
1274/// Returns a `GridTemplateAreas` with deduplicated named areas and their
1275/// computed row/column line boundaries (1-based, as taffy expects).
1276#[cfg(feature = "parser")]
1277/// # Errors
1278///
1279/// Returns an error if `input` is not a valid CSS `grid-template-areas` value.
1280pub fn parse_grid_template_areas(input: &str) -> Result<GridTemplateAreas, ()> {
1281    use alloc::collections::BTreeMap;
1282    let input = input.trim();
1283    if input == "none" {
1284        return Ok(GridTemplateAreas::default());
1285    }
1286
1287    // Extract quoted strings: each one is a row
1288    let mut rows: Vec<Vec<String>> = Vec::new();
1289    let mut i = 0;
1290    let bytes = input.as_bytes();
1291    while i < bytes.len() {
1292        if bytes[i] == b'"' || bytes[i] == b'\'' {
1293            let quote = bytes[i];
1294            i += 1;
1295            let start = i;
1296            while i < bytes.len() && bytes[i] != quote {
1297                i += 1;
1298            }
1299            if i >= bytes.len() {
1300                return Err(());
1301            }
1302            let row_str = &input[start..i];
1303            let cells: Vec<String> = row_str.split_whitespace().map(std::string::ToString::to_string).collect();
1304            if cells.is_empty() {
1305                return Err(());
1306            }
1307            rows.push(cells);
1308        }
1309        // advance past the closing quote (quoted branch) or the current char (else)
1310        i += 1;
1311    }
1312
1313    if rows.is_empty() {
1314        return Err(());
1315    }
1316
1317    // Validate: all rows must have the same number of columns
1318    let num_cols = rows[0].len();
1319    for row in &rows {
1320        if row.len() != num_cols {
1321            return Err(());
1322        }
1323    }
1324
1325    // Build area map: name -> (min_row, max_row, min_col, max_col) in 0-based indices
1326    let mut area_map: BTreeMap<String, (usize, usize, usize, usize)> = BTreeMap::new();
1327
1328    for (row_idx, row) in rows.iter().enumerate() {
1329        for (col_idx, cell) in row.iter().enumerate() {
1330            if cell == "." {
1331                continue; // skip null cell tokens
1332            }
1333            let entry = area_map.entry(cell.clone()).or_insert((row_idx, row_idx, col_idx, col_idx));
1334            entry.0 = entry.0.min(row_idx);
1335            entry.1 = entry.1.max(row_idx);
1336            entry.2 = entry.2.min(col_idx);
1337            entry.3 = entry.3.max(col_idx);
1338        }
1339    }
1340
1341    // Convert to 1-based grid line numbers (taffy convention)
1342    let mut areas = Vec::new();
1343    for (name, (min_row, max_row, min_col, max_col)) in area_map {
1344        areas.push(GridAreaDefinition {
1345            name: name.into(),
1346            row_start: u16::try_from(min_row + 1).unwrap_or(u16::MAX),
1347            row_end: u16::try_from(max_row + 2).unwrap_or(u16::MAX), // end line is one past the last cell
1348            column_start: u16::try_from(min_col + 1).unwrap_or(u16::MAX),
1349            column_end: u16::try_from(max_col + 2).unwrap_or(u16::MAX),
1350        });
1351    }
1352
1353    Ok(GridTemplateAreas { areas: GridAreaDefinitionVec::from_vec(areas) })
1354}