1use crate::{
4 CheapCloneStr, GenericGridTemplateComponent, GenericRepetition as _, GridAreaAxis, GridAreaEnd, GridContainerStyle,
5 GridPlacement, GridTemplateArea, Line, NonNamedGridPlacement, RepetitionCount,
6};
7use core::{borrow::Borrow, cmp::Ordering, fmt::Debug};
8
9use super::{GridLine, MAX_GRID_TRACKS};
10#[cfg(feature = "detailed_layout_info")]
11use crate::geometry::AbsoluteAxis;
12#[cfg(feature = "detailed_layout_info")]
13use crate::sys::DefaultCheapStr;
14use crate::sys::{format, Map, Vec};
16use smallvec::{smallvec, SmallVec};
17
18#[derive(Debug, Clone)]
21pub(crate) struct StrHasher<T: CheapCloneStr>(pub T);
22impl<T: CheapCloneStr> PartialOrd for StrHasher<T> {
23 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
24 Some(self.cmp(other))
25 }
26}
27impl<T: CheapCloneStr> Ord for StrHasher<T> {
28 fn cmp(&self, other: &Self) -> Ordering {
29 self.0.as_ref().cmp(other.0.as_ref())
30 }
31}
32impl<T: CheapCloneStr> PartialEq for StrHasher<T> {
33 fn eq(&self, other: &Self) -> bool {
34 other.0.as_ref() == self.0.as_ref()
35 }
36}
37impl<T: CheapCloneStr> Eq for StrHasher<T> {}
38#[cfg(feature = "std")]
39impl<T: CheapCloneStr> std::hash::Hash for StrHasher<T> {
40 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
41 self.0.as_ref().hash(state)
42 }
43}
44impl<T: CheapCloneStr> Borrow<str> for StrHasher<T> {
45 fn borrow(&self) -> &str {
46 self.0.as_ref()
47 }
48}
49
50pub(crate) type LinePositions = SmallVec<[u32; 4]>;
54
55type NamedGridLinesMap<S> = Map<StrHasher<S>, LinePositions>;
57
58struct NamedLineResolverAxis<'a, S: CheapCloneStr> {
60 lines: &'a NamedGridLinesMap<S>,
62 explicit_track_count: u16,
64}
65
66pub(crate) struct NamedLineResolver<S: CheapCloneStr> {
69 row_lines: NamedGridLinesMap<S>,
72 column_lines: NamedGridLinesMap<S>,
75 areas: Map<StrHasher<S>, GridTemplateArea<S>>,
77 area_column_count: u16,
79 area_row_count: u16,
81 explicit_column_count: u16,
84 explicit_row_count: u16,
87 #[cfg(feature = "detailed_layout_info")]
90 column_line_name_pairs: Vec<(u32, S)>,
91 #[cfg(feature = "detailed_layout_info")]
94 row_line_name_pairs: Vec<(u32, S)>,
95}
96
97fn upsert_line_name_map<S: CheapCloneStr>(map: &mut NamedGridLinesMap<S>, key: S, value: u32) {
99 map.entry(StrHasher(key)).and_modify(|lines| lines.push(value)).or_insert_with(|| smallvec![value]);
100}
101
102impl<S: CheapCloneStr> NamedLineResolverAxis<'_, S> {
103 fn resolve_line_names(&self, line: &Line<GridPlacement<S>>) -> Line<NonNamedGridPlacement> {
105 let start_holder;
106 let start_line_resolved = if let GridPlacement::NamedLine(name, idx) = &line.start {
107 start_holder =
108 GridPlacement::Line(self.find_line_index(name, *idx as i32, GridAreaEnd::Start, &|lines| lines));
109 &start_holder
110 } else {
111 &line.start
112 };
113
114 let end_holder;
115 let end_line_resolved = if let GridPlacement::NamedLine(name, idx) = &line.end {
116 end_holder = GridPlacement::Line(self.find_line_index(name, *idx as i32, GridAreaEnd::End, &|lines| lines));
117 &end_holder
118 } else {
119 &line.end
120 };
121
122 match (&start_line_resolved, &end_line_resolved) {
130 (GridPlacement::Line(start_line), GridPlacement::NamedSpan(name, idx)) => {
131 let normalized_start_line = if start_line.as_i16() > 0 {
132 start_line.as_i16() as u32
133 } else {
134 (self.explicit_track_count as i32 + 1 + start_line.as_i16() as i32).max(0) as u32
135 };
136 let end_line = self.find_line_index(name, *idx as i32, GridAreaEnd::End, &|lines| {
137 let point = lines.partition_point(|line| *line <= normalized_start_line);
138 &lines[point..]
139 });
140 Line { start: NonNamedGridPlacement::Line(*start_line), end: NonNamedGridPlacement::Line(end_line) }
141 }
142 (GridPlacement::NamedSpan(name, idx), GridPlacement::Line(end_line)) => {
143 let normalized_end_line = if end_line.as_i16() > 0 {
144 end_line.as_i16() as u32
145 } else {
146 (self.explicit_track_count as i32 + 1 + end_line.as_i16() as i32).max(0) as u32
147 };
148 let start_line = self.find_line_index(name, *idx as i32, GridAreaEnd::Start, &|lines| {
149 let point = lines.partition_point(|line| *line < normalized_end_line);
150 &lines[..point]
151 });
152 Line { start: NonNamedGridPlacement::Line(start_line), end: NonNamedGridPlacement::Line(*end_line) }
153 }
154 (start, end) => Line {
155 start: match start {
156 GridPlacement::Auto => NonNamedGridPlacement::Auto,
157 GridPlacement::Line(grid_line) => NonNamedGridPlacement::Line(*grid_line),
158 GridPlacement::Span(span) => NonNamedGridPlacement::Span(*span),
159 GridPlacement::NamedSpan(_, _) => NonNamedGridPlacement::Span(1),
160 _ => unreachable!(),
161 },
162 end: match end {
163 GridPlacement::Auto => NonNamedGridPlacement::Auto,
164 GridPlacement::Line(grid_line) => NonNamedGridPlacement::Line(*grid_line),
165 GridPlacement::Span(span) => NonNamedGridPlacement::Span(*span),
166 GridPlacement::NamedSpan(_, _) => NonNamedGridPlacement::Span(1),
167 _ => unreachable!(),
168 },
169 },
170 }
171 }
172
173 fn find_line_index(
175 &self,
176 name: &S,
177 idx: i32,
178 end: GridAreaEnd,
179 filter_lines: &dyn Fn(&[u32]) -> &[u32],
180 ) -> GridLine {
181 let name = name.as_ref();
182 let mut idx = idx;
183 let explicit_track_count = self.explicit_track_count as i32;
184
185 if idx == 0 {
187 idx = 1;
188 }
189
190 fn get_line(lines: &[u32], explicit_track_count: i32, idx: i32) -> i16 {
191 let abs_idx = idx.unsigned_abs() as usize;
192 let line = if abs_idx <= lines.len() {
193 if idx > 0 {
194 lines[abs_idx - 1] as i64
195 } else {
196 lines[lines.len() - abs_idx] as i64
197 }
198 } else {
199 let remaining_lines = (abs_idx - lines.len()) as i64 * idx.signum() as i64;
200 if idx > 0 {
201 explicit_track_count as i64 + 1 + remaining_lines
202 } else {
203 -(explicit_track_count as i64 + 1 + remaining_lines)
204 }
205 };
206 line.clamp(i16::MIN as i64, i16::MAX as i64) as i16
207 }
208
209 if let Some(lines) = self.lines.get(name) {
211 return GridLine::from(get_line(filter_lines(lines), explicit_track_count, idx));
212 }
213
214 let implicit_name = match end {
216 GridAreaEnd::Start => format!("{name}-start"),
217 GridAreaEnd::End => format!("{name}-end"),
218 };
219 if let Some(lines) = self.lines.get(&*implicit_name) {
220 return GridLine::from(get_line(filter_lines(lines), explicit_track_count, idx));
221 }
222
223 let line = if idx > 0 {
231 explicit_track_count as i64 + 1 + idx as i64
232 } else {
233 -(explicit_track_count as i64 + 1 + idx as i64)
234 };
235 GridLine::from(line.clamp(i16::MIN as i64, i16::MAX as i64) as i16)
236 }
237}
238
239impl<S: CheapCloneStr> NamedLineResolver<S> {
240 pub(crate) fn new(
242 style: &impl GridContainerStyle<CustomIdent = S>,
243 column_auto_repetitions: u16,
244 row_auto_repetitions: u16,
245 ) -> Self {
246 let mut areas: Map<StrHasher<S>, GridTemplateArea<_>> = Map::new();
247 let mut column_lines: NamedGridLinesMap<S> = Map::new();
248 let mut row_lines: NamedGridLinesMap<S> = Map::new();
249
250 #[cfg(feature = "detailed_layout_info")]
251 let mut column_line_name_pairs: Vec<(u32, S)> = Vec::new();
252 #[cfg(feature = "detailed_layout_info")]
253 let mut row_line_name_pairs: Vec<(u32, S)> = Vec::new();
254
255 let mut current_line = 0;
256 if let Some(mut column_tracks) = style.grid_template_columns() {
257 if let Some(column_line_names_iter) = style.grid_template_column_names() {
258 for line_names in column_line_names_iter {
259 current_line += 1;
260 for line_name in line_names.into_iter() {
261 #[cfg(feature = "detailed_layout_info")]
262 column_line_name_pairs.push((current_line, line_name.clone()));
263 upsert_line_name_map(&mut column_lines, line_name.clone(), current_line);
264 }
265
266 if let Some(GenericGridTemplateComponent::Repeat(repeat)) = column_tracks.next() {
267 let repeat_count = match repeat.count() {
268 RepetitionCount::Count(count) => count,
269 RepetitionCount::AutoFill | RepetitionCount::AutoFit => column_auto_repetitions,
270 };
271
272 let line_name_set_count = repeat.lines_names().len() as u32;
278 let lines_per_repetition = repeat.track_count() as u32;
279 assert!(
280 line_name_set_count == 0 || line_name_set_count == lines_per_repetition + 1,
281 "grid template repetition must have no line name sets or exactly track count + 1 of them ({} tracks but {} line name sets)",
282 lines_per_repetition,
283 line_name_set_count,
284 );
285
286 for _ in 0..repeat_count {
287 for (line, line_name_set) in (current_line..).zip(repeat.lines_names()) {
288 for line_name in line_name_set {
289 #[cfg(feature = "detailed_layout_info")]
290 column_line_name_pairs.push((line, line_name.clone()));
291 upsert_line_name_map(&mut column_lines, line_name.clone(), line);
292 }
293 }
294 current_line += lines_per_repetition;
295
296 if current_line > MAX_GRID_TRACKS as u32 {
299 break;
300 }
301 }
302 if repeat_count > 0 {
304 current_line = current_line.saturating_sub(1);
305 }
306 }
307 }
308 }
309 }
310
311 let mut current_line = 0;
312 if let Some(mut row_tracks) = style.grid_template_rows() {
313 if let Some(row_line_names_iter) = style.grid_template_row_names() {
314 for line_names in row_line_names_iter {
315 current_line += 1;
316 for line_name in line_names.into_iter() {
317 #[cfg(feature = "detailed_layout_info")]
318 row_line_name_pairs.push((current_line, line_name.clone()));
319 upsert_line_name_map(&mut row_lines, line_name.clone(), current_line);
320 }
321
322 if let Some(GenericGridTemplateComponent::Repeat(repeat)) = row_tracks.next() {
323 let repeat_count = match repeat.count() {
324 RepetitionCount::Count(count) => count,
325 RepetitionCount::AutoFill | RepetitionCount::AutoFit => row_auto_repetitions,
326 };
327
328 let line_name_set_count = repeat.lines_names().len() as u32;
334 let lines_per_repetition = repeat.track_count() as u32;
335 assert!(
336 line_name_set_count == 0 || line_name_set_count == lines_per_repetition + 1,
337 "grid template repetition must have no line name sets or exactly track count + 1 of them ({} tracks but {} line name sets)",
338 lines_per_repetition,
339 line_name_set_count,
340 );
341
342 for _ in 0..repeat_count {
343 for (line, line_name_set) in (current_line..).zip(repeat.lines_names()) {
344 for line_name in line_name_set {
345 #[cfg(feature = "detailed_layout_info")]
346 row_line_name_pairs.push((line, line_name.clone()));
347 upsert_line_name_map(&mut row_lines, line_name.clone(), line);
348 }
349 }
350 current_line += lines_per_repetition;
351
352 if current_line > MAX_GRID_TRACKS as u32 {
355 break;
356 }
357 }
358 if repeat_count > 0 {
360 current_line = current_line.saturating_sub(1);
361 }
362 }
363 }
364 }
365 }
366 let area_column_count = style.grid_template_area_column_count();
370 let area_row_count = style.grid_template_area_row_count();
371 if let Some(area_iter) = style.grid_template_areas() {
372 for area in area_iter.into_iter() {
373 areas.insert(StrHasher(area.name.clone()), area.clone());
375
376 let col_start_name = S::from(format!("{}-start", area.name.as_ref()));
377 #[cfg(feature = "detailed_layout_info")]
378 column_line_name_pairs.push((area.column_start as u32, col_start_name.clone()));
379 upsert_line_name_map(&mut column_lines, col_start_name, area.column_start as u32);
380 let col_end_name = S::from(format!("{}-end", area.name.as_ref()));
381 #[cfg(feature = "detailed_layout_info")]
382 column_line_name_pairs.push((area.column_end as u32, col_end_name.clone()));
383 upsert_line_name_map(&mut column_lines, col_end_name, area.column_end as u32);
384 let row_start_name = S::from(format!("{}-start", area.name.as_ref()));
385 #[cfg(feature = "detailed_layout_info")]
386 row_line_name_pairs.push((area.row_start as u32, row_start_name.clone()));
387 upsert_line_name_map(&mut row_lines, row_start_name, area.row_start as u32);
388 let row_end_name = S::from(format!("{}-end", area.name.as_ref()));
389 #[cfg(feature = "detailed_layout_info")]
390 row_line_name_pairs.push((area.row_end as u32, row_end_name.clone()));
391 upsert_line_name_map(&mut row_lines, row_end_name, area.row_end as u32);
392 }
393 }
394
395 for lines in column_lines.values_mut() {
397 lines.sort_unstable();
398 lines.dedup();
399 }
400 for lines in row_lines.values_mut() {
402 lines.sort_unstable();
403 lines.dedup();
404 }
405
406 Self {
407 area_column_count,
408 area_row_count,
409 explicit_column_count: 0, explicit_row_count: 0, areas,
412 row_lines,
413 column_lines,
414 #[cfg(feature = "detailed_layout_info")]
415 column_line_name_pairs,
416 #[cfg(feature = "detailed_layout_info")]
417 row_line_name_pairs,
418 }
419 }
420
421 #[cfg(feature = "detailed_layout_info")]
427 pub(crate) fn detailed_line_names(&self, axis: AbsoluteAxis) -> GridLineNames<S> {
428 let (pairs, explicit_track_count) = match axis {
429 AbsoluteAxis::Horizontal => (&self.column_line_name_pairs, self.explicit_column_count),
430 AbsoluteAxis::Vertical => (&self.row_line_name_pairs, self.explicit_row_count),
431 };
432
433 if pairs.is_empty() {
434 return GridLineNames::default();
435 }
436
437 let mut sorted_pairs: Vec<&(u32, S)> = pairs.iter().collect();
440 sorted_pairs.sort_by_key(|(line, _)| *line);
441
442 let line_count = explicit_track_count as usize + 1;
443 let mut line_names = GridLineNames::with_capacity(sorted_pairs.len(), line_count + 1);
444 let mut pair_iter = sorted_pairs.into_iter().peekable();
445 for line in 1..=(line_count as u32) {
446 line_names.start_line();
447 while let Some(&&(pair_line, ref name)) = pair_iter.peek() {
448 if pair_line != line {
449 break;
450 }
451 pair_iter.next();
452 if !line_names.current_line_contains(name.as_ref()) {
453 line_names.push_name(name.clone());
454 }
455 }
456 }
457 line_names
458 }
459
460 #[inline(always)]
462 pub(crate) fn resolve_row_names(&self, line: &Line<GridPlacement<S>>) -> Line<NonNamedGridPlacement> {
463 self.resolve_line_names(line, GridAreaAxis::Row)
464 }
465
466 #[inline(always)]
468 pub(crate) fn resolve_column_names(&self, line: &Line<GridPlacement<S>>) -> Line<NonNamedGridPlacement> {
469 self.resolve_line_names(line, GridAreaAxis::Column)
470 }
471
472 #[inline(always)]
474 pub(crate) fn resolve_line_names(
475 &self,
476 line: &Line<GridPlacement<S>>,
477 axis: GridAreaAxis,
478 ) -> Line<NonNamedGridPlacement> {
479 match axis {
480 GridAreaAxis::Row => {
481 NamedLineResolverAxis { lines: &self.row_lines, explicit_track_count: self.explicit_row_count }
482 }
483 GridAreaAxis::Column => {
484 NamedLineResolverAxis { lines: &self.column_lines, explicit_track_count: self.explicit_column_count }
485 }
486 }
487 .resolve_line_names(line)
488 }
489
490 #[cfg(feature = "detailed_layout_info")]
492 pub(crate) fn populate_detailed_line_resolvers(self, rows: &mut GridLineNames<S>, columns: &mut GridLineNames<S>) {
493 rows.resolver = self.row_lines;
494 columns.resolver = self.column_lines;
495 }
496
497 pub(crate) fn area_column_count(&self) -> u16 {
499 self.area_column_count
500 }
501
502 pub(crate) fn area_row_count(&self) -> u16 {
504 self.area_row_count
505 }
506
507 pub(crate) fn set_explicit_column_count(&mut self, count: u16) {
509 self.explicit_column_count = count;
510 }
511
512 pub(crate) fn set_explicit_row_count(&mut self, count: u16) {
514 self.explicit_row_count = count;
515 }
516}
517
518impl<S: CheapCloneStr> Debug for NamedLineResolver<S> {
519 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
520 writeln!(f, "Grid Areas:")?;
521 for area in self.areas.values() {
522 writeln!(
523 f,
524 "{}: row:{}/{} col: {}/{}",
525 area.name.as_ref(),
526 area.row_start,
527 area.row_end,
528 area.column_start,
529 area.column_end
530 )?;
531 }
532
533 writeln!(f, "Grid Rows:")?;
534 for (name, lines) in self.row_lines.iter() {
535 write!(f, "{}: ", name.0.as_ref())?;
536 for line in lines {
537 write!(f, "{line} ")?;
538 }
539 writeln!(f)?;
540 }
541
542 writeln!(f, "Grid Columns:")?;
543 for (name, lines) in self.column_lines.iter() {
544 write!(f, "{}: ", name.0.as_ref())?;
545 for line in lines {
546 write!(f, "{line} ")?;
547 }
548 writeln!(f)?;
549 }
550
551 Ok(())
552 }
553}
554
555#[derive(Debug, Clone, PartialEq, Default)]
567#[cfg(feature = "detailed_layout_info")]
568pub struct GridLineNames<S: CheapCloneStr = DefaultCheapStr> {
569 names: Vec<S>,
571 offsets: Vec<u32>,
574 resolver: NamedGridLinesMap<S>,
576}
577
578#[cfg(feature = "detailed_layout_info")]
579impl<S: CheapCloneStr> GridLineNames<S> {
580 pub(crate) fn with_capacity(name_capacity: usize, offset_capacity: usize) -> Self {
582 let mut offsets = Vec::with_capacity(offset_capacity);
583 offsets.push(0);
584 Self { names: Vec::with_capacity(name_capacity), offsets, resolver: Map::new() }
585 }
586
587 pub(crate) fn start_line(&mut self) {
589 self.offsets.push(self.names.len() as u32);
590 }
591
592 pub(crate) fn push_name(&mut self, name: S) {
594 self.names.push(name);
595 *self.offsets.last_mut().unwrap() = self.names.len() as u32;
596 }
597
598 pub(crate) fn current_line_contains(&self, name: &str) -> bool {
600 self.line(self.line_count().wrapping_sub(1)).iter().any(|n| n.as_ref() == name)
601 }
602
603 pub(crate) fn resolve_line_names(
605 &self,
606 line: &Line<GridPlacement<S>>,
607 explicit_track_count: u16,
608 ) -> Line<NonNamedGridPlacement> {
609 NamedLineResolverAxis { lines: &self.resolver, explicit_track_count }.resolve_line_names(line)
610 }
611
612 pub fn is_empty(&self) -> bool {
614 self.names.is_empty()
615 }
616
617 pub fn line_count(&self) -> usize {
619 self.offsets.len().saturating_sub(1)
620 }
621
622 pub fn line(&self, line_index: usize) -> &[S] {
625 match (self.offsets.get(line_index), self.offsets.get(line_index + 1)) {
626 (Some(&start), Some(&end)) => &self.names[start as usize..end as usize],
627 _ => &[],
628 }
629 }
630
631 pub fn iter(&self) -> GridLineNamesIter<'_, S> {
633 self.iter_padded(0, 0)
634 }
635
636 pub(crate) fn iter_padded(&self, leading_empty: usize, trailing_empty: usize) -> GridLineNamesIter<'_, S> {
640 GridLineNamesIter { names: &self.names, offsets: self.offsets.windows(2), leading_empty, trailing_empty }
641 }
642}
643
644#[cfg(feature = "detailed_layout_info")]
645impl<'a, S: CheapCloneStr> IntoIterator for &'a GridLineNames<S> {
646 type Item = &'a [S];
647 type IntoIter = GridLineNamesIter<'a, S>;
648 fn into_iter(self) -> Self::IntoIter {
649 self.iter()
650 }
651}
652
653#[derive(Debug, Clone)]
656#[cfg(feature = "detailed_layout_info")]
657pub struct GridLineNamesIter<'a, S: CheapCloneStr> {
658 names: &'a [S],
660 offsets: core::slice::Windows<'a, u32>,
662 leading_empty: usize,
664 trailing_empty: usize,
666}
667
668#[cfg(feature = "detailed_layout_info")]
669impl<'a, S: CheapCloneStr> Iterator for GridLineNamesIter<'a, S> {
670 type Item = &'a [S];
671
672 fn next(&mut self) -> Option<Self::Item> {
673 if self.leading_empty > 0 {
674 self.leading_empty -= 1;
675 return Some(&[]);
676 }
677 if let Some(window) = self.offsets.next() {
678 return Some(&self.names[window[0] as usize..window[1] as usize]);
679 }
680 if self.trailing_empty > 0 {
681 self.trailing_empty -= 1;
682 return Some(&[]);
683 }
684 None
685 }
686
687 fn size_hint(&self) -> (usize, Option<usize>) {
688 let len = self.leading_empty + self.offsets.len() + self.trailing_empty;
689 (len, Some(len))
690 }
691}
692
693#[cfg(feature = "detailed_layout_info")]
694impl<S: CheapCloneStr> ExactSizeIterator for GridLineNamesIter<'_, S> {}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699 use crate::style::GenericGridPlacement;
700 use crate::sys::DefaultCheapStr;
701 use crate::GridTemplateAreas;
702 use crate::Style;
703
704 fn resolver(explicit_track_count: u16) -> NamedLineResolver<DefaultCheapStr> {
705 let mut resolver = NamedLineResolver::new(&Style::DEFAULT, 0, 0);
706 resolver.set_explicit_column_count(explicit_track_count);
707 resolver
708 }
709
710 fn resolved_start_line(
711 resolver: &NamedLineResolver<DefaultCheapStr>,
712 placement: GridPlacement<DefaultCheapStr>,
713 ) -> i16 {
714 let resolved = resolver.resolve_column_names(&Line { start: placement, end: GridPlacement::Auto });
715 match resolved.start {
716 GenericGridPlacement::Line(line) => line.as_i16(),
717 _ => panic!("expected a resolved line"),
718 }
719 }
720
721 #[test]
722 fn extreme_missing_named_line_indices_do_not_overflow() {
723 let resolver = resolver(10_000);
724 assert_eq!(
725 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("missing"), i16::MAX)),
726 i16::MAX
727 );
728 assert_eq!(
729 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("missing"), i16::MIN)),
730 22_767
731 );
732 }
733
734 #[test]
735 fn large_named_span_does_not_wrap_negative() {
736 let resolver = resolver(10_000);
737 let resolved = resolver.resolve_column_names(&Line {
738 start: GridPlacement::Line(GridLine::from(1)),
739 end: GridPlacement::NamedSpan(DefaultCheapStr::from("missing"), u16::MAX),
740 });
741 match resolved.end {
742 GenericGridPlacement::Line(line) => assert_eq!(line.as_i16(), i16::MAX),
743 _ => panic!("expected a resolved line"),
744 }
745 }
746
747 #[test]
748 fn area_lines_saturate_when_converted_to_grid_lines() {
749 let style = Style {
750 grid_template_areas: Some(GridTemplateAreas {
751 areas: vec![GridTemplateArea {
752 name: DefaultCheapStr::from("area"),
753 row_start: 1,
754 row_end: 2,
755 column_start: u16::MAX,
756 column_end: u16::MAX,
757 }],
758 row_count: 1,
759 column_count: u16::MAX,
760 }),
761 ..Style::DEFAULT
762 };
763 let resolver = NamedLineResolver::new(&style, 0, 0);
764 assert_eq!(
765 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("area-start"), 1)),
766 i16::MAX
767 );
768 }
769}