1use crate::{
4 CheapCloneStr, GenericGridTemplateComponent, GenericRepetition as _, GridAreaAxis, GridAreaEnd, GridContainerStyle,
5 GridPlacement, Line, NonNamedGridPlacement, RepetitionCount,
6};
7use core::{borrow::Borrow, cmp::Ordering, fmt::Debug};
8
9use super::{GridLine, OriginZeroLine, MAX_GRID_TRACKS};
10use crate::geometry::AbsoluteAxis;
11use crate::sys::DefaultCheapStr;
12use crate::sys::{Map, Vec};
13use smallvec::{smallvec, SmallVec};
14
15#[derive(Debug, Clone)]
18pub(crate) struct StrHasher<T: CheapCloneStr>(pub T);
19impl<T: CheapCloneStr> PartialOrd for StrHasher<T> {
20 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
21 Some(self.cmp(other))
22 }
23}
24impl<T: CheapCloneStr> Ord for StrHasher<T> {
25 fn cmp(&self, other: &Self) -> Ordering {
26 self.0.as_ref().cmp(other.0.as_ref())
27 }
28}
29impl<T: CheapCloneStr> PartialEq for StrHasher<T> {
30 fn eq(&self, other: &Self) -> bool {
31 other.0.as_ref() == self.0.as_ref()
32 }
33}
34impl<T: CheapCloneStr> Eq for StrHasher<T> {}
35#[cfg(feature = "std")]
36impl<T: CheapCloneStr> std::hash::Hash for StrHasher<T> {
37 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
38 self.0.as_ref().hash(state)
39 }
40}
41impl<T: CheapCloneStr> Borrow<str> for StrHasher<T> {
42 fn borrow(&self) -> &str {
43 self.0.as_ref()
44 }
45}
46
47pub(crate) type LinePositions = SmallVec<[u32; 4]>;
51
52type NamedGridLinesMap<S> = Map<StrHasher<S>, LinePositions>;
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59pub(crate) struct AreaLines {
60 row_start: u32,
62 row_end: u32,
64 column_start: u32,
66 column_end: u32,
68}
69
70impl AreaLines {
71 fn line(&self, axis: GridAreaAxis, end: GridAreaEnd) -> Option<&u32> {
73 let line = match (axis, end) {
74 (GridAreaAxis::Row, GridAreaEnd::Start) => &self.row_start,
75 (GridAreaAxis::Row, GridAreaEnd::End) => &self.row_end,
76 (GridAreaAxis::Column, GridAreaEnd::Start) => &self.column_start,
77 (GridAreaAxis::Column, GridAreaEnd::End) => &self.column_end,
78 };
79 Some(line).filter(|line| **line != 0)
80 }
81
82 fn line_mut(&mut self, axis: GridAreaAxis, end: GridAreaEnd) -> &mut u32 {
84 match (axis, end) {
85 (GridAreaAxis::Row, GridAreaEnd::Start) => &mut self.row_start,
86 (GridAreaAxis::Row, GridAreaEnd::End) => &mut self.row_end,
87 (GridAreaAxis::Column, GridAreaEnd::Start) => &mut self.column_start,
88 (GridAreaAxis::Column, GridAreaEnd::End) => &mut self.column_end,
89 }
90 }
91}
92
93pub(crate) type GridAreasMap<S> = Map<StrHasher<S>, AreaLines>;
98
99fn split_area_edge_name(name: &str) -> Option<(&str, GridAreaEnd)> {
101 if let Some(area_name) = name.strip_suffix("-start") {
102 Some((area_name, GridAreaEnd::Start))
103 } else if let Some(area_name) = name.strip_suffix("-end") {
104 Some((area_name, GridAreaEnd::End))
105 } else {
106 None
107 }
108}
109
110struct NamedLineResolverAxis<'a, S: CheapCloneStr> {
112 lines: &'a NamedGridLinesMap<S>,
114 areas: &'a GridAreasMap<S>,
116 axis: GridAreaAxis,
118 explicit_track_count: u16,
120}
121
122pub(crate) struct NamedLineResolver<S: CheapCloneStr> {
125 row_lines: NamedGridLinesMap<S>,
128 column_lines: NamedGridLinesMap<S>,
131 areas: GridAreasMap<S>,
133 area_column_count: u16,
135 area_row_count: u16,
137 explicit_column_count: u16,
140 explicit_row_count: u16,
143 column_line_name_pairs: Vec<(u32, S)>,
146 row_line_name_pairs: Vec<(u32, S)>,
149}
150
151fn upsert_line_name_map<S: CheapCloneStr>(
155 map: &mut NamedGridLinesMap<S>,
156 areas: &mut GridAreasMap<S>,
157 axis: GridAreaAxis,
158 key: S,
159 value: u32,
160) {
161 let mut area_line = 0;
165 if let Some((area_name, end)) = split_area_edge_name(key.as_ref()) {
166 if !areas.contains_key(area_name) {
168 areas.insert(StrHasher(S::from(area_name)), AreaLines::default());
169 }
170 let edge = areas.get_mut(area_name).unwrap().line_mut(axis, end);
171 area_line = *edge;
172 *edge = if *edge == 0 { value } else { (*edge).min(value) };
173 }
174
175 map.entry(StrHasher(key)).and_modify(|lines| lines.push(value)).or_insert_with(|| {
176 if area_line == 0 {
178 smallvec![value]
179 } else {
180 smallvec![area_line, value]
181 }
182 });
183}
184
185impl<S: CheapCloneStr> NamedLineResolverAxis<'_, S> {
186 fn area_line(&self, area_name: &str, end: GridAreaEnd) -> Option<&u32> {
188 self.areas.get(area_name)?.line(self.axis, end)
189 }
190
191 fn resolve_line_names(&self, line: &Line<GridPlacement<S>>) -> Line<NonNamedGridPlacement> {
193 let start_holder;
194 let start_line_resolved = if let GridPlacement::NamedLine(name, idx) = &line.start {
195 start_holder = GridPlacement::Line(self.find_named_line(name, *idx, GridAreaEnd::Start));
196 &start_holder
197 } else {
198 &line.start
199 };
200
201 let end_holder;
202 let end_line_resolved = if let GridPlacement::NamedLine(name, idx) = &line.end {
203 end_holder = GridPlacement::Line(self.find_named_line(name, *idx, GridAreaEnd::End));
204 &end_holder
205 } else {
206 &line.end
207 };
208
209 match (&start_line_resolved, &end_line_resolved) {
217 (GridPlacement::Line(start_line), GridPlacement::NamedSpan(name, idx)) if start_line.as_i16() != 0 => {
218 let after_start_line = start_line.into_origin_zero_line(self.explicit_track_count) + 1;
219 let end_line =
220 self.nth_line_forwards(self.lines_named(name.as_ref()), after_start_line, (*idx).max(1) as i32);
221 Line {
222 start: NonNamedGridPlacement::Line(*start_line),
223 end: NonNamedGridPlacement::Line(end_line.into_grid_line(self.explicit_track_count)),
224 }
225 }
226 (GridPlacement::NamedSpan(name, idx), GridPlacement::Line(end_line)) if end_line.as_i16() != 0 => {
227 let before_end_line = end_line.into_origin_zero_line(self.explicit_track_count) - 1;
228 let start_line =
229 self.nth_line_backwards(self.lines_named(name.as_ref()), before_end_line, (*idx).max(1) as i32);
230 Line {
231 start: NonNamedGridPlacement::Line(start_line.into_grid_line(self.explicit_track_count)),
232 end: NonNamedGridPlacement::Line(*end_line),
233 }
234 }
235 (start, end) => Line {
236 start: match start {
237 GridPlacement::Auto => NonNamedGridPlacement::Auto,
238 GridPlacement::Line(grid_line) => NonNamedGridPlacement::Line(*grid_line),
239 GridPlacement::Span(span) => NonNamedGridPlacement::Span(*span),
240 GridPlacement::NamedSpan(_, _) => NonNamedGridPlacement::Span(1),
241 _ => unreachable!(),
242 },
243 end: match end {
244 GridPlacement::Auto => NonNamedGridPlacement::Auto,
245 GridPlacement::Line(grid_line) => NonNamedGridPlacement::Line(*grid_line),
246 GridPlacement::Span(span) => NonNamedGridPlacement::Span(*span),
247 GridPlacement::NamedSpan(_, _) => NonNamedGridPlacement::Span(1),
248 _ => unreachable!(),
249 },
250 },
251 }
252 }
253
254 fn find_named_line(&self, name: &S, idx: i16, end: GridAreaEnd) -> GridLine {
259 let name = name.as_ref();
260
261 if idx == 0 {
265 if let Some(first_line) = self.area_line(name, end) {
266 return OriginZeroLine::clamped(oz_line(*first_line)).into_grid_line(self.explicit_track_count);
267 }
268 }
269
270 let lines = self.lines_named(name);
271 let line = if idx >= 0 {
272 self.nth_line_forwards(lines, OriginZeroLine(0), (idx as i32).max(1))
273 } else {
274 self.nth_line_backwards(lines, self.last_explicit_line(), -(idx as i32))
275 };
276 line.into_grid_line(self.explicit_track_count)
277 }
278
279 fn lines_named(&self, name: &str) -> &[u32] {
284 match self.lines.get(name) {
285 Some(lines) => lines.as_slice(),
286 None => split_area_edge_name(name)
287 .and_then(|(area_name, area_end)| self.area_line(area_name, area_end))
288 .map(core::slice::from_ref)
289 .unwrap_or(&[]),
290 }
291 }
292
293 fn last_explicit_line(&self) -> OriginZeroLine {
295 OriginZeroLine(self.explicit_track_count as i16)
296 }
297
298 fn nth_line_forwards(&self, lines: &[u32], first_line: OriginZeroLine, n: i32) -> OriginZeroLine {
305 let first_line = first_line.0.max(0) as i32;
306 let lines = &lines[lines.partition_point(|line| oz_line(*line) < first_line)..];
307 let line = match lines.get(n as usize - 1) {
308 Some(line) => oz_line(*line),
309 None => first_line.max(self.last_explicit_line().0 as i32 + 1) + (n - lines.len() as i32) - 1,
310 };
311 OriginZeroLine::clamped(line)
312 }
313
314 fn nth_line_backwards(&self, lines: &[u32], last_line: OriginZeroLine, n: i32) -> OriginZeroLine {
321 let last_line = last_line.0.min(self.last_explicit_line().0) as i32;
322 let lines = &lines[..lines.partition_point(|line| oz_line(*line) <= last_line)];
323 let line = match lines.len().checked_sub(n as usize) {
324 Some(index) => oz_line(lines[index]),
325 None => last_line.min(-1) - (n - lines.len() as i32) + 1,
326 };
327 OriginZeroLine::clamped(line)
328 }
329}
330
331fn oz_line(line: u32) -> i32 {
334 line as i32 - 1
335}
336
337impl<S: CheapCloneStr> NamedLineResolver<S> {
338 pub(crate) fn new(
340 style: &impl GridContainerStyle<CustomIdent = S>,
341 column_auto_repetitions: u16,
342 row_auto_repetitions: u16,
343 ) -> Self {
344 let mut column_lines: NamedGridLinesMap<S> = Map::new();
345 let mut row_lines: NamedGridLinesMap<S> = Map::new();
346 let mut areas: GridAreasMap<S> = Map::new();
347
348 let mut column_line_name_pairs: Vec<(u32, S)> = Vec::new();
349 let mut row_line_name_pairs: Vec<(u32, S)> = Vec::new();
350
351 let area_column_count = style.grid_template_area_column_count();
355 let area_row_count = style.grid_template_area_row_count();
356 if let Some(area_iter) = style.grid_template_areas() {
357 for area in area_iter.into_iter() {
358 let lines = AreaLines {
359 row_start: area.row_start as u32,
360 row_end: area.row_end as u32,
361 column_start: area.column_start as u32,
362 column_end: area.column_end as u32,
363 };
364 areas.insert(StrHasher(area.name.clone()), lines);
365 }
366 }
367
368 let mut current_line = 0;
369 if let Some(mut column_tracks) = style.grid_template_columns() {
370 if let Some(column_line_names_iter) = style.grid_template_column_names() {
371 for line_names in column_line_names_iter {
372 current_line += 1;
373 for line_name in line_names.into_iter() {
374 column_line_name_pairs.push((current_line, line_name.clone()));
375 upsert_line_name_map(
376 &mut column_lines,
377 &mut areas,
378 GridAreaAxis::Column,
379 line_name.clone(),
380 current_line,
381 );
382 }
383
384 if let Some(GenericGridTemplateComponent::Repeat(repeat)) = column_tracks.next() {
385 let repeat_count = match repeat.count() {
386 RepetitionCount::Count(count) => count,
387 RepetitionCount::AutoFill | RepetitionCount::AutoFit => column_auto_repetitions,
388 };
389
390 let line_name_set_count = repeat.lines_names().len() as u32;
396 let lines_per_repetition = repeat.track_count() as u32;
397 assert!(
398 line_name_set_count == 0 || line_name_set_count == lines_per_repetition + 1,
399 "grid template repetition must have no line name sets or exactly track count + 1 of them ({} tracks but {} line name sets)",
400 lines_per_repetition,
401 line_name_set_count,
402 );
403
404 for _ in 0..repeat_count {
405 for (line, line_name_set) in (current_line..).zip(repeat.lines_names()) {
406 for line_name in line_name_set {
407 column_line_name_pairs.push((line, line_name.clone()));
408 upsert_line_name_map(
409 &mut column_lines,
410 &mut areas,
411 GridAreaAxis::Column,
412 line_name.clone(),
413 line,
414 );
415 }
416 }
417 current_line += lines_per_repetition;
418
419 if current_line > MAX_GRID_TRACKS as u32 {
422 break;
423 }
424 }
425 if repeat_count > 0 {
427 current_line = current_line.saturating_sub(1);
428 }
429 }
430 }
431 }
432 }
433
434 let mut current_line = 0;
435 if let Some(mut row_tracks) = style.grid_template_rows() {
436 if let Some(row_line_names_iter) = style.grid_template_row_names() {
437 for line_names in row_line_names_iter {
438 current_line += 1;
439 for line_name in line_names.into_iter() {
440 row_line_name_pairs.push((current_line, line_name.clone()));
441 upsert_line_name_map(
442 &mut row_lines,
443 &mut areas,
444 GridAreaAxis::Row,
445 line_name.clone(),
446 current_line,
447 );
448 }
449
450 if let Some(GenericGridTemplateComponent::Repeat(repeat)) = row_tracks.next() {
451 let repeat_count = match repeat.count() {
452 RepetitionCount::Count(count) => count,
453 RepetitionCount::AutoFill | RepetitionCount::AutoFit => row_auto_repetitions,
454 };
455
456 let line_name_set_count = repeat.lines_names().len() as u32;
462 let lines_per_repetition = repeat.track_count() as u32;
463 assert!(
464 line_name_set_count == 0 || line_name_set_count == lines_per_repetition + 1,
465 "grid template repetition must have no line name sets or exactly track count + 1 of them ({} tracks but {} line name sets)",
466 lines_per_repetition,
467 line_name_set_count,
468 );
469
470 for _ in 0..repeat_count {
471 for (line, line_name_set) in (current_line..).zip(repeat.lines_names()) {
472 for line_name in line_name_set {
473 row_line_name_pairs.push((line, line_name.clone()));
474 upsert_line_name_map(
475 &mut row_lines,
476 &mut areas,
477 GridAreaAxis::Row,
478 line_name.clone(),
479 line,
480 );
481 }
482 }
483 current_line += lines_per_repetition;
484
485 if current_line > MAX_GRID_TRACKS as u32 {
488 break;
489 }
490 }
491 if repeat_count > 0 {
493 current_line = current_line.saturating_sub(1);
494 }
495 }
496 }
497 }
498 }
499 for lines in column_lines.values_mut() {
501 lines.sort_unstable();
502 lines.dedup();
503 }
504 for lines in row_lines.values_mut() {
506 lines.sort_unstable();
507 lines.dedup();
508 }
509
510 Self {
511 area_column_count,
512 area_row_count,
513 explicit_column_count: 0, explicit_row_count: 0, row_lines,
516 column_lines,
517 areas,
518 column_line_name_pairs,
519 row_line_name_pairs,
520 }
521 }
522
523 pub(crate) fn detailed_line_names(&self, axis: AbsoluteAxis) -> GridLineNames<S> {
532 let (pairs, explicit_track_count) = match axis {
533 AbsoluteAxis::Horizontal => (&self.column_line_name_pairs, self.explicit_column_count),
534 AbsoluteAxis::Vertical => (&self.row_line_name_pairs, self.explicit_row_count),
535 };
536
537 if pairs.is_empty() {
538 return GridLineNames::default();
539 }
540
541 let mut sorted_pairs: Vec<&(u32, S)> = pairs.iter().collect();
543 sorted_pairs.sort_by_key(|(line, _)| *line);
544
545 let line_count = explicit_track_count as usize + 1;
546 let mut line_names = GridLineNames::with_capacity(sorted_pairs.len(), line_count + 1);
547 let mut pair_iter = sorted_pairs.into_iter().peekable();
548 for line in 1..=(line_count as u32) {
549 line_names.start_line();
550 while let Some(&&(pair_line, ref name)) = pair_iter.peek() {
551 if pair_line != line {
552 break;
553 }
554 pair_iter.next();
555 if !line_names.current_line_contains(name.as_ref()) {
556 line_names.push_name(name.clone());
557 }
558 }
559 }
560 line_names
561 }
562
563 #[inline(always)]
565 pub(crate) fn resolve_row_names(&self, line: &Line<GridPlacement<S>>) -> Line<NonNamedGridPlacement> {
566 self.resolve_line_names(line, GridAreaAxis::Row)
567 }
568
569 #[inline(always)]
571 pub(crate) fn resolve_column_names(&self, line: &Line<GridPlacement<S>>) -> Line<NonNamedGridPlacement> {
572 self.resolve_line_names(line, GridAreaAxis::Column)
573 }
574
575 #[inline(always)]
577 pub(crate) fn resolve_line_names(
578 &self,
579 line: &Line<GridPlacement<S>>,
580 axis: GridAreaAxis,
581 ) -> Line<NonNamedGridPlacement> {
582 let (lines, explicit_track_count) = match axis {
583 GridAreaAxis::Row => (&self.row_lines, self.explicit_row_count),
584 GridAreaAxis::Column => (&self.column_lines, self.explicit_column_count),
585 };
586 NamedLineResolverAxis { lines, areas: &self.areas, axis, explicit_track_count }.resolve_line_names(line)
587 }
588
589 pub(crate) fn populate_detailed_line_resolvers(
592 self,
593 rows: &mut GridLineNames<S>,
594 columns: &mut GridLineNames<S>,
595 ) -> GridAreasMap<S> {
596 rows.resolver = self.row_lines;
597 columns.resolver = self.column_lines;
598 self.areas
599 }
600
601 pub(crate) fn area_column_count(&self) -> u16 {
603 self.area_column_count
604 }
605
606 pub(crate) fn area_row_count(&self) -> u16 {
608 self.area_row_count
609 }
610
611 pub(crate) fn set_explicit_column_count(&mut self, count: u16) {
613 self.explicit_column_count = count;
614 }
615
616 pub(crate) fn set_explicit_row_count(&mut self, count: u16) {
618 self.explicit_row_count = count;
619 }
620}
621
622impl<S: CheapCloneStr> Debug for NamedLineResolver<S> {
623 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
624 writeln!(f, "Grid Areas (row-start / column-start / row-end / column-end):")?;
625 for (name, area) in self.areas.iter() {
626 let AreaLines { row_start, column_start, row_end, column_end } = area;
627 writeln!(f, "{}: {row_start} / {column_start} / {row_end} / {column_end}", name.0.as_ref())?;
628 }
629
630 writeln!(f, "Grid Rows:")?;
631 for (name, lines) in self.row_lines.iter() {
632 write!(f, "{}: ", name.0.as_ref())?;
633 for line in lines {
634 write!(f, "{line} ")?;
635 }
636 writeln!(f)?;
637 }
638
639 writeln!(f, "Grid Columns:")?;
640 for (name, lines) in self.column_lines.iter() {
641 write!(f, "{}: ", name.0.as_ref())?;
642 for line in lines {
643 write!(f, "{line} ")?;
644 }
645 writeln!(f)?;
646 }
647
648 Ok(())
649 }
650}
651
652#[derive(Debug, Clone, PartialEq, Default)]
664pub struct GridLineNames<S: CheapCloneStr = DefaultCheapStr> {
665 names: Vec<S>,
667 offsets: Vec<u32>,
670 resolver: NamedGridLinesMap<S>,
672}
673
674impl<S: CheapCloneStr> GridLineNames<S> {
675 pub(crate) fn with_capacity(name_capacity: usize, offset_capacity: usize) -> Self {
677 let mut offsets = Vec::with_capacity(offset_capacity);
678 offsets.push(0);
679 Self { names: Vec::with_capacity(name_capacity), offsets, resolver: Map::new() }
680 }
681
682 pub(crate) fn start_line(&mut self) {
684 self.offsets.push(self.names.len() as u32);
685 }
686
687 pub(crate) fn push_name(&mut self, name: S) {
689 self.names.push(name);
690 *self.offsets.last_mut().unwrap() = self.names.len() as u32;
691 }
692
693 pub(crate) fn current_line_contains(&self, name: &str) -> bool {
695 self.line(self.line_count().wrapping_sub(1)).iter().any(|n| n.as_ref() == name)
696 }
697
698 pub(crate) fn resolve_line_names(
700 &self,
701 line: &Line<GridPlacement<S>>,
702 areas: &GridAreasMap<S>,
703 axis: GridAreaAxis,
704 explicit_track_count: u16,
705 ) -> Line<NonNamedGridPlacement> {
706 NamedLineResolverAxis { lines: &self.resolver, areas, axis, explicit_track_count }.resolve_line_names(line)
707 }
708
709 pub fn is_empty(&self) -> bool {
711 self.names.is_empty()
712 }
713
714 pub fn line_count(&self) -> usize {
716 self.offsets.len().saturating_sub(1)
717 }
718
719 pub fn line(&self, line_index: usize) -> &[S] {
722 match (self.offsets.get(line_index), self.offsets.get(line_index + 1)) {
723 (Some(&start), Some(&end)) => &self.names[start as usize..end as usize],
724 _ => &[],
725 }
726 }
727
728 pub fn iter(&self) -> GridLineNamesIter<'_, S> {
730 self.iter_padded(0, 0)
731 }
732
733 pub(crate) fn iter_padded(&self, leading_empty: usize, trailing_empty: usize) -> GridLineNamesIter<'_, S> {
737 GridLineNamesIter { names: &self.names, offsets: self.offsets.windows(2), leading_empty, trailing_empty }
738 }
739}
740
741impl<'a, S: CheapCloneStr> IntoIterator for &'a GridLineNames<S> {
742 type Item = &'a [S];
743 type IntoIter = GridLineNamesIter<'a, S>;
744 fn into_iter(self) -> Self::IntoIter {
745 self.iter()
746 }
747}
748
749#[derive(Debug, Clone)]
752pub struct GridLineNamesIter<'a, S: CheapCloneStr> {
753 names: &'a [S],
755 offsets: core::slice::Windows<'a, u32>,
757 leading_empty: usize,
759 trailing_empty: usize,
761}
762
763impl<'a, S: CheapCloneStr> Iterator for GridLineNamesIter<'a, S> {
764 type Item = &'a [S];
765
766 fn next(&mut self) -> Option<Self::Item> {
767 if self.leading_empty > 0 {
768 self.leading_empty -= 1;
769 return Some(&[]);
770 }
771 if let Some(window) = self.offsets.next() {
772 return Some(&self.names[window[0] as usize..window[1] as usize]);
773 }
774 if self.trailing_empty > 0 {
775 self.trailing_empty -= 1;
776 return Some(&[]);
777 }
778 None
779 }
780
781 fn size_hint(&self) -> (usize, Option<usize>) {
782 let len = self.leading_empty + self.offsets.len() + self.trailing_empty;
783 (len, Some(len))
784 }
785}
786
787impl<S: CheapCloneStr> ExactSizeIterator for GridLineNamesIter<'_, S> {}
788
789#[cfg(test)]
790mod tests {
791 use super::super::{MAX_OZ_LINE, MIN_OZ_LINE};
792 use super::*;
793 use crate::style::GenericGridPlacement;
794 use crate::sys::DefaultCheapStr;
795 use crate::GridTemplateAreas;
796 use crate::Style;
797 use crate::{GridTemplateArea, NonNamedGridPlacement};
798
799 fn resolver(explicit_track_count: u16) -> NamedLineResolver<DefaultCheapStr> {
800 let mut resolver = NamedLineResolver::new(&Style::DEFAULT, 0, 0);
801 resolver.set_explicit_column_count(explicit_track_count);
802 resolver
803 }
804
805 fn resolved_start_line(
806 resolver: &NamedLineResolver<DefaultCheapStr>,
807 placement: GridPlacement<DefaultCheapStr>,
808 ) -> i16 {
809 let resolved = resolver.resolve_column_names(&Line { start: placement, end: GridPlacement::Auto });
810 match resolved.start {
811 GenericGridPlacement::Line(line) => line.as_i16(),
812 _ => panic!("expected a resolved line"),
813 }
814 }
815
816 #[test]
817 fn extreme_missing_named_line_indices_do_not_overflow() {
818 let resolver = resolver(10_000);
820 assert_eq!(
821 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("missing"), i16::MAX)),
822 OriginZeroLine(MAX_OZ_LINE).into_grid_line(10_000).as_i16()
823 );
824 assert_eq!(
825 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("missing"), i16::MIN)),
826 OriginZeroLine(MIN_OZ_LINE).into_grid_line(10_000).as_i16()
827 );
828 }
829
830 #[test]
831 fn large_named_span_does_not_wrap_negative() {
832 let resolver = resolver(10_000);
833 let resolved = resolver.resolve_column_names(&Line {
834 start: GridPlacement::Line(GridLine::from(1)),
835 end: GridPlacement::NamedSpan(DefaultCheapStr::from("missing"), u16::MAX),
836 });
837 match resolved.end {
838 GenericGridPlacement::Line(line) => assert_eq!(line, OriginZeroLine(MAX_OZ_LINE).into_grid_line(10_000)),
839 _ => panic!("expected a resolved line"),
840 }
841 }
842
843 #[test]
844 fn area_lines_saturate_when_converted_to_grid_lines() {
845 let style = Style {
846 grid_template_areas: Some(GridTemplateAreas {
847 areas: vec![GridTemplateArea {
848 name: DefaultCheapStr::from("area"),
849 row_start: 1,
850 row_end: 2,
851 column_start: u16::MAX,
852 column_end: u16::MAX,
853 }],
854 row_count: 1,
855 column_count: u16::MAX,
856 }),
857 ..Style::DEFAULT
858 };
859 let resolver = NamedLineResolver::new(&style, 0, 0);
860 assert_eq!(
861 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("area-start"), 1)),
862 OriginZeroLine(MAX_OZ_LINE).into_grid_line(0).as_i16()
863 );
864 assert_eq!(
865 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("area"), 0)),
866 OriginZeroLine(MAX_OZ_LINE).into_grid_line(0).as_i16()
867 );
868 }
869
870 fn area_resolver(column_line_names: [&[&str]; 5]) -> NamedLineResolver<DefaultCheapStr> {
873 let style = Style {
874 grid_template_column_names: column_line_names
875 .iter()
876 .map(|names| names.iter().map(|name| DefaultCheapStr::from(*name)).collect())
877 .collect(),
878 grid_template_areas: Some(GridTemplateAreas {
879 areas: vec![GridTemplateArea {
880 name: DefaultCheapStr::from("area"),
881 row_start: 1,
882 row_end: 2,
883 column_start: 2,
884 column_end: 4,
885 }],
886 row_count: 1,
887 column_count: 4,
888 }),
889 ..Style::DEFAULT
890 };
891 let mut resolver = NamedLineResolver::new(&style, 0, 0);
892 resolver.set_explicit_column_count(4);
893 resolver.set_explicit_row_count(1);
894 resolver
895 }
896
897 fn named_line(name: &str, index: i16) -> GridPlacement<DefaultCheapStr> {
898 GridPlacement::NamedLine(DefaultCheapStr::from(name), index)
899 }
900
901 fn resolved_end_line(
902 resolver: &NamedLineResolver<DefaultCheapStr>,
903 placement: GridPlacement<DefaultCheapStr>,
904 ) -> i16 {
905 let resolved = resolver.resolve_column_names(&Line { start: GridPlacement::Auto, end: placement });
906 match resolved.end {
907 GenericGridPlacement::Line(line) => line.as_i16(),
908 _ => panic!("expected a resolved line"),
909 }
910 }
911
912 #[test]
913 fn bare_ident_resolves_to_area_edge() {
914 let resolver = area_resolver([&[], &[], &[], &[], &[]]);
915 assert_eq!(resolved_start_line(&resolver, named_line("area", 0)), 2);
916 assert_eq!(resolved_end_line(&resolver, named_line("area", 0)), 4);
917
918 let rows = resolver.resolve_row_names(&Line { start: named_line("area", 0), end: named_line("area", 0) });
919 assert_eq!(rows.start, NonNamedGridPlacement::Line(GridLine::from(1)));
920 assert_eq!(rows.end, NonNamedGridPlacement::Line(GridLine::from(2)));
921 }
922
923 #[test]
924 fn bare_ident_resolves_to_edge_of_implicitly_named_area() {
925 let resolver = area_resolver([&[], &["foo-start"], &["foo-start"], &["foo-end"], &["foo-end"]]);
926 assert_eq!(resolved_start_line(&resolver, named_line("foo", 0)), 2);
927 assert_eq!(resolved_end_line(&resolver, named_line("foo", 0)), 4);
928 }
929
930 #[test]
931 fn area_edges_are_lines_named_with_start_and_end_suffixes() {
932 let resolver = area_resolver([&[], &[], &[], &[], &[]]);
933 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 0)), 2);
934 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 1)), 2);
935 assert_eq!(resolved_start_line(&resolver, named_line("area-end", 1)), 4);
936 assert_eq!(resolved_start_line(&resolver, named_line("area-end", -1)), 4);
937 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 2)), 6);
939 }
940
941 #[test]
942 fn area_edges_are_merged_with_explicitly_named_lines() {
943 let resolver = area_resolver([&["area-start"], &[], &["area-start"], &["area-end"], &["area-end"]]);
945 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 1)), 1);
946 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 2)), 2);
947 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 3)), 3);
948 assert_eq!(resolved_start_line(&resolver, named_line("area-start", -1)), 3);
949 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 4)), 6);
950 assert_eq!(resolved_start_line(&resolver, named_line("area-end", 1)), 4);
952 assert_eq!(resolved_start_line(&resolver, named_line("area-end", 2)), 5);
953 assert_eq!(resolved_start_line(&resolver, named_line("area-end", 3)), 6);
954 assert_eq!(resolved_start_line(&resolver, named_line("area", 0)), 1);
956 assert_eq!(resolved_end_line(&resolver, named_line("area", 0)), 4);
957
958 let span = resolver.resolve_column_names(&Line {
959 start: GridPlacement::Line(GridLine::from(1)),
960 end: GridPlacement::NamedSpan(DefaultCheapStr::from("area-start"), 2),
961 });
962 assert_eq!(span.end, NonNamedGridPlacement::Line(GridLine::from(3)));
963 }
964
965 #[test]
966 fn area_edges_are_only_matched_by_bare_idents() {
967 let resolver = area_resolver([&[], &[], &[], &[], &[]]);
968 assert_eq!(resolved_start_line(&resolver, named_line("area", 1)), 6);
970 assert_eq!(resolved_start_line(&resolver, named_line("area", 2)), 7);
971
972 let span = resolver.resolve_column_names(&Line {
973 start: GridPlacement::Line(GridLine::from(1)),
974 end: GridPlacement::NamedSpan(DefaultCheapStr::from("area"), 0),
975 });
976 assert_eq!(span.end, NonNamedGridPlacement::Line(GridLine::from(6)));
977 }
978
979 #[test]
980 fn area_edge_is_the_first_line_with_the_edge_name() {
981 let resolver = area_resolver([&[], &[], &["area-start"], &[], &["area-end"]]);
984 assert_eq!(resolved_start_line(&resolver, named_line("area", 0)), 2);
985 assert_eq!(resolved_end_line(&resolver, named_line("area", 0)), 4);
986 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 1)), 2);
987 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 2)), 3);
988 assert_eq!(resolved_start_line(&resolver, named_line("area-end", 2)), 5);
989
990 let resolver = area_resolver([&[], &["area-start"], &[], &["area-end"], &[]]);
992 assert_eq!(resolved_start_line(&resolver, named_line("area", 0)), 2);
993 assert_eq!(resolved_end_line(&resolver, named_line("area", 0)), 4);
994 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 1)), 2);
995 assert_eq!(resolved_start_line(&resolver, named_line("area-start", 2)), 6);
996 assert_eq!(resolved_start_line(&resolver, named_line("area-end", 2)), 6);
997 }
998}