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;
10use crate::sys::{format, single_value_vec, Map, Vec};
12
13#[derive(Debug, Clone)]
16pub(crate) struct StrHasher<T: CheapCloneStr>(pub T);
17impl<T: CheapCloneStr> PartialOrd for StrHasher<T> {
18 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
19 Some(self.cmp(other))
20 }
21}
22impl<T: CheapCloneStr> Ord for StrHasher<T> {
23 fn cmp(&self, other: &Self) -> Ordering {
24 self.0.as_ref().cmp(other.0.as_ref())
25 }
26}
27impl<T: CheapCloneStr> PartialEq for StrHasher<T> {
28 fn eq(&self, other: &Self) -> bool {
29 other.0.as_ref() == self.0.as_ref()
30 }
31}
32impl<T: CheapCloneStr> Eq for StrHasher<T> {}
33#[cfg(feature = "std")]
34impl<T: CheapCloneStr> std::hash::Hash for StrHasher<T> {
35 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
36 self.0.as_ref().hash(state)
37 }
38}
39impl<T: CheapCloneStr> Borrow<str> for StrHasher<T> {
40 fn borrow(&self) -> &str {
41 self.0.as_ref()
42 }
43}
44
45pub(crate) struct NamedLineResolver<S: CheapCloneStr> {
48 row_lines: Map<StrHasher<S>, Vec<u32>>,
51 column_lines: Map<StrHasher<S>, Vec<u32>>,
54 areas: Map<StrHasher<S>, GridTemplateArea<S>>,
56 area_column_count: u16,
58 area_row_count: u16,
60 explicit_column_count: u16,
63 explicit_row_count: u16,
66}
67
68fn upsert_line_name_map<S: CheapCloneStr>(map: &mut Map<StrHasher<S>, Vec<u32>>, key: S, value: u32) {
70 map.entry(StrHasher(key)).and_modify(|lines| lines.push(value)).or_insert_with(|| single_value_vec(value));
71}
72
73impl<S: CheapCloneStr> NamedLineResolver<S> {
74 pub(crate) fn new(
76 style: &impl GridContainerStyle<CustomIdent = S>,
77 column_auto_repetitions: u16,
78 row_auto_repetitions: u16,
79 ) -> Self {
80 let mut areas: Map<StrHasher<S>, GridTemplateArea<_>> = Map::new();
81 let mut column_lines: Map<StrHasher<S>, Vec<u32>> = Map::new();
82 let mut row_lines: Map<StrHasher<S>, Vec<u32>> = Map::new();
83
84 let area_column_count = style.grid_template_area_column_count();
88 let area_row_count = style.grid_template_area_row_count();
89 if let Some(area_iter) = style.grid_template_areas() {
90 for area in area_iter.into_iter() {
91 areas.insert(StrHasher(area.name.clone()), area.clone());
93
94 let col_start_name = S::from(format!("{}-start", area.name.as_ref()));
95 upsert_line_name_map(&mut column_lines, col_start_name, area.column_start as u32);
96 let col_end_name = S::from(format!("{}-end", area.name.as_ref()));
97 upsert_line_name_map(&mut column_lines, col_end_name, area.column_end as u32);
98 let row_start_name = S::from(format!("{}-start", area.name.as_ref()));
99 upsert_line_name_map(&mut row_lines, row_start_name, area.row_start as u32);
100 let row_end_name = S::from(format!("{}-end", area.name.as_ref()));
101 upsert_line_name_map(&mut row_lines, row_end_name, area.row_end as u32);
102 }
103 }
104
105 let mut current_line = 0;
108 if let Some(mut column_tracks) = style.grid_template_columns() {
109 if let Some(column_line_names_iter) = style.grid_template_column_names() {
110 for line_names in column_line_names_iter {
111 current_line += 1;
112 for line_name in line_names.into_iter() {
113 column_lines
114 .entry(StrHasher(line_name.clone()))
115 .and_modify(|lines: &mut Vec<u32>| lines.push(current_line))
116 .or_insert_with(|| single_value_vec(current_line));
117 }
118
119 if let Some(GenericGridTemplateComponent::Repeat(repeat)) = column_tracks.next() {
120 let repeat_count = match repeat.count() {
121 RepetitionCount::Count(count) => count,
122 RepetitionCount::AutoFill | RepetitionCount::AutoFit => column_auto_repetitions,
123 };
124
125 for _ in 0..repeat_count {
126 for line_name_set in repeat.lines_names() {
127 for line_name in line_name_set {
128 upsert_line_name_map(&mut column_lines, line_name.clone(), current_line);
129 }
130 current_line += 1;
131 }
132 current_line -= 1;
134 }
135 if repeat_count > 0 {
137 current_line -= 1;
138 }
139 }
140 }
141 }
142 }
143 for lines in column_lines.values_mut() {
145 lines.sort_unstable();
146 lines.dedup();
147 }
148
149 let mut current_line = 0;
150 if let Some(mut row_tracks) = style.grid_template_rows() {
151 if let Some(row_line_names_iter) = style.grid_template_row_names() {
152 for line_names in row_line_names_iter {
153 current_line += 1;
154 for line_name in line_names.into_iter() {
155 row_lines
156 .entry(StrHasher(line_name.clone()))
157 .and_modify(|lines: &mut Vec<u32>| lines.push(current_line))
158 .or_insert_with(|| single_value_vec(current_line));
159 }
160
161 if let Some(GenericGridTemplateComponent::Repeat(repeat)) = row_tracks.next() {
162 let repeat_count = match repeat.count() {
163 RepetitionCount::Count(count) => count,
164 RepetitionCount::AutoFill | RepetitionCount::AutoFit => row_auto_repetitions,
165 };
166
167 for _ in 0..repeat_count {
168 for line_name_set in repeat.lines_names() {
169 for line_name in line_name_set {
170 upsert_line_name_map(&mut row_lines, line_name.clone(), current_line);
171 }
172 current_line += 1;
173 }
174 current_line -= 1;
176 }
177 if repeat_count > 0 {
179 current_line -= 1;
180 }
181 }
182 }
183 }
184 }
185 for lines in row_lines.values_mut() {
187 lines.sort_unstable();
188 lines.dedup();
189 }
190
191 Self {
192 area_column_count,
193 area_row_count,
194 explicit_column_count: 0, explicit_row_count: 0, areas,
197 row_lines,
198 column_lines,
199 }
200 }
201
202 #[inline(always)]
204 pub(crate) fn resolve_row_names(&self, line: &Line<GridPlacement<S>>) -> Line<NonNamedGridPlacement> {
205 self.resolve_line_names(line, GridAreaAxis::Row)
206 }
207
208 #[inline(always)]
210 pub(crate) fn resolve_column_names(&self, line: &Line<GridPlacement<S>>) -> Line<NonNamedGridPlacement> {
211 self.resolve_line_names(line, GridAreaAxis::Column)
212 }
213
214 #[inline(always)]
216 pub(crate) fn resolve_line_names(
217 &self,
218 line: &Line<GridPlacement<S>>,
219 axis: GridAreaAxis,
220 ) -> Line<NonNamedGridPlacement> {
221 let start_holder;
222 let start_line_resolved = if let GridPlacement::NamedLine(name, idx) = &line.start {
223 start_holder =
224 GridPlacement::Line(self.find_line_index(name, *idx as i32, axis, GridAreaEnd::Start, &|lines| lines));
225 &start_holder
226 } else {
227 &line.start
228 };
229
230 let end_holder;
231 let end_line_resolved = if let GridPlacement::NamedLine(name, idx) = &line.end {
232 end_holder =
233 GridPlacement::Line(self.find_line_index(name, *idx as i32, axis, GridAreaEnd::End, &|lines| lines));
234 &end_holder
235 } else {
236 &line.end
237 };
238
239 match (&start_line_resolved, &end_line_resolved) {
247 (GridPlacement::Line(start_line), GridPlacement::NamedSpan(name, idx)) => {
248 let explicit_track_count = match axis {
249 GridAreaAxis::Row => self.explicit_row_count as i32,
250 GridAreaAxis::Column => self.explicit_column_count as i32,
251 };
252 let normalized_start_line = if start_line.as_i16() > 0 {
253 start_line.as_i16() as u32
254 } else {
255 (explicit_track_count + 1 + start_line.as_i16() as i32).max(0) as u32
256 };
257 let end_line = self.find_line_index(name, *idx as i32, axis, GridAreaEnd::End, &|lines| {
258 let point = lines.partition_point(|line| *line <= normalized_start_line);
259 &lines[point..]
260 });
261 Line { start: NonNamedGridPlacement::Line(*start_line), end: NonNamedGridPlacement::Line(end_line) }
262 }
263 (GridPlacement::NamedSpan(name, idx), GridPlacement::Line(end_line)) => {
264 let explicit_track_count = match axis {
265 GridAreaAxis::Row => self.explicit_row_count as i32,
266 GridAreaAxis::Column => self.explicit_column_count as i32,
267 };
268 let normalized_end_line = if end_line.as_i16() > 0 {
269 end_line.as_i16() as u32
270 } else {
271 (explicit_track_count + 1 + end_line.as_i16() as i32).max(0) as u32
272 };
273 let start_line = self.find_line_index(name, *idx as i32, axis, GridAreaEnd::Start, &|lines| {
274 let point = lines.partition_point(|line| *line < normalized_end_line);
275 &lines[..point]
276 });
277 Line { start: NonNamedGridPlacement::Line(start_line), end: NonNamedGridPlacement::Line(*end_line) }
278 }
279 (start, end) => Line {
280 start: match start {
281 GridPlacement::Auto => NonNamedGridPlacement::Auto,
282 GridPlacement::Line(grid_line) => NonNamedGridPlacement::Line(*grid_line),
283 GridPlacement::Span(span) => NonNamedGridPlacement::Span(*span),
284 GridPlacement::NamedSpan(_, _) => NonNamedGridPlacement::Span(1),
285 _ => unreachable!(),
286 },
287 end: match end {
288 GridPlacement::Auto => NonNamedGridPlacement::Auto,
289 GridPlacement::Line(grid_line) => NonNamedGridPlacement::Line(*grid_line),
290 GridPlacement::Span(span) => NonNamedGridPlacement::Span(*span),
291 GridPlacement::NamedSpan(_, _) => NonNamedGridPlacement::Span(1),
292 _ => unreachable!(),
293 },
294 },
295 }
296 }
297
298 fn find_line_index(
300 &self,
301 name: &S,
302 idx: i32,
303 axis: GridAreaAxis,
304 end: GridAreaEnd,
305 filter_lines: &dyn Fn(&[u32]) -> &[u32],
306 ) -> GridLine {
307 let name = name.as_ref();
308 let mut idx = idx;
309 let explicit_track_count = match axis {
310 GridAreaAxis::Row => self.explicit_row_count as i32,
311 GridAreaAxis::Column => self.explicit_column_count as i32,
312 };
313
314 if idx == 0 {
316 idx = 1;
317 }
318
319 fn get_line(lines: &[u32], explicit_track_count: i32, idx: i32) -> i16 {
320 let abs_idx = idx.unsigned_abs() as usize;
321 let line = if abs_idx <= lines.len() {
322 if idx > 0 {
323 lines[abs_idx - 1] as i64
324 } else {
325 lines[lines.len() - abs_idx] as i64
326 }
327 } else {
328 let remaining_lines = (abs_idx - lines.len()) as i64 * idx.signum() as i64;
329 if idx > 0 {
330 explicit_track_count as i64 + 1 + remaining_lines
331 } else {
332 -(explicit_track_count as i64 + 1 + remaining_lines)
333 }
334 };
335 line.clamp(i16::MIN as i64, i16::MAX as i64) as i16
336 }
337
338 let line_lookup = match axis {
340 GridAreaAxis::Row => &self.row_lines,
341 GridAreaAxis::Column => &self.column_lines,
342 };
343 if let Some(lines) = line_lookup.get(name) {
344 return GridLine::from(get_line(filter_lines(lines), explicit_track_count, idx));
345 } else {
346 match end {
348 GridAreaEnd::Start => {
349 let implicit_name = format!("{name}-start");
350 if let Some(lines) = line_lookup.get(&*implicit_name) {
351 return GridLine::from(get_line(filter_lines(lines), explicit_track_count, idx));
353 }
354 }
355 GridAreaEnd::End => {
356 let implicit_name = format!("{name}-end");
357 if let Some(lines) = line_lookup.get(&*implicit_name) {
358 return GridLine::from(get_line(filter_lines(lines), explicit_track_count, idx));
360 }
361 }
362 }
363 }
364
365 let line = if idx > 0 {
373 explicit_track_count as i64 + 1 + idx as i64
374 } else {
375 -(explicit_track_count as i64 + 1 + idx as i64)
376 };
377
378 GridLine::from(line.clamp(i16::MIN as i64, i16::MAX as i64) as i16)
379 }
380
381 pub(crate) fn area_column_count(&self) -> u16 {
383 self.area_column_count
384 }
385
386 pub(crate) fn area_row_count(&self) -> u16 {
388 self.area_row_count
389 }
390
391 pub(crate) fn set_explicit_column_count(&mut self, count: u16) {
393 self.explicit_column_count = count;
394 }
395
396 pub(crate) fn set_explicit_row_count(&mut self, count: u16) {
398 self.explicit_row_count = count;
399 }
400}
401
402impl<S: CheapCloneStr> Debug for NamedLineResolver<S> {
403 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
404 writeln!(f, "Grid Areas:")?;
405 for area in self.areas.values() {
406 writeln!(
407 f,
408 "{}: row:{}/{} col: {}/{}",
409 area.name.as_ref(),
410 area.row_start,
411 area.row_end,
412 area.column_start,
413 area.column_end
414 )?;
415 }
416
417 writeln!(f, "Grid Rows:")?;
418 for (name, lines) in self.row_lines.iter() {
419 write!(f, "{}: ", name.0.as_ref())?;
420 for line in lines {
421 write!(f, "{line} ")?;
422 }
423 writeln!(f)?;
424 }
425
426 writeln!(f, "Grid Columns:")?;
427 for (name, lines) in self.column_lines.iter() {
428 write!(f, "{}: ", name.0.as_ref())?;
429 for line in lines {
430 write!(f, "{line} ")?;
431 }
432 writeln!(f)?;
433 }
434
435 Ok(())
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use crate::style::GenericGridPlacement;
443 use crate::sys::DefaultCheapStr;
444 use crate::GridTemplateAreas;
445 use crate::Style;
446
447 fn resolver(explicit_track_count: u16) -> NamedLineResolver<DefaultCheapStr> {
448 let mut resolver = NamedLineResolver::new(&Style::DEFAULT, 0, 0);
449 resolver.set_explicit_column_count(explicit_track_count);
450 resolver
451 }
452
453 fn resolved_start_line(
454 resolver: &NamedLineResolver<DefaultCheapStr>,
455 placement: GridPlacement<DefaultCheapStr>,
456 ) -> i16 {
457 let resolved = resolver.resolve_column_names(&Line { start: placement, end: GridPlacement::Auto });
458 match resolved.start {
459 GenericGridPlacement::Line(line) => line.as_i16(),
460 _ => panic!("expected a resolved line"),
461 }
462 }
463
464 #[test]
465 fn extreme_missing_named_line_indices_do_not_overflow() {
466 let resolver = resolver(10_000);
467 assert_eq!(
468 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("missing"), i16::MAX)),
469 i16::MAX
470 );
471 assert_eq!(
472 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("missing"), i16::MIN)),
473 22_767
474 );
475 }
476
477 #[test]
478 fn large_named_span_does_not_wrap_negative() {
479 let resolver = resolver(10_000);
480 let resolved = resolver.resolve_column_names(&Line {
481 start: GridPlacement::Line(GridLine::from(1)),
482 end: GridPlacement::NamedSpan(DefaultCheapStr::from("missing"), u16::MAX),
483 });
484 match resolved.end {
485 GenericGridPlacement::Line(line) => assert_eq!(line.as_i16(), i16::MAX),
486 _ => panic!("expected a resolved line"),
487 }
488 }
489
490 #[test]
491 fn area_lines_saturate_when_converted_to_grid_lines() {
492 let style = Style {
493 grid_template_areas: Some(GridTemplateAreas {
494 areas: vec![GridTemplateArea {
495 name: DefaultCheapStr::from("area"),
496 row_start: 1,
497 row_end: 2,
498 column_start: u16::MAX,
499 column_end: u16::MAX,
500 }],
501 row_count: 1,
502 column_count: u16::MAX,
503 }),
504 ..Style::DEFAULT
505 };
506 let resolver = NamedLineResolver::new(&style, 0, 0);
507 assert_eq!(
508 resolved_start_line(&resolver, GridPlacement::NamedLine(DefaultCheapStr::from("area-start"), 1)),
509 i16::MAX
510 );
511 }
512}