1use super::*;
4
5pub(crate) fn is_subgrid(s: &str) -> bool {
8 let t = s.trim();
9 if t.eq_ignore_ascii_case("subgrid") {
10 return true;
11 }
12 if t.get(..7).is_some_and(|h| h.eq_ignore_ascii_case("subgrid")) && t.as_bytes().get(7).is_some_and(|b| b.is_ascii_whitespace()) {
13 return true;
14 }
15 false
16}
17
18#[derive(Debug, Clone)]
19pub(crate) struct SubgridContext {
20 pub col_widths: Vec<i32>,
21 pub col_gap: i32,
22}
23
24pub(crate) static SUBGRID_CTX: spin::Mutex<Option<SubgridContext>> = spin::Mutex::new(None);
25
26pub(crate) fn set_subgrid_ctx(ctx: Option<SubgridContext>) -> Option<SubgridContext> {
27 let mut lock = SUBGRID_CTX.lock();
28 let prev = lock.clone();
29 *lock = ctx;
30 prev
31}
32
33pub(crate) fn get_subgrid_ctx() -> Option<SubgridContext> {
34 SUBGRID_CTX.lock().clone()
35}
36
37pub(crate) fn parse_grid_template_columns(s: &str, avail_w: i32) -> Vec<i32> {
40 parse_grid_template_columns_gap(s, avail_w, 0)
41}
42
43pub(crate) fn parse_grid_template_columns_gap(s: &str, avail_w: i32, gap: i32) -> Vec<i32> {
51 let gap = gap.max(0);
52 let s = s.trim();
53 if s.is_empty() {
54 return alloc::vec![avail_w];
55 }
56 if let Some(inner) = s.strip_prefix("repeat(").and_then(|t| t.strip_suffix(')')) {
58 if let Some((count_str, rest)) = inner.split_once(',') {
59 let count_s = count_str.trim();
60 let col_def = rest.trim();
61 let min_w = if let Some(mm) = col_def
63 .strip_prefix("minmax(")
64 .and_then(|t| t.strip_suffix(')'))
65 {
66 mm.split_once(',')
67 .and_then(|(a, _)| resolve_track_tok_opt(a.trim(), avail_w))
68 .unwrap_or(200)
69 } else {
70 parse_px_like(col_def).unwrap_or(avail_w)
71 };
72 let n: usize = if count_s == "auto-fit" || count_s == "auto-fill" {
73 (libm::floorf((avail_w + gap) as f32 / (min_w.max(1) + gap) as f32) as usize).max(1)
74 } else {
75 count_s.parse::<usize>().unwrap_or(1).max(1)
76 };
77 let is_fixed_px = !col_def.ends_with("fr")
79 && !col_def.starts_with("minmax(")
80 && !col_def.ends_with('%')
81 && parse_px_like(col_def).is_some();
82 let col_w = if is_fixed_px {
83 parse_px_like(col_def).unwrap_or(1).max(1)
84 } else {
85 ((avail_w - gap * (n as i32 - 1)) / n as i32).max(1)
86 };
87 return (0..n).map(|_| col_w).collect();
88 }
89 }
90 let tokens: alloc::vec::Vec<&str> = s.split_whitespace().collect();
92 if tokens.is_empty() {
93 return alloc::vec![avail_w];
94 }
95 fn resolve_track_tok_opt(tok: &str, avail_w: i32) -> Option<i32> {
100 if let Some(pct) = tok.strip_suffix('%') {
101 return pct
102 .trim()
103 .parse::<f32>()
104 .ok()
105 .map(|p| round_f32_to_i32(avail_w as f32 * p / 100.0));
106 }
107 parse_px_like(tok)
108 }
109 fn resolve_track_tok(tok: &str, avail_w: i32) -> i32 {
110 resolve_track_tok_opt(tok, avail_w).unwrap_or(0)
111 }
112 let mut fixed_total = 0i32;
114 let mut fr_total = 0.0f32;
115 for tok in &tokens {
116 if let Some(fr) = tok.strip_suffix("fr") {
117 fr_total += fr.trim().parse::<f32>().unwrap_or(1.0);
118 } else {
119 fixed_total += resolve_track_tok(tok, avail_w);
120 }
121 }
122 let gaps_total = gap * (tokens.len() as i32 - 1).max(0);
123 let remaining = (avail_w - fixed_total - gaps_total).max(0);
124 tokens
125 .iter()
126 .map(|tok| {
127 if let Some(fr) = tok.strip_suffix("fr") {
128 let f = fr.trim().parse::<f32>().unwrap_or(1.0);
129 if fr_total > 0.0 {
130 round_f32_to_i32(remaining as f32 * f / fr_total)
131 } else {
132 remaining
133 }
134 } else {
135 resolve_track_tok(tok, avail_w)
136 }
137 })
138 .collect()
139}
140
141#[derive(Clone, Copy, Debug, PartialEq)]
143pub(crate) enum GridTrack {
144 Px(i32),
145 Fr(f32),
146 Auto,
147 MinContent,
148 MaxContent,
149 FitContent(i32),
150 MinMax {
153 min_px: i32,
154 max_fr: f32,
155 max_px: i32,
156 max_is_fr: bool,
157 max_is_auto: bool,
158 },
159}
160
161pub(crate) fn split_top_level_ws(s: &str) -> Vec<&str> {
164 let mut out = Vec::new();
165 let mut depth = 0i32;
166 let mut start = 0usize;
167 let mut in_tok = false;
168 for (i, ch) in s.char_indices() {
169 match ch {
170 '(' => depth += 1,
171 ')' => depth -= 1,
172 c if c.is_whitespace() && depth == 0 => {
173 if in_tok {
174 if let Some(t) = s.get(start..i) {
175 out.push(t);
176 }
177 in_tok = false;
178 }
179 continue;
180 }
181 _ => {}
182 }
183 if !in_tok {
184 start = i;
185 in_tok = true;
186 }
187 }
188 if in_tok {
189 if let Some(t) = s.get(start..) {
190 out.push(t);
191 }
192 }
193 out
194}
195
196pub(crate) fn parse_one_grid_track(tok: &str, base: i32) -> GridTrack {
199 let tok = tok.trim();
200 if tok.is_empty() || tok.eq_ignore_ascii_case("auto") {
201 return GridTrack::Auto;
202 }
203 if tok.eq_ignore_ascii_case("min-content") {
204 return GridTrack::MinContent;
205 }
206 if tok.eq_ignore_ascii_case("max-content") {
207 return GridTrack::MaxContent;
208 }
209 if let Some(fit) = tok
210 .strip_prefix("fit-content(")
211 .and_then(|t| t.strip_suffix(')'))
212 {
213 let limit = match parse_length_value(Some(fit.trim())) {
214 Some(LengthValue::Px(px)) => px.max(0),
215 Some(LengthValue::Percent(p)) => round_f32_to_i32(base as f32 * p).max(0),
216 _ => parse_px_like(fit.trim()).unwrap_or(base).max(0),
217 };
218 return GridTrack::FitContent(limit);
219 }
220 if let Some(fr) = tok.strip_suffix("fr") {
221 return GridTrack::Fr(fr.trim().parse::<f32>().unwrap_or(1.0).max(0.0));
222 }
223 if let Some(mm) = tok
224 .strip_prefix("minmax(")
225 .and_then(|t| t.strip_suffix(')'))
226 {
227 if let Some((a, b)) = mm.split_once(',') {
229 let min_px = match parse_length_value(Some(a.trim())) {
230 Some(LengthValue::Px(px)) => px.max(0),
231 Some(LengthValue::Percent(p)) => round_f32_to_i32(base as f32 * p).max(0),
232 _ => 0, };
234 let bt = b.trim();
235 if let Some(fr) = bt.strip_suffix("fr") {
236 return GridTrack::MinMax {
237 min_px,
238 max_fr: fr.trim().parse::<f32>().unwrap_or(1.0).max(0.0),
239 max_px: 0,
240 max_is_fr: true,
241 max_is_auto: false,
242 };
243 }
244 let (max_px, max_is_auto) = match parse_length_value(Some(bt)) {
245 Some(LengthValue::Px(px)) => (px.max(0), false),
246 Some(LengthValue::Percent(p)) => (round_f32_to_i32(base as f32 * p).max(0), false),
247 _ => (0, true), };
249 return GridTrack::MinMax {
250 min_px,
251 max_fr: 0.0,
252 max_px,
253 max_is_fr: false,
254 max_is_auto,
255 };
256 }
257 }
258 match parse_length_value(Some(tok)) {
259 Some(LengthValue::Px(px)) => GridTrack::Px(px),
260 Some(LengthValue::Percent(p)) => GridTrack::Px(round_f32_to_i32(base as f32 * p)),
261 _ => GridTrack::Auto,
262 }
263}
264
265pub(crate) fn parse_grid_tracks(s: &str, base: i32) -> Vec<GridTrack> {
268 let s = s.trim();
269 if s.is_empty() {
270 return Vec::new();
271 }
272 let mut out = Vec::new();
273 for tok in split_top_level_ws(s) {
274 if let Some(inner) = tok
275 .strip_prefix("repeat(")
276 .and_then(|t| t.strip_suffix(')'))
277 {
278 if let Some((count_str, rest)) = inner.split_once(',') {
279 let count_s = count_str.trim();
280 let track = parse_one_grid_track(rest.trim(), base);
281 let n: usize = if count_s == "auto-fit" || count_s == "auto-fill" {
282 let min_w = match track {
283 GridTrack::Px(px) => px.max(1),
284 GridTrack::MinMax { min_px, .. } => min_px.max(1),
285 _ => 1,
286 };
287 (libm::floorf(base as f32 / min_w as f32) as usize).max(1)
288 } else {
289 count_s.parse::<usize>().unwrap_or(1).max(1)
290 };
291 for _ in 0..n {
292 out.push(track);
293 }
294 continue;
295 }
296 }
297 out.push(parse_one_grid_track(tok, base));
298 }
299 out
300}
301
302pub(crate) fn grid_axis_placement(
307 b: &LayoutBox,
308 short: &str,
309 start_p: &str,
310 end_p: &str,
311) -> (Option<usize>, usize) {
312 let (sv, ev) = if let Some(v) = box_style_value(b, short) {
313 if let Some((a, c)) = v.split_once('/') {
314 (Some(String::from(a.trim())), Some(String::from(c.trim())))
315 } else {
316 (Some(String::from(v.trim())), None)
317 }
318 } else {
319 (
320 box_style_value(b, start_p).map(|s| String::from(s.trim())),
321 box_style_value(b, end_p).map(|s| String::from(s.trim())),
322 )
323 };
324 let mut start: Option<usize> = None;
325 let mut span: usize = 1;
326 if let Some(sv) = &sv {
327 if let Some(n) = sv.strip_prefix("span ") {
328 span = n.trim().parse::<usize>().unwrap_or(1).max(1);
329 } else if let Ok(line) = sv.parse::<i32>() {
330 if line >= 1 {
331 start = Some((line - 1) as usize);
332 }
333 }
334 }
335 if let Some(ev) = &ev {
336 if let Some(n) = ev.strip_prefix("span ") {
337 span = n.trim().parse::<usize>().unwrap_or(1).max(1);
338 } else if let Ok(eline) = ev.parse::<i32>() {
339 if let Some(s0) = start {
340 let e0 = (eline - 1).max(0) as usize;
341 if e0 > s0 {
342 span = e0 - s0;
343 }
344 }
345 }
346 }
347 (start, span)
348}
349
350pub(crate) fn parse_grid_template_areas(s: &str) -> Vec<Vec<String>> {
354 let mut rows = Vec::new();
355 let mut chars = s.chars().peekable();
356 while let Some(c) = chars.next() {
357 if c == '"' || c == '\'' {
358 let quote = c;
359 let mut row_str = String::new();
360 for c2 in chars.by_ref() {
361 if c2 == quote {
362 break;
363 }
364 row_str.push(c2);
365 }
366 let row: Vec<String> = row_str
367 .split_whitespace()
368 .map(String::from)
369 .collect();
370 if !row.is_empty() {
371 rows.push(row);
372 }
373 }
374 }
375 rows
376}
377
378pub(crate) fn find_grid_area(
381 template_areas: &[Vec<String>],
382 name: &str,
383) -> Option<(usize, usize, usize, usize)> {
384 if name == "." {
385 return None;
386 }
387 let mut min_r = usize::MAX;
388 let mut max_r = 0usize;
389 let mut min_c = usize::MAX;
390 let mut max_c = 0usize;
391 let mut found = false;
392 for (r, row) in template_areas.iter().enumerate() {
393 for (c, cell) in row.iter().enumerate() {
394 if cell == name {
395 found = true;
396 min_r = min_r.min(r);
397 max_r = max_r.max(r);
398 min_c = min_c.min(c);
399 max_c = max_c.max(c);
400 }
401 }
402 }
403 if !found {
404 return None;
405 }
406 Some((min_r, min_c, max_r - min_r + 1, max_c - min_c + 1))
407}
408
409pub(crate) fn grid_ensure_row(occ: &mut Vec<Vec<bool>>, r: usize, n_cols: usize) {
411 while occ.len() <= r {
412 occ.push(alloc::vec![false; n_cols]);
413 }
414}
415
416pub(crate) fn grid_ensure_col(occ: &mut [Vec<bool>], min_cols: usize) {
420 for row in occ.iter_mut() {
421 while row.len() < min_cols {
422 row.push(false);
423 }
424 }
425}
426
427pub(crate) fn grid_cells_free(
429 occ: &[Vec<bool>],
430 r: usize,
431 c: usize,
432 row_span: usize,
433 col_span: usize,
434 n_cols: usize,
435) -> bool {
436 if c + col_span > n_cols {
437 return false;
438 }
439 for rr in r..r + row_span {
440 let Some(row) = occ.get(rr) else { continue };
441 for cc in c..c + col_span {
442 if row.get(cc).copied().unwrap_or(false) {
443 return false;
444 }
445 }
446 }
447 true
448}
449
450pub(crate) fn grid_mark(
452 occ: &mut [Vec<bool>],
453 r: usize,
454 c: usize,
455 row_span: usize,
456 col_span: usize,
457 n_cols: usize,
458) {
459 for rr in r..r + row_span {
460 let Some(row) = occ.get_mut(rr) else { continue };
461 for cc in c..(c + col_span).min(n_cols) {
462 if let Some(cell) = row.get_mut(cc) {
463 *cell = true;
464 }
465 }
466 }
467}
468
469pub(crate) fn grid_col_x(col_widths: &[i32], col_gap: i32, col: usize) -> i32 {
471 let mut x = 0i32;
472 for (i, w) in col_widths.iter().enumerate() {
473 if i >= col {
474 break;
475 }
476 x += w + col_gap;
477 }
478 x
479}
480
481pub(crate) fn grid_span_width(col_widths: &[i32], col_gap: i32, col: usize, col_span: usize) -> i32 {
483 let n = col_widths.len();
484 if n == 0 {
485 return 1;
486 }
487 let end = (col + col_span).min(n);
488 let start = col.min(n.saturating_sub(1));
489 let mut w = 0i32;
490 for item in col_widths.iter().take(end).skip(start) {
491 w += *item;
492 }
493 w += (end.saturating_sub(start).saturating_sub(1) as i32) * col_gap;
494 w.max(1)
495}
496
497
498pub(crate) fn flex_factors(template: &str, n: usize) -> Vec<Option<f32>> {
503 let fr_of = |tok: &str| -> Option<f32> {
504 let f = tok.trim().strip_suffix("fr")?.trim().parse::<f32>().ok()?;
505 (f > 0.0).then_some(f)
506 };
507 let t = template.trim();
508 let mut out: Vec<Option<f32>> =
509 if let Some(inner) = t.strip_prefix("repeat(").and_then(|x| x.strip_suffix(')')) {
510 let f = inner.split_once(',').and_then(|(_, def)| fr_of(def));
511 alloc::vec![f; n]
512 } else {
513 t.split_whitespace().map(fr_of).collect()
514 };
515 out.resize(n, None);
516 out
517}
518
519pub(crate) fn expand_flexible_tracks(base: &[i32], flex: &[Option<f32>], mins: &[i32]) -> Vec<i32> {
534 let n = base.len();
535 let f = |i: usize| flex.get(i).copied().flatten();
536 let m = |i: usize| mins.get(i).copied().unwrap_or(0).max(0);
537 let flex_idx: Vec<usize> = (0..n).filter(|&i| f(i).is_some()).collect();
538 if flex_idx.iter().all(|&i| base[i] >= m(i)) {
539 return base.to_vec();
540 }
541 let space: i32 = flex_idx.iter().map(|&i| base[i]).sum();
542 let mut frozen = alloc::vec![false; n];
543 let mut hyp;
544 loop {
545 let used: i32 = flex_idx.iter().filter(|&&i| frozen[i]).map(|&i| m(i)).sum();
546 let sum_f: f32 = flex_idx
547 .iter()
548 .filter(|&&i| !frozen[i])
549 .filter_map(|&i| f(i))
550 .sum();
551 hyp = if sum_f > 0.0 { (space - used).max(0) as f32 / sum_f } else { 0.0 };
552 let mut changed = false;
553 for &i in &flex_idx {
554 if !frozen[i] && f(i).unwrap_or(0.0) * hyp < m(i) as f32 {
555 frozen[i] = true;
556 changed = true;
557 }
558 }
559 if !changed {
560 break;
561 }
562 }
563 let mut out = base.to_vec();
564 for &i in &flex_idx {
565 out[i] = if frozen[i] {
566 m(i)
567 } else {
568 round_f32_to_i32(f(i).unwrap_or(0.0) * hyp)
569 };
570 }
571 if let Some(&last) = flex_idx.iter().rev().find(|&&i| !frozen[i]) {
574 let total: i32 = flex_idx.iter().map(|&i| out[i]).sum();
575 out[last] = (out[last] + space - total).max(0);
576 }
577 out
578}
579
580pub(crate) fn grid_column_count_for_intrinsic(template: &str) -> usize {
585 let t = template.trim();
586 if t.is_empty() {
587 return 1;
588 }
589 if let Some(inner) = t.strip_prefix("repeat(").and_then(|x| x.strip_suffix(')')) {
590 let count = inner.split_once(',').map(|(c, _)| c.trim()).unwrap_or("1");
591 return count.parse::<usize>().unwrap_or(1).max(1);
592 }
593 split_top_level_ws(t).len().max(1)
594}
595
596pub(crate) fn grid_intrinsic_widths(
605 n_cols: usize,
606 fixed: &[Option<i32>],
607 items: &[(usize, i32, i32)],
608 gap: i32,
609) -> (i32, i32) {
610 if n_cols == 0 {
611 return (0, 0);
612 }
613 let mut mins = alloc::vec![0i32; n_cols];
614 let mut maxs = alloc::vec![0i32; n_cols];
615 for &(c, mn, mx) in items {
616 if c < n_cols {
617 mins[c] = mins[c].max(mn.max(0));
618 maxs[c] = maxs[c].max(mx.max(0));
619 }
620 }
621 for c in 0..n_cols {
622 if let Some(px) = fixed.get(c).copied().flatten() {
623 mins[c] = px.max(0);
624 maxs[c] = px.max(0);
625 }
626 }
627 let gaps = gap.max(0) * (n_cols as i32 - 1);
628 let minc = mins.iter().sum::<i32>() + gaps;
629 let maxc = maxs.iter().sum::<i32>() + gaps;
630 (minc, maxc.max(minc))
631}