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