1use super::*;
4
5pub(crate) fn parse_grid_template_columns(s: &str, avail_w: i32) -> Vec<i32> {
8 parse_grid_template_columns_gap(s, avail_w, 0)
9}
10
11pub(crate) fn parse_grid_template_columns_gap(s: &str, avail_w: i32, gap: i32) -> Vec<i32> {
19 let gap = gap.max(0);
20 let s = s.trim();
21 if s.is_empty() {
22 return alloc::vec![avail_w];
23 }
24 if let Some(inner) = s.strip_prefix("repeat(").and_then(|t| t.strip_suffix(')')) {
26 if let Some((count_str, rest)) = inner.split_once(',') {
27 let count_s = count_str.trim();
28 let col_def = rest.trim();
29 let min_w = if let Some(mm) = col_def
31 .strip_prefix("minmax(")
32 .and_then(|t| t.strip_suffix(')'))
33 {
34 mm.split_once(',')
35 .and_then(|(a, _)| resolve_track_tok_opt(a.trim(), avail_w))
36 .unwrap_or(200)
37 } else {
38 parse_px_like(col_def).unwrap_or(avail_w)
39 };
40 let n: usize = if count_s == "auto-fit" || count_s == "auto-fill" {
41 (libm::floorf((avail_w + gap) as f32 / (min_w.max(1) + gap) as f32) as usize).max(1)
42 } else {
43 count_s.parse::<usize>().unwrap_or(1).max(1)
44 };
45 let is_fixed_px = !col_def.ends_with("fr")
47 && !col_def.starts_with("minmax(")
48 && !col_def.ends_with('%')
49 && parse_px_like(col_def).is_some();
50 let col_w = if is_fixed_px {
51 parse_px_like(col_def).unwrap_or(1).max(1)
52 } else {
53 ((avail_w - gap * (n as i32 - 1)) / n as i32).max(1)
54 };
55 return (0..n).map(|_| col_w).collect();
56 }
57 }
58 let tokens: alloc::vec::Vec<&str> = s.split_whitespace().collect();
60 if tokens.is_empty() {
61 return alloc::vec![avail_w];
62 }
63 fn resolve_track_tok_opt(tok: &str, avail_w: i32) -> Option<i32> {
68 if let Some(pct) = tok.strip_suffix('%') {
69 return pct
70 .trim()
71 .parse::<f32>()
72 .ok()
73 .map(|p| round_f32_to_i32(avail_w as f32 * p / 100.0));
74 }
75 parse_px_like(tok)
76 }
77 fn resolve_track_tok(tok: &str, avail_w: i32) -> i32 {
78 resolve_track_tok_opt(tok, avail_w).unwrap_or(0)
79 }
80 let mut fixed_total = 0i32;
82 let mut fr_total = 0.0f32;
83 for tok in &tokens {
84 if let Some(fr) = tok.strip_suffix("fr") {
85 fr_total += fr.trim().parse::<f32>().unwrap_or(1.0);
86 } else {
87 fixed_total += resolve_track_tok(tok, avail_w);
88 }
89 }
90 let gaps_total = gap * (tokens.len() as i32 - 1).max(0);
91 let remaining = (avail_w - fixed_total - gaps_total).max(0);
92 tokens
93 .iter()
94 .map(|tok| {
95 if let Some(fr) = tok.strip_suffix("fr") {
96 let f = fr.trim().parse::<f32>().unwrap_or(1.0);
97 if fr_total > 0.0 {
98 round_f32_to_i32(remaining as f32 * f / fr_total)
99 } else {
100 remaining
101 }
102 } else {
103 resolve_track_tok(tok, avail_w)
104 }
105 })
106 .collect()
107}
108
109#[derive(Clone, Copy)]
111pub(crate) enum GridTrack {
112 Px(i32),
113 Fr(f32),
114 Auto,
115 MinMax {
118 min_px: i32,
119 max_fr: f32,
120 max_px: i32,
121 max_is_fr: bool,
122 max_is_auto: bool,
123 },
124}
125
126pub(crate) fn split_top_level_ws(s: &str) -> Vec<&str> {
129 let mut out = Vec::new();
130 let mut depth = 0i32;
131 let mut start = 0usize;
132 let mut in_tok = false;
133 for (i, ch) in s.char_indices() {
134 match ch {
135 '(' => depth += 1,
136 ')' => depth -= 1,
137 c if c.is_whitespace() && depth == 0 => {
138 if in_tok {
139 if let Some(t) = s.get(start..i) {
140 out.push(t);
141 }
142 in_tok = false;
143 }
144 continue;
145 }
146 _ => {}
147 }
148 if !in_tok {
149 start = i;
150 in_tok = true;
151 }
152 }
153 if in_tok {
154 if let Some(t) = s.get(start..) {
155 out.push(t);
156 }
157 }
158 out
159}
160
161pub(crate) fn parse_one_grid_track(tok: &str, base: i32) -> GridTrack {
164 let tok = tok.trim();
165 if tok.is_empty() || tok.eq_ignore_ascii_case("auto") {
166 return GridTrack::Auto;
167 }
168 if let Some(fr) = tok.strip_suffix("fr") {
169 return GridTrack::Fr(fr.trim().parse::<f32>().unwrap_or(1.0).max(0.0));
170 }
171 if let Some(mm) = tok
172 .strip_prefix("minmax(")
173 .and_then(|t| t.strip_suffix(')'))
174 {
175 if let Some((a, b)) = mm.split_once(',') {
177 let min_px = match parse_length_value(Some(a.trim())) {
178 Some(LengthValue::Px(px)) => px.max(0),
179 Some(LengthValue::Percent(p)) => round_f32_to_i32(base as f32 * p).max(0),
180 _ => 0, };
182 let bt = b.trim();
183 if let Some(fr) = bt.strip_suffix("fr") {
184 return GridTrack::MinMax {
185 min_px,
186 max_fr: fr.trim().parse::<f32>().unwrap_or(1.0).max(0.0),
187 max_px: 0,
188 max_is_fr: true,
189 max_is_auto: false,
190 };
191 }
192 let (max_px, max_is_auto) = match parse_length_value(Some(bt)) {
193 Some(LengthValue::Px(px)) => (px.max(0), false),
194 Some(LengthValue::Percent(p)) => (round_f32_to_i32(base as f32 * p).max(0), false),
195 _ => (0, true), };
197 return GridTrack::MinMax {
198 min_px,
199 max_fr: 0.0,
200 max_px,
201 max_is_fr: false,
202 max_is_auto,
203 };
204 }
205 }
206 match parse_length_value(Some(tok)) {
207 Some(LengthValue::Px(px)) => GridTrack::Px(px),
208 Some(LengthValue::Percent(p)) => GridTrack::Px(round_f32_to_i32(base as f32 * p)),
209 _ => GridTrack::Auto,
210 }
211}
212
213pub(crate) fn parse_grid_tracks(s: &str, base: i32) -> Vec<GridTrack> {
216 let s = s.trim();
217 if s.is_empty() {
218 return Vec::new();
219 }
220 let mut out = Vec::new();
221 for tok in split_top_level_ws(s) {
222 if let Some(inner) = tok
223 .strip_prefix("repeat(")
224 .and_then(|t| t.strip_suffix(')'))
225 {
226 if let Some((count_str, rest)) = inner.split_once(',') {
227 let count_s = count_str.trim();
228 let track = parse_one_grid_track(rest.trim(), base);
229 let n: usize = if count_s == "auto-fit" || count_s == "auto-fill" {
230 let min_w = match track {
231 GridTrack::Px(px) => px.max(1),
232 GridTrack::MinMax { min_px, .. } => min_px.max(1),
233 _ => 1,
234 };
235 (libm::floorf(base as f32 / min_w as f32) as usize).max(1)
236 } else {
237 count_s.parse::<usize>().unwrap_or(1).max(1)
238 };
239 for _ in 0..n {
240 out.push(track);
241 }
242 continue;
243 }
244 }
245 out.push(parse_one_grid_track(tok, base));
246 }
247 out
248}
249
250pub(crate) fn grid_axis_placement(
255 b: &LayoutBox,
256 short: &str,
257 start_p: &str,
258 end_p: &str,
259) -> (Option<usize>, usize) {
260 let (sv, ev) = if let Some(v) = box_style_value(b, short) {
261 if let Some((a, c)) = v.split_once('/') {
262 (Some(String::from(a.trim())), Some(String::from(c.trim())))
263 } else {
264 (Some(String::from(v.trim())), None)
265 }
266 } else {
267 (
268 box_style_value(b, start_p).map(|s| String::from(s.trim())),
269 box_style_value(b, end_p).map(|s| String::from(s.trim())),
270 )
271 };
272 let mut start: Option<usize> = None;
273 let mut span: usize = 1;
274 if let Some(sv) = &sv {
275 if let Some(n) = sv.strip_prefix("span ") {
276 span = n.trim().parse::<usize>().unwrap_or(1).max(1);
277 } else if let Ok(line) = sv.parse::<i32>() {
278 if line >= 1 {
279 start = Some((line - 1) as usize);
280 }
281 }
282 }
283 if let Some(ev) = &ev {
284 if let Some(n) = ev.strip_prefix("span ") {
285 span = n.trim().parse::<usize>().unwrap_or(1).max(1);
286 } else if let Ok(eline) = ev.parse::<i32>() {
287 if let Some(s0) = start {
288 let e0 = (eline - 1).max(0) as usize;
289 if e0 > s0 {
290 span = e0 - s0;
291 }
292 }
293 }
294 }
295 (start, span)
296}
297
298pub(crate) fn parse_grid_template_areas(s: &str) -> Vec<Vec<String>> {
302 let mut rows = Vec::new();
303 let mut chars = s.chars().peekable();
304 while let Some(c) = chars.next() {
305 if c == '"' || c == '\'' {
306 let quote = c;
307 let mut row_str = String::new();
308 for c2 in chars.by_ref() {
309 if c2 == quote {
310 break;
311 }
312 row_str.push(c2);
313 }
314 let row: Vec<String> = row_str
315 .split_whitespace()
316 .map(String::from)
317 .collect();
318 if !row.is_empty() {
319 rows.push(row);
320 }
321 }
322 }
323 rows
324}
325
326pub(crate) fn find_grid_area(
329 template_areas: &[Vec<String>],
330 name: &str,
331) -> Option<(usize, usize, usize, usize)> {
332 if name == "." {
333 return None;
334 }
335 let mut min_r = usize::MAX;
336 let mut max_r = 0usize;
337 let mut min_c = usize::MAX;
338 let mut max_c = 0usize;
339 let mut found = false;
340 for (r, row) in template_areas.iter().enumerate() {
341 for (c, cell) in row.iter().enumerate() {
342 if cell == name {
343 found = true;
344 min_r = min_r.min(r);
345 max_r = max_r.max(r);
346 min_c = min_c.min(c);
347 max_c = max_c.max(c);
348 }
349 }
350 }
351 if !found {
352 return None;
353 }
354 Some((min_r, min_c, max_r - min_r + 1, max_c - min_c + 1))
355}
356
357pub(crate) fn grid_ensure_row(occ: &mut Vec<Vec<bool>>, r: usize, n_cols: usize) {
359 while occ.len() <= r {
360 occ.push(alloc::vec![false; n_cols]);
361 }
362}
363
364pub(crate) fn grid_ensure_col(occ: &mut [Vec<bool>], min_cols: usize) {
368 for row in occ.iter_mut() {
369 while row.len() < min_cols {
370 row.push(false);
371 }
372 }
373}
374
375pub(crate) fn grid_cells_free(
377 occ: &[Vec<bool>],
378 r: usize,
379 c: usize,
380 row_span: usize,
381 col_span: usize,
382 n_cols: usize,
383) -> bool {
384 if c + col_span > n_cols {
385 return false;
386 }
387 for rr in r..r + row_span {
388 let Some(row) = occ.get(rr) else { continue };
389 for cc in c..c + col_span {
390 if row.get(cc).copied().unwrap_or(false) {
391 return false;
392 }
393 }
394 }
395 true
396}
397
398pub(crate) fn grid_mark(
400 occ: &mut [Vec<bool>],
401 r: usize,
402 c: usize,
403 row_span: usize,
404 col_span: usize,
405 n_cols: usize,
406) {
407 for rr in r..r + row_span {
408 let Some(row) = occ.get_mut(rr) else { continue };
409 for cc in c..(c + col_span).min(n_cols) {
410 if let Some(cell) = row.get_mut(cc) {
411 *cell = true;
412 }
413 }
414 }
415}
416
417pub(crate) fn grid_col_x(col_widths: &[i32], col_gap: i32, col: usize) -> i32 {
419 let mut x = 0i32;
420 for (i, w) in col_widths.iter().enumerate() {
421 if i >= col {
422 break;
423 }
424 x += w + col_gap;
425 }
426 x
427}
428
429pub(crate) fn grid_span_width(col_widths: &[i32], col_gap: i32, col: usize, col_span: usize) -> i32 {
431 let n = col_widths.len();
432 if n == 0 {
433 return 1;
434 }
435 let end = (col + col_span).min(n);
436 let start = col.min(n.saturating_sub(1));
437 let mut w = 0i32;
438 for item in col_widths.iter().take(end).skip(start) {
439 w += *item;
440 }
441 w += (end.saturating_sub(start).saturating_sub(1) as i32) * col_gap;
442 w.max(1)
443}
444