1use super::*;
4
5pub fn layout_tree<'a>(node: &'a StyledNode<'a>, containing_block: Dimensions) -> LayoutBox<'a> {
15 let icb = &containing_block.content;
17 let icb_h = if icb.height > 0 {
18 icb.height
19 } else {
20 VIEWPORT_HINT_H.load(AtomicOrdering::Relaxed)
21 };
22 abs_cb_set(icb.x, icb.y, icb.width, icb_h);
23 VP_ORIGIN_X.store(icb.x, AtomicOrdering::Relaxed);
24 VP_ORIGIN_Y.store(icb.y, AtomicOrdering::Relaxed);
25 let mut layout_box = build_layout_tree(node);
26 layout_box.layout(containing_block);
27 layout_box
28}
29
30pub(crate) fn resolved_display<'a>(node: &'a StyledNode<'a>) -> &'a str {
31 match &node.node.node_type {
32 NodeType::Element { tag_name, .. } => node
33 .value_ref("display")
34 .unwrap_or_else(|| default_display_for(tag_name)),
35 NodeType::Text(_) => "inline",
36 }
37}
38
39pub(crate) fn build_layout_tree<'a>(node: &'a StyledNode<'a>) -> LayoutBox<'a> {
40 let box_type = match &node.node.node_type {
41 NodeType::Element { .. } => {
42 let display = resolved_display(node);
43 match display {
44 "block" => BoxType::BlockNode(node),
45 "inline-block" => BoxType::InlineBlockNode(node),
46 "flex" => BoxType::FlexNode(node),
47 "grid" | "inline-grid" => BoxType::GridNode(node),
48 "table" => BoxType::TableNode(node),
49 "table-row-group" => BoxType::TableRowGroupNode(node),
50 "table-row" => BoxType::TableRowNode(node),
51 "table-cell" => BoxType::TableCellNode(node),
52 _ => BoxType::InlineNode(node), }
54 }
55 NodeType::Text(_) => BoxType::InlineNode(node),
56 };
57
58 let mut layout_box = LayoutBox {
59 dimensions: Dimensions::default(),
60 box_type,
61 children: Vec::new(),
62 };
63
64 for child in &node.children {
65 if resolved_display(child) != "none" {
66 layout_box.children.push(build_layout_tree(child));
68 }
69 }
70
71 if matches!(layout_box.box_type, BoxType::TableNode(_)) {
74 let mut flattened: Vec<LayoutBox<'a>> = Vec::new();
75 for child in layout_box.children.drain(..) {
76 if matches!(child.box_type, BoxType::TableRowGroupNode(_)) {
77 for row in child.children {
78 flattened.push(row);
79 }
80 } else {
81 flattened.push(child);
82 }
83 }
84 layout_box.children = flattened;
85 }
86
87 layout_box
88}
89
90impl<'a> LayoutBox<'a> {
91 fn translate_y(&mut self, dy: i32) {
95 if dy == 0 {
96 return;
97 }
98 self.dimensions.content.y += dy;
99 for child in &mut self.children {
100 child.translate_y(dy);
101 }
102 }
103
104 fn translate(&mut self, dx: i32, dy: i32) {
110 if dx == 0 && dy == 0 {
111 return;
112 }
113 self.dimensions.content.x += dx;
114 self.dimensions.content.y += dy;
115 for child in &mut self.children {
116 child.translate(dx, dy);
117 }
118 }
119
120 fn layout_captions_at(&mut self, indices: &[usize], base_x: i32, y: i32, width: i32) -> i32 {
123 let mut consumed = 0i32;
124 for &ci in indices {
125 let cap_y = y + consumed;
126 let cap = &mut self.children[ci];
127 let mut cb = Dimensions::default();
128 cb.content.x = base_x;
129 cb.content.y = cap_y;
130 cb.content.height = 0;
131 cb.content.width = width;
132 cap.layout(cb);
133 consumed += cap.dimensions.margin_box().height;
134 }
135 consumed
136 }
137
138 fn layout(&mut self, containing_block: Dimensions) {
139 match &self.box_type {
140 BoxType::BlockNode(_) => self.layout_block(containing_block),
141 BoxType::InlineNode(_) => self.layout_inline(containing_block, false),
142 BoxType::InlineBlockNode(_) => self.layout_inline(containing_block, true),
143 BoxType::FlexNode(_) => self.layout_flex(containing_block),
144 BoxType::GridNode(_) => self.layout_grid(containing_block),
145 BoxType::TableNode(_) => self.layout_table(containing_block),
146 BoxType::TableRowGroupNode(_)
149 | BoxType::TableRowNode(_)
150 | BoxType::TableCellNode(_) => self.layout_block(containing_block),
151 BoxType::AnonymousBlock => {}
152 }
153 }
154
155 fn establish_abs_cb(&self) -> Option<(i32, i32, i32, i32)> {
160 if matches!(
161 box_position(self).as_str(),
162 "relative" | "absolute" | "fixed"
163 ) {
164 let prev = abs_cb_get();
165 abs_cb_set(
166 self.dimensions.content.x,
167 self.dimensions.content.y,
168 self.dimensions.content.width,
169 self.dimensions.content.height,
170 );
171 Some(prev)
172 } else {
173 None
174 }
175 }
176
177 fn restore_abs_cb(saved: Option<(i32, i32, i32, i32)>) {
179 if let Some(prev) = saved {
180 abs_cb_set(prev.0, prev.1, prev.2, prev.3);
181 }
182 }
183
184 fn child_is_out_of_flow(child: &LayoutBox) -> bool {
186 matches!(box_position(child).as_str(), "absolute" | "fixed")
187 }
188
189 fn apply_positioned_offsets(&mut self) {
197 let cw = self.dimensions.content.width;
198 let ch = self.dimensions.content.height;
199 for child in &mut self.children {
200 match box_position(child).as_str() {
201 "relative" => {
202 let dx = box_inset_len(child, "left", cw)
203 .or_else(|| box_inset_len(child, "right", cw).map(|r| -r))
204 .unwrap_or(0);
205 let dy = box_inset_len(child, "top", ch)
206 .or_else(|| box_inset_len(child, "bottom", ch).map(|b| -b))
207 .unwrap_or(0);
208 if dx != 0 || dy != 0 {
209 shift_layout_box(child, dx, dy);
210 }
211 }
212 pos @ ("absolute" | "fixed") => {
213 let (bx, by, bw, bh) = if pos == "fixed" {
215 let (vx, vy) = vp_origin_get();
216 (
217 vx,
218 vy,
219 VIEWPORT_HINT_W.load(AtomicOrdering::Relaxed),
220 VIEWPORT_HINT_H.load(AtomicOrdering::Relaxed),
221 )
222 } else {
223 abs_cb_get()
224 };
225 let mb = child.dimensions.margin_box();
226 let tx = if let Some(l) = box_inset_len(child, "left", bw) {
227 bx + l
228 } else if let Some(r) = box_inset_len(child, "right", bw) {
229 bx + bw - r - mb.width
230 } else {
231 bx
232 };
233 let ty = if let Some(t) = box_inset_len(child, "top", bh) {
234 by + t
235 } else if let Some(b) = box_inset_len(child, "bottom", bh) {
236 by + bh - b - mb.height
237 } else {
238 by
239 };
240 shift_layout_box(child, tx - mb.x, ty - mb.y);
241 }
242 _ => {}
243 }
244
245 let (txx, tyy) = box_translate(child);
248 if txx != 0 || tyy != 0 {
249 shift_layout_box(child, txx, tyy);
250 }
251 }
252 }
253
254 fn layout_grid(&mut self, containing_block: Dimensions) {
261 self.apply_box_model_styles_with_base(containing_block.content.width);
262
263 let node = match &self.box_type {
264 BoxType::GridNode(n) => *n,
265 _ => return,
266 };
267 let avail_w = containing_block.content.width.max(1);
268
269 let inner_non_content = self.dimensions.border.left
271 + self.dimensions.border.right
272 + self.dimensions.padding.left
273 + self.dimensions.padding.right;
274 let h_non_content =
275 self.dimensions.margin.left + self.dimensions.margin.right + inner_non_content;
276 let width_val = node.value("width");
277 let box_sizing = node.value("box-sizing").unwrap_or_default();
278 let h_auto = margin_is_h_auto(node);
279 let mut content_w = match parse_length_value(width_val) {
280 Some(LengthValue::Auto) | None => (avail_w - h_non_content).max(1),
281 Some(LengthValue::Px(px)) => px.max(1),
282 Some(LengthValue::Percent(pct)) => round_f32_to_i32(avail_w as f32 * pct).max(1),
283 Some(LengthValue::MinContent) => {
284 let (minc, _maxc) = self.block_intrinsic_widths();
285 minc.max(1)
286 }
287 Some(LengthValue::MaxContent) => {
288 let (_minc, maxc) = self.block_intrinsic_widths();
289 maxc.max(1)
290 }
291 Some(LengthValue::FitContent) => {
292 let (minc, maxc) = self.block_intrinsic_widths();
293 let avail = (avail_w - h_non_content).max(0);
294 avail.min(maxc).max(minc).max(1)
295 }
296 Some(LengthValue::MathExpr(expr)) => {
297 crate::os_lib::css::eval_css_math(&expr, avail_w)
298 .unwrap_or((avail_w - h_non_content).max(1))
299 .max(1)
300 }
301 };
302 if box_sizing.trim() == "border-box" {
303 content_w = (content_w - inner_non_content).max(1);
304 }
305 self.dimensions.content.width = content_w;
306 if h_auto {
307 let free = (avail_w - content_w - inner_non_content).max(0);
308 self.dimensions.margin.left = free / 2;
309 self.dimensions.margin.right = free / 2;
310 }
311
312 let flow_y = containing_block.content.y
313 + containing_block.content.height
314 + self.dimensions.margin.top;
315 self.dimensions.content.x = containing_block.content.x
316 + self.dimensions.margin.left
317 + self.dimensions.border.left
318 + self.dimensions.padding.left;
319 self.dimensions.content.y =
320 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
321
322 let mut col_widths = parse_grid_template_columns(
324 node.value("grid-template-columns").as_deref().unwrap_or(""),
325 content_w,
326 );
327 let n_cols = col_widths.len().max(1);
328
329 let auto_col_tracks = parse_grid_tracks(
335 node.value("grid-auto-columns").as_deref().unwrap_or(""),
336 content_w,
337 );
338 let auto_col_fallback_px = col_widths.last().copied().unwrap_or(100).max(1);
339 let resolve_auto_col_px = |idx: usize| -> i32 {
340 if auto_col_tracks.is_empty() {
341 return auto_col_fallback_px;
342 }
343 match auto_col_tracks[idx % auto_col_tracks.len()] {
344 GridTrack::Px(px) => px.max(1),
345 GridTrack::MinMax { min_px, .. } => min_px.max(1),
346 _ => auto_col_fallback_px,
347 }
348 };
349
350 let template_areas =
352 parse_grid_template_areas(node.value("grid-template-areas").as_deref().unwrap_or(""));
353
354 let (row_gap, col_gap) = {
355 let gpx = |s: &str| -> i32 {
357 match parse_length_value(Some(String::from(s.trim()))) {
358 Some(LengthValue::Px(p)) => p,
359 Some(LengthValue::Percent(pct)) => {
360 round_f32_to_i32(content_w as f32 * pct).max(0)
361 }
362 _ => 0,
363 }
364 };
365 let gap_all = node.value("gap").unwrap_or_default();
366 if !gap_all.is_empty() {
367 let toks: Vec<&str> = gap_all.split_whitespace().collect();
368 let rg = gpx(toks.first().copied().unwrap_or("0"));
369 let cg = gpx(toks
370 .get(1)
371 .copied()
372 .unwrap_or(toks.first().copied().unwrap_or("0")));
373 (rg, cg)
374 } else {
375 let rg = node
376 .value("row-gap")
377 .or_else(|| node.value("grid-row-gap"))
378 .map(|v| gpx(&v))
379 .unwrap_or(0);
380 let cg = node
381 .value("column-gap")
382 .or_else(|| node.value("grid-column-gap"))
383 .map(|v| gpx(&v))
384 .unwrap_or(0);
385 (rg, cg)
386 }
387 };
388
389 let row_base = match parse_length_value(node.value("height")) {
396 Some(LengthValue::Px(px)) => px.max(0),
397 Some(LengthValue::Percent(pct)) => {
398 round_f32_to_i32(containing_block.content.height as f32 * pct).max(0)
399 }
400 _ => 0,
401 };
402
403 let row_tracks = parse_grid_tracks(
405 node.value("grid-template-rows").as_deref().unwrap_or(""),
406 row_base,
407 );
408
409 let auto_row_tracks = parse_grid_tracks(
411 node.value("grid-auto-rows").as_deref().unwrap_or(""),
412 row_base,
413 );
414
415 let eff_row_track = |r: usize| -> Option<GridTrack> {
417 if let Some(t) = row_tracks.get(r) {
418 return Some(*t);
419 }
420 if auto_row_tracks.is_empty() {
421 return None;
422 }
423 let implicit_idx = r - row_tracks.len();
424 auto_row_tracks
425 .get(implicit_idx % auto_row_tracks.len())
426 .copied()
427 };
428
429 let justify_items = node
432 .value("justify-items")
433 .unwrap_or_else(|| String::from("stretch"))
434 .trim()
435 .to_lowercase();
436 let align_items = node
437 .value("align-items")
438 .unwrap_or_else(|| String::from("stretch"))
439 .trim()
440 .to_lowercase();
441
442 let auto_flow_col = node
446 .value("grid-auto-flow")
447 .map(|v| v.to_lowercase().contains("column"))
448 .unwrap_or(false);
449 let flow_rows = row_tracks.len().max(1);
451 let mut occ: Vec<Vec<bool>> = Vec::new();
452 let mut n_cols_dyn = n_cols;
458 let n_children = self.children.len();
460 let mut placement: Vec<Option<(usize, usize, usize, usize)>> =
461 alloc::vec![None; n_children];
462 let mut cursor_r = 0usize;
463 let mut cursor_c = 0usize;
464 for (idx, child) in self.children.iter().enumerate() {
465 if Self::child_is_out_of_flow(child) {
466 continue;
467 }
468 let (mut col_start, mut col_span_raw) =
469 grid_axis_placement(child, "grid-column", "grid-column-start", "grid-column-end");
470 let (mut row_start, mut row_span_raw) =
471 grid_axis_placement(child, "grid-row", "grid-row-start", "grid-row-end");
472 if col_start.is_none() && row_start.is_none() {
475 if let Some(area_name) = box_style_value(child, "grid-area") {
476 let area_name = area_name.trim();
477 if !area_name.is_empty() {
478 if let Some((ar, ac, arspan, acspan)) =
479 find_grid_area(&template_areas, area_name)
480 {
481 row_start = Some(ar);
482 col_start = Some(ac);
483 row_span_raw = arspan;
484 col_span_raw = acspan;
485 }
486 }
487 }
488 }
489 let col_span = col_span_raw.min(n_cols).max(1);
490 let row_span = row_span_raw.max(1);
491
492 let (r, c) = match (row_start, col_start) {
494 (Some(r), Some(c)) => (r, c.min(n_cols.saturating_sub(1))),
495 (Some(r), None) => {
496 grid_ensure_row(&mut occ, r + row_span - 1, n_cols);
498 let mut cc = 0usize;
499 while cc < n_cols && !grid_cells_free(&occ, r, cc, row_span, col_span, n_cols) {
500 cc += 1;
501 }
502 (r, cc.min(n_cols.saturating_sub(1)))
503 }
504 (None, Some(c)) => {
505 let c = c.min(n_cols.saturating_sub(1));
507 let mut rr = cursor_r;
508 loop {
509 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols);
510 if grid_cells_free(&occ, rr, c, row_span, col_span, n_cols) {
511 break;
512 }
513 rr += 1;
514 }
515 (rr, c)
516 }
517 (None, None) => {
518 if auto_flow_col {
519 let mut rr = cursor_r;
526 let mut cc = cursor_c;
527 loop {
528 if rr + row_span > flow_rows {
529 cc += 1;
530 rr = 0;
531 }
532 if cc + col_span > n_cols_dyn {
533 n_cols_dyn = cc + col_span;
534 grid_ensure_col(&mut occ, n_cols_dyn);
535 }
536 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols_dyn);
537 if grid_cells_free(&occ, rr, cc, row_span, col_span, n_cols_dyn) {
538 break;
539 }
540 rr += 1;
541 }
542 cursor_r = rr + row_span;
543 cursor_c = cc;
544 (rr, cc)
545 } else {
546 let mut rr = cursor_r;
548 let mut cc = cursor_c;
549 loop {
550 if cc + col_span > n_cols {
551 rr += 1;
552 cc = 0;
553 }
554 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols);
555 if grid_cells_free(&occ, rr, cc, row_span, col_span, n_cols) {
556 break;
557 }
558 cc += 1;
559 }
560 cursor_r = rr;
561 cursor_c = cc + col_span;
562 (rr, cc)
563 }
564 }
565 };
566 grid_ensure_row(&mut occ, r + row_span - 1, n_cols_dyn);
567 grid_mark(&mut occ, r, c, row_span, col_span, n_cols_dyn);
568 placement[idx] = Some((r, c, row_span, col_span));
569 }
570
571 while col_widths.len() < n_cols_dyn {
574 let idx = col_widths.len() - n_cols;
575 col_widths.push(resolve_auto_col_px(idx));
576 }
577
578 let n_cols_eff = col_widths.len();
583 let cols_total: i32 =
584 col_widths.iter().sum::<i32>() + (n_cols_eff.saturating_sub(1) as i32) * col_gap;
585 let cols_free = (content_w - cols_total).max(0);
586 let justify_content_grid = node
587 .value("justify-content")
588 .unwrap_or_default()
589 .trim()
590 .to_lowercase();
591 let (col_start_offset, col_extra_gap) = if cols_free > 0 && n_cols_eff > 0 {
592 match justify_content_grid.as_str() {
593 "end" | "flex-end" => (cols_free, 0),
594 "center" => (cols_free / 2, 0),
595 "space-between" if n_cols_eff > 1 => (0, cols_free / (n_cols_eff as i32 - 1)),
596 "space-around" if n_cols_eff > 0 => {
597 let g = cols_free / n_cols_eff as i32;
598 (g / 2, g)
599 }
600 "space-evenly" if n_cols_eff > 0 => {
601 let g = cols_free / (n_cols_eff as i32 + 1);
602 (g, g)
603 }
604 _ => (0, 0),
605 }
606 } else {
607 (0, 0)
608 };
609 let col_gap_eff = col_gap + col_extra_gap;
610
611 let n_rows = occ.len().max(row_tracks.len());
612
613 let mut row_heights: Vec<i32> = alloc::vec![0; n_rows];
616 for (r, h) in row_heights.iter_mut().enumerate() {
618 match eff_row_track(r) {
619 Some(GridTrack::Px(px)) => *h = px.max(0),
620 Some(GridTrack::MinMax { min_px, .. }) => *h = min_px.max(0),
621 _ => {}
622 }
623 }
624 let base_x = self.dimensions.content.x;
627 let base_y = self.dimensions.content.y;
628 for (idx, child) in self.children.iter_mut().enumerate() {
629 if Self::child_is_out_of_flow(child) {
630 continue;
631 }
632 let Some((_r, c, _rs, cs)) = placement[idx] else {
633 continue;
634 };
635 let cell_w = grid_span_width(&col_widths, col_gap_eff, c, cs);
636 let cell_cb = Dimensions {
637 content: Rect {
638 x: base_x + col_start_offset + grid_col_x(&col_widths, col_gap_eff, c),
639 y: base_y,
640 width: cell_w,
641 height: 0,
642 },
643 ..Dimensions::default()
644 };
645 child.layout(cell_cb);
646 }
647 for (idx, child) in self.children.iter().enumerate() {
649 let Some((r, _c, rs, _cs)) = placement[idx] else {
650 continue;
651 };
652 if rs != 1 {
653 continue;
654 }
655 let explicit_px = matches!(eff_row_track(r), Some(GridTrack::Px(_)));
656 if explicit_px {
657 continue;
658 }
659 let ch = child.dimensions.margin_box().height;
660 if let Some(h) = row_heights.get_mut(r) {
661 *h = (*h).max(ch);
662 }
663 if let Some(GridTrack::MinMax {
665 max_px,
666 max_is_fr,
667 max_is_auto,
668 ..
669 }) = eff_row_track(r)
670 {
671 if !max_is_fr && !max_is_auto {
672 if let Some(h) = row_heights.get_mut(r) {
673 *h = (*h).min(max_px);
674 }
675 }
676 }
677 }
678 for (idx, child) in self.children.iter().enumerate() {
680 let Some((r, _c, rs, _cs)) = placement[idx] else {
681 continue;
682 };
683 if rs <= 1 {
684 continue;
685 }
686 let ch = child.dimensions.margin_box().height;
687 let mut covered = 0i32;
688 for rr in r..(r + rs).min(row_heights.len()) {
689 covered += row_heights.get(rr).copied().unwrap_or(0);
690 }
691 covered += (rs.saturating_sub(1) as i32) * row_gap;
692 if ch > covered {
693 let last = (r + rs - 1).min(row_heights.len().saturating_sub(1));
694 if let Some(h) = row_heights.get_mut(last) {
695 *h += ch - covered;
696 }
697 }
698 }
699
700 let mut container_h_explicit: Option<i32> = None;
702 let height_val = node.value("height");
703 if let Some(h) = parse_length_value(height_val.clone())
704 .and_then(|v| resolve_length_value(v, containing_block.content.height.max(0)))
705 {
706 let vert_non = self.dimensions.border.top
707 + self.dimensions.border.bottom
708 + self.dimensions.padding.top
709 + self.dimensions.padding.bottom;
710 container_h_explicit = Some(if box_sizing.trim() == "border-box" {
711 (h - vert_non).max(0)
712 } else {
713 h.max(0)
714 });
715 }
716 if let Some(ch) = container_h_explicit {
717 let track_fr = |t: &GridTrack| -> f32 {
719 match t {
720 GridTrack::Fr(f) => *f,
721 GridTrack::MinMax {
722 max_fr, max_is_fr, ..
723 } if *max_is_fr => *max_fr,
724 _ => 0.0,
725 }
726 };
727 let mut fr_total = 0.0f32;
729 for r in 0..n_rows {
730 if let Some(t) = eff_row_track(r) {
731 fr_total += track_fr(&t);
732 }
733 }
734 if fr_total > 0.0 {
735 let mut used = 0i32;
736 for (r, h) in row_heights.iter().enumerate() {
737 let is_fr = eff_row_track(r).map(|t| track_fr(&t)).unwrap_or(0.0) > 0.0;
738 if !is_fr {
739 used += *h;
740 }
741 }
742 used += (n_rows.saturating_sub(1) as i32) * row_gap;
743 let free = (ch - used).max(0);
744 for r in 0..n_rows {
745 let Some(t) = eff_row_track(r) else { continue };
746 let f = track_fr(&t);
747 if f > 0.0 {
748 let share = round_f32_to_i32(free as f32 * f / fr_total);
749 let floor = match t {
751 GridTrack::MinMax { min_px, .. } => min_px,
752 _ => 0,
753 };
754 if let Some(rh) = row_heights.get_mut(r) {
755 *rh = share.max(floor);
756 }
757 }
758 }
759 }
760 }
761
762 let rows_total_h: i32 =
767 row_heights.iter().sum::<i32>() + (n_rows.saturating_sub(1) as i32) * row_gap;
768 let rows_free = container_h_explicit
769 .map(|ch| (ch - rows_total_h).max(0))
770 .unwrap_or(0);
771 let align_content_grid = node
772 .value("align-content")
773 .unwrap_or_default()
774 .trim()
775 .to_lowercase();
776 let (row_start_offset, row_extra_gap) = if rows_free > 0 && n_rows > 0 {
777 match align_content_grid.as_str() {
778 "end" | "flex-end" => (rows_free, 0),
779 "center" => (rows_free / 2, 0),
780 "space-between" if n_rows > 1 => (0, rows_free / (n_rows as i32 - 1)),
781 "space-around" if n_rows > 0 => {
782 let g = rows_free / n_rows as i32;
783 (g / 2, g)
784 }
785 "space-evenly" if n_rows > 0 => {
786 let g = rows_free / (n_rows as i32 + 1);
787 (g, g)
788 }
789 _ => (0, 0),
790 }
791 } else {
792 (0, 0)
793 };
794 let row_gap_eff = row_gap + row_extra_gap;
795
796 let mut row_tops: Vec<i32> = alloc::vec![0; n_rows + 1];
798 for r in 0..n_rows {
799 let h = row_heights.get(r).copied().unwrap_or(0);
800 row_tops[r + 1] = row_tops[r] + h + if r + 1 < n_rows { row_gap_eff } else { 0 };
801 }
802
803 for (idx, child) in self.children.iter_mut().enumerate() {
805 if Self::child_is_out_of_flow(child) {
806 let oof_cb = Dimensions {
809 content: Rect {
810 x: base_x,
811 y: base_y,
812 width: content_w,
813 height: 0,
814 },
815 ..Dimensions::default()
816 };
817 child.layout(oof_cb);
818 continue;
819 }
820 let Some((r, c, rs, cs)) = placement[idx] else {
821 continue;
822 };
823 let cell_w = grid_span_width(&col_widths, col_gap_eff, c, cs);
824 let cell_x = base_x + col_start_offset + grid_col_x(&col_widths, col_gap_eff, c);
825 let cell_y = base_y + row_start_offset + row_tops.get(r).copied().unwrap_or(0);
826 let mut cell_h = 0i32;
828 for rr in r..(r + rs).min(row_heights.len()) {
829 cell_h += row_heights.get(rr).copied().unwrap_or(0);
830 }
831 cell_h += (rs.saturating_sub(1) as i32) * row_gap;
832
833 let j = grid_item_align(child, "justify-self", &justify_items);
835 let a = grid_item_align(child, "align-self", &align_items);
836
837 let cell_cb = Dimensions {
839 content: Rect {
840 x: cell_x,
841 y: cell_y,
842 width: cell_w,
843 height: 0,
844 },
845 ..Dimensions::default()
846 };
847 child.layout(cell_cb);
848
849 if a == "stretch" && !box_has_explicit(child, "height") && cell_h > 0 {
851 let mb = child.dimensions.margin_box();
852 let non_content = mb.height - child.dimensions.content.height;
853 child.dimensions.content.height = (cell_h - non_content).max(0);
854 }
855
856 let mb = child.dimensions.margin_box();
860 let dx = if j == "stretch" || !box_has_explicit(child, "width") {
861 0
862 } else {
863 let free = (cell_w - mb.width).max(0);
864 match j {
865 "center" => free / 2,
866 "end" => free,
867 _ => 0, }
869 };
870 let dy = if a == "stretch" || !box_has_explicit(child, "height") {
872 0
873 } else {
874 let free = (cell_h - mb.height).max(0);
875 match a {
876 "center" => free / 2,
877 "end" => free,
878 _ => 0, }
880 };
881 if dx != 0 || dy != 0 {
882 shift_layout_box(child, dx, dy);
883 }
884 }
885
886 let natural_h = row_tops.get(n_rows).copied().unwrap_or(0).max(0);
888 self.dimensions.content.height = container_h_explicit.unwrap_or(natural_h);
889
890 let saved_abs_cb = self.establish_abs_cb();
892 self.apply_positioned_offsets();
893 Self::restore_abs_cb(saved_abs_cb);
894 }
895
896 fn layout_table(&mut self, containing_block: Dimensions) {
897 self.apply_box_model_styles_with_base(containing_block.content.width);
898
899 let h_non_content = self.dimensions.margin.left
900 + self.dimensions.margin.right
901 + self.dimensions.border.left
902 + self.dimensions.border.right
903 + self.dimensions.padding.left
904 + self.dimensions.padding.right;
905 let avail = (containing_block.content.width - h_non_content).max(1);
906
907 let table_width = match &self.box_type {
909 BoxType::TableNode(node) => match parse_length_value(node.value("width")) {
910 Some(LengthValue::Px(px)) => px.max(1).min(avail),
911 Some(LengthValue::Percent(pct)) => round_f32_to_i32(avail as f32 * pct).max(1),
912 _ => avail,
913 },
914 _ => avail,
915 };
916
917 let flow_y = containing_block.content.y
919 + containing_block.content.height
920 + self.dimensions.margin.top;
921 self.dimensions.content.x = containing_block.content.x
922 + self.dimensions.margin.left
923 + self.dimensions.border.left
924 + self.dimensions.padding.left;
925 self.dimensions.content.y =
926 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
927 self.dimensions.content.width = table_width;
928
929 let base_x = self.dimensions.content.x;
930
931 let collapse = match &self.box_type {
933 BoxType::TableNode(node) => node
934 .value("border-collapse")
935 .map(|v| v.trim().eq_ignore_ascii_case("collapse"))
936 .unwrap_or(false),
937 _ => false,
938 };
939 let collapse_px: i32 = if collapse { 1 } else { 0 };
940
941 let table_layout_fixed = match &self.box_type {
945 BoxType::TableNode(node) => node
946 .value("table-layout")
947 .map(|v| v.trim().eq_ignore_ascii_case("fixed"))
948 .unwrap_or(false),
949 _ => false,
950 };
951
952 let (h_spacing, v_spacing): (i32, i32) = if collapse {
955 (0, 0)
956 } else {
957 match &self.box_type {
958 BoxType::TableNode(node) => match node.value("border-spacing") {
959 Some(v) => {
960 let toks: Vec<&str> = v.split_whitespace().collect();
961 let px = |s: &str| -> i32 {
962 match parse_length_value(Some(String::from(s))) {
963 Some(LengthValue::Px(px)) => px.max(0),
964 _ => 0,
965 }
966 };
967 match toks.as_slice() {
968 [h] => (px(h), px(h)),
969 [h, w] => (px(h), px(w)),
970 _ => (0, 0),
971 }
972 }
973 None => (0, 0),
974 },
975 _ => (0, 0),
976 }
977 };
978
979 let caption_indices: Vec<usize> = (0..self.children.len())
983 .filter(|&i| box_tag_name(&self.children[i]) == Some("caption"))
984 .collect();
985 let caption_side_bottom = caption_indices
986 .first()
987 .and_then(|&ci| box_style_value(&self.children[ci], "caption-side"))
988 .map(|v| v.trim().eq_ignore_ascii_case("bottom"))
989 .unwrap_or(false);
990 let mut top_offset = 0i32;
991 if !caption_side_bottom {
992 let y = self.dimensions.content.y;
993 top_offset = self.layout_captions_at(&caption_indices, base_x, y, table_width);
994 }
995 let rows_top = self.dimensions.content.y + top_offset;
996
997 let row_indices: Vec<usize> = (0..self.children.len())
1001 .filter(|&i| {
1002 matches!(self.children[i].box_type, BoxType::TableRowNode(_))
1003 && box_style_value(&self.children[i], "visibility")
1004 .map(|v| !v.trim().eq_ignore_ascii_case("collapse"))
1005 .unwrap_or(true)
1006 })
1007 .collect();
1008 let nrows = row_indices.len();
1009
1010 struct CellPlace {
1012 row_ord: usize,
1013 cell_ord: usize,
1014 col: usize,
1015 colspan: usize,
1016 rowspan: usize,
1017 }
1018 let mut occ: Vec<Vec<bool>> = Vec::new();
1019 let mut places: Vec<CellPlace> = Vec::new();
1020 let mut ncols = 0usize;
1021 for (row_ord, &ri) in row_indices.iter().enumerate() {
1022 let ncells = self.children[ri].children.len();
1023 let mut c = 0usize;
1024 for cell_ord in 0..ncells {
1025 while occ_is(&occ, row_ord, c) {
1027 c += 1;
1028 }
1029 let (cs, rs) = cell_spans(&self.children[ri].children[cell_ord]);
1030 let rs = rs.min(nrows - row_ord); for rr in row_ord..(row_ord + rs) {
1032 for cc in c..(c + cs) {
1033 occ_mark(&mut occ, rr, cc);
1034 }
1035 }
1036 places.push(CellPlace {
1037 row_ord,
1038 cell_ord,
1039 col: c,
1040 colspan: cs,
1041 rowspan: rs.max(1),
1042 });
1043 c += cs;
1044 ncols = ncols.max(c);
1045 }
1046 }
1047
1048 if ncols == 0 {
1049 if caption_side_bottom {
1050 let y = self.dimensions.content.y + top_offset;
1051 top_offset += self.layout_captions_at(&caption_indices, base_x, y, table_width);
1052 }
1053 self.dimensions.content.height = top_offset;
1054 return;
1055 }
1056
1057 let mut col_w = alloc::vec![0i32; ncols];
1059 let mut is_fixed_col = alloc::vec![false; ncols];
1060 let pad = 24; let usable_width = (table_width - h_spacing * (ncols as i32 - 1).max(0)).max(1);
1062
1063 if table_layout_fixed {
1064 for p in &places {
1067 if p.row_ord != 0 || p.colspan != 1 {
1068 continue;
1069 }
1070 let cell = &self.children[row_indices[p.row_ord]].children[p.cell_ord];
1071 if let Some(w) = box_style_value(cell, "width") {
1072 match parse_length_value(Some(w)) {
1073 Some(LengthValue::Px(px)) => {
1074 col_w[p.col] = px.max(1);
1075 is_fixed_col[p.col] = true;
1076 }
1077 Some(LengthValue::Percent(pct)) => {
1078 col_w[p.col] = round_f32_to_i32(usable_width as f32 * pct).max(1);
1079 is_fixed_col[p.col] = true;
1080 }
1081 _ => {}
1082 }
1083 }
1084 }
1085 let fixed_sum: i32 = (0..ncols).filter(|&c| is_fixed_col[c]).map(|c| col_w[c]).sum();
1087 let auto_cols: alloc::vec::Vec<usize> =
1088 (0..ncols).filter(|&c| !is_fixed_col[c]).collect();
1089 if !auto_cols.is_empty() {
1090 let remaining = (usable_width - fixed_sum).max(0);
1091 let auto_w = (remaining / auto_cols.len() as i32).max(1);
1092 for &c in &auto_cols {
1093 col_w[c] = auto_w;
1094 }
1095 }
1096 } else {
1097 for p in &places {
1098 if p.colspan != 1 {
1099 continue;
1100 }
1101 let (cw, _) = self.children[row_indices[p.row_ord]].children[p.cell_ord]
1102 .intrinsic_inline_size(table_width);
1103 col_w[p.col] = col_w[p.col].max(cw + pad);
1104 }
1105 for p in &places {
1106 if p.colspan <= 1 {
1107 continue;
1108 }
1109 let (cw, _) = self.children[row_indices[p.row_ord]].children[p.cell_ord]
1110 .intrinsic_inline_size(table_width);
1111 let need = cw + pad;
1112 let cur: i32 = col_w[p.col..p.col + p.colspan].iter().sum();
1113 if need > cur {
1114 let deficit = need - cur;
1115 let add = deficit / p.colspan as i32;
1116 for cc in p.col..p.col + p.colspan {
1117 col_w[cc] += add;
1118 }
1119 col_w[p.col] += deficit - add * p.colspan as i32; }
1121 }
1122
1123 let total: i32 = col_w.iter().sum::<i32>().max(1);
1126 if total > usable_width {
1127 for w in col_w.iter_mut() {
1128 *w = ((*w as i64 * usable_width as i64) / total as i64).max(1) as i32;
1129 }
1130 } else {
1131 let extra = (usable_width - total) / ncols as i32;
1132 for w in col_w.iter_mut() {
1133 *w += extra;
1134 }
1135 }
1136 }
1137 let mut col_x = alloc::vec![0i32; ncols];
1139 let mut acc = 0i32;
1140 for c in 0..ncols {
1141 col_x[c] = acc;
1142 acc += col_w[c];
1143 if c + 1 < ncols {
1144 acc += h_spacing;
1145 }
1146 }
1147 let span_w = |col: usize, colspan: usize| -> i32 {
1148 col_w[col..(col + colspan).min(ncols)].iter().sum::<i32>()
1149 + h_spacing * colspan.saturating_sub(1) as i32
1150 };
1151
1152 let mut place_h = alloc::vec![0i32; places.len()];
1154 for (pi, p) in places.iter().enumerate() {
1155 let cw_total = span_w(p.col, p.colspan);
1156 let cell = &mut self.children[row_indices[p.row_ord]].children[p.cell_ord];
1157 let mut cb = Dimensions::default();
1158 cb.content.x = base_x + col_x[p.col];
1159 cb.content.y = rows_top;
1160 cb.content.height = 0;
1161 cb.content.width = cw_total;
1162 cell.layout(cb);
1163 place_h[pi] = cell.dimensions.margin_box().height;
1164 }
1165 let mut row_h = alloc::vec![0i32; nrows];
1167 for (pi, p) in places.iter().enumerate() {
1168 if p.rowspan == 1 {
1169 row_h[p.row_ord] = row_h[p.row_ord].max(place_h[pi]);
1170 }
1171 }
1172 for (pi, p) in places.iter().enumerate() {
1173 if p.rowspan <= 1 {
1174 continue;
1175 }
1176 let end = (p.row_ord + p.rowspan).min(nrows);
1177 let cur: i32 = row_h[p.row_ord..end].iter().sum();
1178 if place_h[pi] > cur {
1179 row_h[end - 1] += place_h[pi] - cur;
1180 }
1181 }
1182 let mut row_y = alloc::vec![0i32; nrows];
1184 let mut accy = 0i32;
1185 for r in 0..nrows {
1186 row_y[r] = accy;
1187 accy += row_h[r];
1188 if r + 1 < nrows {
1189 accy += v_spacing;
1190 }
1191 }
1192 let rows_total = accy;
1193
1194 for p in places.iter() {
1196 let cw_total = span_w(p.col, p.colspan);
1197 let end_r = (p.row_ord + p.rowspan).min(nrows);
1198 let span_h: i32 = row_h[p.row_ord..end_r].iter().sum();
1199 let x0 = base_x + col_x[p.col] - collapse_px * p.col as i32;
1200 let y0 = rows_top + row_y[p.row_ord] - collapse_px * p.row_ord as i32;
1201 let cell = &mut self.children[row_indices[p.row_ord]].children[p.cell_ord];
1202 let mut cb = Dimensions::default();
1203 cb.content.x = x0;
1204 cb.content.y = y0;
1205 cb.content.height = 0;
1206 cb.content.width = cw_total;
1207 cell.layout(cb); let w_extra = cell.dimensions.padding.left
1209 + cell.dimensions.padding.right
1210 + cell.dimensions.border.left
1211 + cell.dimensions.border.right;
1212 let h_extra = cell.dimensions.padding.top
1213 + cell.dimensions.padding.bottom
1214 + cell.dimensions.border.top
1215 + cell.dimensions.border.bottom;
1216 let natural_h = cell.dimensions.content.height;
1219 let target_h = (span_h - h_extra).max(natural_h);
1220 let extra_space = (target_h - natural_h).max(0);
1221 if extra_space > 0 {
1222 let va = match &cell.box_type {
1223 BoxType::TableCellNode(node) => node.value("vertical-align").unwrap_or_default(),
1224 _ => String::new(),
1225 };
1226 let dy = match va.trim() {
1227 "middle" | "center" => extra_space / 2,
1228 "bottom" => extra_space,
1229 _ => 0, };
1231 for child in &mut cell.children {
1233 child.translate_y(dy);
1234 }
1235 }
1236 cell.dimensions.content.width = (cw_total - w_extra).max(1);
1237 cell.dimensions.content.height = target_h;
1238 }
1239
1240 for (row_ord, &ri) in row_indices.iter().enumerate() {
1242 let row = &mut self.children[ri];
1243 row.dimensions.content.x = base_x;
1244 row.dimensions.content.y = rows_top + row_y[row_ord] - collapse_px * row_ord as i32;
1245 row.dimensions.content.width = table_width;
1246 row.dimensions.content.height = row_h[row_ord];
1247 let saved_row_cb = row.establish_abs_cb();
1250 row.apply_positioned_offsets();
1251 Self::restore_abs_cb(saved_row_cb);
1252 }
1253
1254 let collapse_shrink = collapse_px * (nrows.saturating_sub(1)) as i32;
1256 let rows_height = (rows_total - collapse_shrink).max(0);
1257 if caption_side_bottom {
1258 let y = self.dimensions.content.y + top_offset + rows_height;
1259 top_offset += self.layout_captions_at(&caption_indices, base_x, y, table_width);
1260 }
1261 self.dimensions.content.height = top_offset + rows_height;
1262
1263 let saved_abs_cb = self.establish_abs_cb();
1267 self.apply_positioned_offsets();
1268 Self::restore_abs_cb(saved_abs_cb);
1269 }
1270
1271 fn layout_flex(&mut self, containing_block: Dimensions) {
1272 self.apply_box_model_styles_with_base(containing_block.content.width);
1273
1274 let (width_val, min_width_val, max_width_val, height_val) = match &self.box_type {
1275 BoxType::FlexNode(node) => (
1276 node.value("width"),
1277 node.value("min-width"),
1278 node.value("max-width"),
1279 node.value("height"),
1280 ),
1281 _ => return,
1282 };
1283
1284 let horizontal_non_content = self.dimensions.margin.left
1285 + self.dimensions.margin.right
1286 + self.dimensions.border.left
1287 + self.dimensions.border.right
1288 + self.dimensions.padding.left
1289 + self.dimensions.padding.right;
1290 let available_width = containing_block.content.width.max(1);
1291
1292 let mut content_width = match parse_length_value(width_val) {
1293 Some(LengthValue::Auto) | None => (available_width - horizontal_non_content).max(1),
1294 Some(LengthValue::Px(px)) => px.max(1),
1295 Some(LengthValue::Percent(pct)) => {
1296 round_f32_to_i32(available_width as f32 * pct).max(1)
1297 }
1298 Some(LengthValue::MinContent) => {
1299 let (minc, _maxc) = self.block_intrinsic_widths();
1300 minc.max(1)
1301 }
1302 Some(LengthValue::MaxContent) => {
1303 let (_minc, maxc) = self.block_intrinsic_widths();
1304 maxc.max(1)
1305 }
1306 Some(LengthValue::FitContent) => {
1307 let (minc, maxc) = self.block_intrinsic_widths();
1308 let avail = (available_width - horizontal_non_content).max(0);
1309 avail.min(maxc).max(minc).max(1)
1310 }
1311 Some(LengthValue::MathExpr(expr)) => {
1312 crate::os_lib::css::eval_css_math(&expr, available_width)
1313 .unwrap_or((available_width - horizontal_non_content).max(1))
1314 .max(1)
1315 }
1316 };
1317
1318 if let Some(min_width) =
1319 parse_length_value(min_width_val).and_then(|v| resolve_length_value(v, available_width))
1320 {
1321 content_width = content_width.max(min_width);
1322 }
1323 if let Some(max_width) =
1324 parse_length_value(max_width_val).and_then(|v| resolve_length_value(v, available_width))
1325 {
1326 content_width = content_width.min(max_width);
1327 }
1328 self.dimensions.content.width = content_width.max(1);
1329
1330 let flow_y = containing_block.content.y
1331 + containing_block.content.height
1332 + self.dimensions.margin.top;
1333 self.dimensions.content.x = containing_block.content.x
1334 + self.dimensions.margin.left
1335 + self.dimensions.border.left
1336 + self.dimensions.padding.left;
1337 self.dimensions.content.y =
1338 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
1339
1340 let (flex_dir, justify_content, align_items, align_content, row_gap, col_gap, flex_wrap) =
1341 match &self.box_type {
1342 BoxType::FlexNode(node) => {
1343 let (rg, cg) = parse_flex_gap(node, self.dimensions.content.width);
1344 (
1345 node.value("flex-direction")
1346 .unwrap_or_else(|| String::from("row"))
1347 .trim()
1348 .to_lowercase(),
1349 node.value("justify-content")
1350 .unwrap_or_else(|| String::from("flex-start"))
1351 .trim()
1352 .to_lowercase(),
1353 node.value("align-items")
1354 .unwrap_or_else(|| String::from("stretch"))
1355 .trim()
1356 .to_lowercase(),
1357 node.value("align-content")
1358 .unwrap_or_else(|| String::from("stretch"))
1359 .trim()
1360 .to_lowercase(),
1361 rg,
1362 cg,
1363 node.value("flex-wrap")
1364 .unwrap_or_else(|| String::from("nowrap"))
1365 .trim()
1366 .to_lowercase(),
1367 )
1368 }
1369 _ => return,
1370 };
1371
1372 let is_row = flex_dir == "row" || flex_dir == "row-reverse";
1374 let is_reverse = flex_dir == "row-reverse" || flex_dir == "column-reverse";
1375 let do_wrap = flex_wrap == "wrap" || flex_wrap == "wrap-reverse";
1377 let gap_main = if is_row { col_gap } else { row_gap };
1379 let gap_cross = if is_row { row_gap } else { col_gap };
1381
1382 if do_wrap {
1383 self.layout_flex_wrapped(
1384 is_row,
1385 is_reverse,
1386 flex_wrap == "wrap-reverse",
1387 &justify_content,
1388 &align_items,
1389 &align_content,
1390 gap_main,
1391 gap_cross,
1392 height_val.clone(),
1393 );
1394 return;
1395 }
1396
1397 let mut total_main = 0;
1399 let mut max_cross = 0;
1400 let oof: Vec<bool> = self
1403 .children
1404 .iter()
1405 .map(|c| Self::child_is_out_of_flow(c))
1406 .collect();
1407 let n = oof.iter().filter(|&&b| !b).count();
1408
1409 let basis_main_ref = if is_row {
1411 self.dimensions.content.width
1412 } else {
1413 match parse_length_value(height_val.clone()) {
1414 Some(LengthValue::Px(px)) => px,
1415 _ => 0,
1416 }
1417 };
1418
1419 for (ci, child) in self.children.iter_mut().enumerate() {
1420 let mut cb = self.dimensions.clone();
1421 cb.content.x = 0;
1422 cb.content.y = 0;
1423 cb.content.height = 0; child.layout(cb);
1425 if oof.get(ci).copied().unwrap_or(false) {
1426 continue;
1427 }
1428 {
1433 let (lead, trail) = box_main_auto_margins(child, is_row);
1434 if lead || trail {
1435 if is_row {
1436 child.dimensions.margin.left = 0;
1437 child.dimensions.margin.right = 0;
1438 } else {
1439 child.dimensions.margin.top = 0;
1440 child.dimensions.margin.bottom = 0;
1441 }
1442 }
1443 }
1444 if let Some(basis) = box_flex_basis(child, basis_main_ref) {
1446 if is_row {
1447 child.dimensions.content.width = basis;
1448 } else {
1449 child.dimensions.content.height = basis;
1450 }
1451 }
1452 if is_row
1464 && box_flex_basis(child, basis_main_ref).is_none()
1465 && !box_has_explicit(child, "width")
1466 {
1467 let max_content = child.block_intrinsic_widths().1;
1468 if max_content > 0 && max_content != child.dimensions.content.width {
1469 let mut cb2 = self.dimensions.clone();
1474 cb2.content.x = 0;
1475 cb2.content.y = 0;
1476 cb2.content.width = max_content;
1477 cb2.content.height = 0; child.layout(cb2);
1479 child.dimensions.content.width = max_content;
1480 }
1481 }
1482 let child_w = child.dimensions.margin_box().width;
1483 let child_h = child.dimensions.margin_box().height;
1484 if is_row {
1485 total_main += child_w;
1486 max_cross = max_cross.max(child_h);
1487 } else {
1488 total_main += child_h;
1489 max_cross = max_cross.max(child_w);
1490 }
1491 }
1492 if n > 1 {
1494 total_main += gap_main * (n as i32 - 1);
1495 }
1496
1497 let container_main = if is_row {
1498 self.dimensions.content.width
1499 } else {
1500 match parse_length_value(height_val.clone()) {
1501 Some(LengthValue::Px(px)) => px,
1502 _ => total_main,
1503 }
1504 };
1505
1506 let mut container_cross = if is_row {
1507 match parse_length_value(height_val.clone()) {
1508 Some(LengthValue::Px(px)) => px,
1509 _ => max_cross,
1510 }
1511 } else {
1512 self.dimensions.content.width
1513 };
1514
1515 if !is_row {
1516 self.dimensions.content.height = container_main;
1517 } else {
1518 self.dimensions.content.height = container_cross;
1519 }
1520
1521 let grows: Vec<i32> = self
1524 .children
1525 .iter()
1526 .enumerate()
1527 .map(|(i, c)| {
1528 if oof.get(i).copied().unwrap_or(false) {
1529 0
1530 } else {
1531 box_flex_grow(c)
1532 }
1533 })
1534 .collect();
1535 let total_grow: i32 = grows.iter().sum();
1536 if total_grow > 0 {
1537 let mut base_main = total_main;
1539 for (i, child) in self.children.iter().enumerate() {
1540 if grows.get(i).copied().unwrap_or(0) <= 0 {
1541 continue;
1542 }
1543 base_main -= if is_row {
1544 child.dimensions.content.width
1545 } else {
1546 child.dimensions.content.height
1547 };
1548 }
1549 let free = (container_main - base_main).max(0);
1550 if free > 0 {
1551 let last_grow_idx = grows.iter().rposition(|g| *g > 0);
1552 let mut remaining = free;
1553 for (i, child) in self.children.iter_mut().enumerate() {
1554 let g = grows.get(i).copied().unwrap_or(0);
1555 if g <= 0 {
1556 continue;
1557 }
1558 let extra = if Some(i) == last_grow_idx {
1560 remaining
1561 } else {
1562 free * g / total_grow
1563 };
1564 remaining -= extra;
1565 if is_row {
1566 child.dimensions.content.width = extra.max(0);
1567 } else {
1568 child.dimensions.content.height = extra.max(0);
1569 }
1570 }
1571 total_main = base_main + free; }
1573 }
1574
1575 let overflow = total_main - container_main;
1579 if total_grow == 0 && overflow > 0 {
1580 let shrinks: Vec<i32> = self
1581 .children
1582 .iter()
1583 .enumerate()
1584 .map(|(i, c)| {
1585 if oof.get(i).copied().unwrap_or(false) {
1586 0
1587 } else {
1588 box_flex_shrink(c)
1589 }
1590 })
1591 .collect();
1592 let total_shrink: i32 = shrinks.iter().sum();
1593 if total_shrink > 0 {
1594 let items: Vec<crate::os_lib::layout::flex_min::ShrinkItem> = self
1600 .children
1601 .iter()
1602 .enumerate()
1603 .map(|(i, c)| {
1604 let base = if is_row {
1605 c.dimensions.content.width
1606 } else {
1607 c.dimensions.content.height
1608 };
1609 let min = if is_row {
1611 c.block_intrinsic_widths().0
1612 } else {
1613 0
1614 };
1615 crate::os_lib::layout::flex_min::ShrinkItem {
1616 base: base.max(0),
1617 min: min.clamp(0, base.max(0)),
1618 shrink: shrinks.get(i).copied().unwrap_or(0).max(0),
1619 }
1620 })
1621 .collect();
1622
1623 match crate::os_lib::layout::flex_min::distribute_shrink(&items, overflow.max(0)) {
1624 Ok(sizes) => {
1625 for (i, child) in self.children.iter_mut().enumerate() {
1626 if let Some(&s) = sizes.get(i) {
1627 if is_row {
1628 child.dimensions.content.width = s;
1629 } else {
1630 child.dimensions.content.height = s;
1631 }
1632 }
1633 }
1634 if is_row {
1645 for (i, child) in self.children.iter_mut().enumerate() {
1646 if oof.get(i).copied().unwrap_or(false) {
1647 continue;
1648 }
1649 let Some(&s) = sizes.get(i) else {
1650 continue;
1651 };
1652 let base = items.get(i).map(|it| it.base).unwrap_or(s);
1653 if s >= base {
1655 continue;
1656 }
1657 let mut cb = self.dimensions.clone();
1658 cb.content.x = 0;
1659 cb.content.y = 0;
1660 cb.content.width = s;
1661 cb.content.height = 0; child.layout(cb);
1663 child.dimensions.content.width = s;
1665 }
1666 }
1667 total_main = sizes.iter().sum::<i32>().max(container_main.min(total_main));
1670 }
1671 Err(_) => {
1672 }
1675 }
1676 }
1677 }
1678
1679 if is_row && !matches!(parse_length_value(height_val.clone()), Some(LengthValue::Px(_))) {
1689 let mut recomputed = 0;
1690 for (ci, child) in self.children.iter().enumerate() {
1691 if oof.get(ci).copied().unwrap_or(false) {
1692 continue;
1693 }
1694 recomputed = recomputed.max(child.dimensions.margin_box().height);
1695 }
1696 if recomputed > container_cross {
1697 container_cross = recomputed;
1698 self.dimensions.content.height = container_cross;
1699 }
1700 }
1701
1702 if align_items == "stretch" {
1705 let cross_prop = if is_row { "height" } else { "width" };
1706 for (ci, child) in self.children.iter_mut().enumerate() {
1707 if oof.get(ci).copied().unwrap_or(false) {
1708 continue;
1709 }
1710 if box_has_explicit(child, cross_prop) {
1711 continue;
1712 }
1713 if let Some(s) = box_align_self(child) {
1715 if s != "stretch" {
1716 continue;
1717 }
1718 }
1719 let mb = child.dimensions.margin_box();
1720 let (cur_cross, cur_content) = if is_row {
1721 (mb.height, child.dimensions.content.height)
1722 } else {
1723 (mb.width, child.dimensions.content.width)
1724 };
1725 let non_content = cur_cross - cur_content; let new_content = (container_cross - non_content).max(0);
1727 if is_row {
1728 child.dimensions.content.height = new_content;
1729 } else {
1730 child.dimensions.content.width = new_content;
1731 }
1732 }
1733 }
1734
1735 let main_free_space = container_main - total_main;
1737
1738 let mut total_auto_margins = 0i32;
1741 for (ci, child) in self.children.iter().enumerate() {
1742 if oof.get(ci).copied().unwrap_or(false) {
1743 continue;
1744 }
1745 let (a, b) = box_main_auto_margins(child, is_row);
1746 total_auto_margins += a as i32 + b as i32;
1747 }
1748 let auto_margin_unit = if total_auto_margins > 0 && main_free_space > 0 {
1749 main_free_space / total_auto_margins
1750 } else {
1751 0
1752 };
1753 let use_auto_margins = total_auto_margins > 0 && main_free_space > 0;
1754
1755 let main_offset = if use_auto_margins {
1756 0
1757 } else {
1758 match justify_content.as_str() {
1759 "flex-end" | "end" => main_free_space,
1764 "center" => main_free_space / 2,
1765 "space-around" if n > 0 => main_free_space / (n as i32 * 2),
1766 "space-evenly" if n > 0 => main_free_space / (n as i32 + 1),
1770 _ => 0,
1772 }
1773 };
1774
1775 let spacing = if use_auto_margins {
1776 0
1777 } else {
1778 match justify_content.as_str() {
1779 "space-between" if n > 1 => main_free_space / (n as i32 - 1),
1780 "space-around" if n > 0 => main_free_space / (n as i32),
1781 "space-evenly" if n > 0 => main_free_space / (n as i32 + 1),
1782 _ => 0,
1783 }
1784 };
1785
1786 let mut order_idx: Vec<usize> = (0..self.children.len()).collect();
1790 order_idx.sort_by_key(|&i| (box_order(&self.children[i]), i));
1791 let mut cursor = main_offset;
1792 for &ci in &order_idx {
1793 if oof.get(ci).copied().unwrap_or(false) {
1795 continue;
1796 }
1797 let (lead_auto, trail_auto) = if use_auto_margins {
1799 box_main_auto_margins(&self.children[ci], is_row)
1800 } else {
1801 (false, false)
1802 };
1803 if lead_auto {
1804 cursor += auto_margin_unit;
1805 }
1806 let child_w = self.children[ci].dimensions.margin_box().width;
1807 let child_h = self.children[ci].dimensions.margin_box().height;
1808 let child_main = if is_row { child_w } else { child_h };
1809
1810 let cross_free_space = container_cross - if is_row { child_h } else { child_w };
1811 let effective_align =
1813 box_align_self(&self.children[ci]).unwrap_or_else(|| align_items.clone());
1814 let cross_offset = match effective_align.as_str() {
1815 "flex-end" | "end" | "self-end" => cross_free_space,
1819 "center" => cross_free_space / 2,
1820 _ => 0, };
1822
1823 let main_start = if is_reverse {
1825 container_main - cursor - child_main
1826 } else {
1827 cursor
1828 };
1829
1830 let start_x = self.dimensions.content.x;
1832 let start_y = self.dimensions.content.y;
1833
1834 let target_x = if is_row {
1835 start_x + main_start
1836 } else {
1837 start_x + cross_offset
1838 };
1839 let target_y = if is_row {
1840 start_y + cross_offset
1841 } else {
1842 start_y + main_start
1843 };
1844
1845 let shift_x = target_x - self.children[ci].dimensions.margin_box().x;
1846 let shift_y = target_y - self.children[ci].dimensions.margin_box().y;
1847
1848 shift_layout_box(&mut self.children[ci], shift_x, shift_y);
1849
1850 cursor += child_main + spacing + gap_main;
1851 if trail_auto {
1852 cursor += auto_margin_unit;
1853 }
1854 }
1855
1856 let saved_abs_cb = self.establish_abs_cb();
1859 self.apply_positioned_offsets();
1860 Self::restore_abs_cb(saved_abs_cb);
1861 }
1862
1863 #[allow(clippy::too_many_arguments)]
1867 fn layout_flex_wrapped(
1868 &mut self,
1869 is_row: bool,
1870 is_reverse: bool,
1871 wrap_reverse: bool,
1872 justify_content: &str,
1873 align_items: &str,
1874 align_content: &str,
1875 gap_main: i32,
1876 gap_cross: i32,
1877 height_val: Option<String>,
1878 ) {
1879 let n = self.children.len();
1880 if n == 0 {
1881 return;
1882 }
1883
1884 let oof: Vec<bool> = self
1888 .children
1889 .iter()
1890 .map(|c| Self::child_is_out_of_flow(c))
1891 .collect();
1892 let mut sizes: Vec<(i32, i32)> = Vec::with_capacity(n); for child in &mut self.children {
1894 let mut cb = self.dimensions.clone();
1895 cb.content.x = 0;
1896 cb.content.y = 0;
1897 cb.content.height = 0;
1898 child.layout(cb);
1899 let w = child.dimensions.margin_box().width;
1900 let h = child.dimensions.margin_box().height;
1901 if is_row {
1902 sizes.push((w, h));
1903 } else {
1904 sizes.push((h, w));
1905 }
1906 }
1907 let flow_idx: Vec<usize> = (0..n).filter(|&i| !oof[i]).collect();
1909
1910 let container_main = if is_row {
1911 self.dimensions.content.width
1912 } else {
1913 match parse_length_value(height_val.clone()) {
1914 Some(LengthValue::Px(px)) => px,
1915 _ => i32::MAX / 4, }
1917 };
1918
1919 let fln = flow_idx.len();
1921 let mut lines: Vec<(usize, usize, i32, i32)> = Vec::new(); let mut line_start = 0usize;
1923 let mut line_main = 0i32;
1924 let mut line_cross = 0i32;
1925 for pos in 0..fln {
1926 let (m, c) = sizes[flow_idx[pos]];
1927 let add = if pos == line_start { m } else { m + gap_main };
1928 if pos > line_start && line_main + add > container_main {
1929 lines.push((line_start, pos, line_main, line_cross));
1930 line_start = pos;
1931 line_main = m;
1932 line_cross = c;
1933 } else {
1934 line_main += add;
1935 line_cross = line_cross.max(c);
1936 }
1937 }
1938 lines.push((line_start, fln, line_main, line_cross));
1939
1940 if wrap_reverse {
1942 lines.reverse();
1943 }
1944
1945 let total_cross: i32 =
1947 lines.iter().map(|l| l.3).sum::<i32>() + gap_cross * (lines.len() as i32 - 1).max(0);
1948 let container_cross = if is_row {
1949 match parse_length_value(height_val.clone()) {
1950 Some(LengthValue::Px(px)) => px,
1951 _ => total_cross,
1952 }
1953 } else {
1954 self.dimensions.content.width
1955 };
1956
1957 if is_row {
1958 self.dimensions.content.height = container_cross.max(total_cross);
1959 } else {
1960 self.dimensions.content.height = container_main.min(total_cross).max(total_cross);
1961 }
1962
1963 let base_x = self.dimensions.content.x;
1964 let base_y = self.dimensions.content.y;
1965
1966 let nlines = lines.len() as i32;
1971 let cross_free = (container_cross - total_cross).max(0);
1972 let mut line_cross_size: Vec<i32> = lines.iter().map(|l| l.3).collect();
1973 if (align_content == "stretch" || align_content.is_empty()) && cross_free > 0 && nlines > 0
1975 {
1976 let per = cross_free / nlines;
1977 let mut rem = cross_free - per * nlines;
1978 for sz in line_cross_size.iter_mut() {
1979 *sz += per;
1980 if rem > 0 {
1981 *sz += 1;
1982 rem -= 1;
1983 }
1984 }
1985 }
1986 let (ac_offset, ac_spacing) = match align_content {
1988 "flex-end" | "end" => (cross_free, 0),
1989 "center" => (cross_free / 2, 0),
1990 "space-between" if nlines > 1 => (0, cross_free / (nlines - 1)),
1991 "space-around" if nlines > 0 => (cross_free / (nlines * 2), cross_free / nlines),
1992 "space-evenly" if nlines > 0 => (cross_free / (nlines + 1), cross_free / (nlines + 1)),
1993 _ => (0, 0),
1995 };
1996 let mut line_cross_start: Vec<i32> = Vec::with_capacity(lines.len());
1997 let mut cc = ac_offset;
1998 for sz in &line_cross_size {
1999 line_cross_start.push(cc);
2000 cc += sz + gap_cross + ac_spacing;
2001 }
2002
2003 for (li, &(start, end_excl, line_main_used, _line_cross_nat)) in lines.iter().enumerate() {
2004 let count = end_excl - start;
2005 let main_free = container_main - line_main_used;
2006 let line_cross = line_cross_size.get(li).copied().unwrap_or(0);
2007 let cross_cursor = line_cross_start.get(li).copied().unwrap_or(0);
2008
2009 let mut main_offset = match justify_content {
2011 "flex-end" | "end" => main_free,
2012 "center" => main_free / 2,
2013 "space-around" if count > 0 => main_free / (count as i32 * 2),
2014 "space-evenly" if count > 0 => main_free / (count as i32 + 1),
2015 _ => 0,
2016 };
2017 let spacing = match justify_content {
2018 "space-between" if count > 1 => main_free / (count as i32 - 1),
2019 "space-around" if count > 0 => main_free / (count as i32),
2020 "space-evenly" if count > 0 => main_free / (count as i32 + 1),
2021 _ => 0,
2022 };
2023
2024 let pos_list: Vec<usize> = if is_reverse {
2026 (start..end_excl).rev().collect()
2027 } else {
2028 (start..end_excl).collect()
2029 };
2030
2031 for &pos in &pos_list {
2032 let ci = flow_idx[pos];
2033 let (m, c) = sizes[ci];
2034 let cross_free_item = line_cross - c;
2035 let effective_align =
2036 box_align_self(&self.children[ci]).unwrap_or_else(|| String::from(align_items));
2037 let cross_in_line = match effective_align.as_str() {
2038 "flex-end" | "end" | "self-end" => cross_free_item,
2039 "center" => cross_free_item / 2,
2040 _ => 0, };
2042 let cross_pos = cross_cursor + cross_in_line;
2043
2044 let (target_x, target_y) = if is_row {
2045 (base_x + main_offset, base_y + cross_pos)
2046 } else {
2047 (base_x + cross_pos, base_y + main_offset)
2048 };
2049
2050 let shift_x = target_x - self.children[ci].dimensions.margin_box().x;
2051 let shift_y = target_y - self.children[ci].dimensions.margin_box().y;
2052 shift_layout_box(&mut self.children[ci], shift_x, shift_y);
2053
2054 main_offset += m + spacing + gap_main;
2055 }
2056 }
2057
2058 let saved_abs_cb = self.establish_abs_cb();
2060 self.apply_positioned_offsets();
2061 Self::restore_abs_cb(saved_abs_cb);
2062 }
2063
2064 fn layout_block(&mut self, containing_block: Dimensions) {
2065 self.apply_box_model_styles_with_base(containing_block.content.width);
2066
2067 let (
2070 width_val,
2071 min_width_val,
2072 max_width_val,
2073 height_val,
2074 min_height_val,
2075 box_sizing,
2076 h_auto,
2077 aspect_ratio_val,
2078 ) = match &self.box_type {
2079 BoxType::BlockNode(node)
2080 | BoxType::TableRowGroupNode(node)
2081 | BoxType::TableRowNode(node)
2082 | BoxType::TableCellNode(node) => (
2083 node.value("width"),
2084 node.value("min-width"),
2085 node.value("max-width"),
2086 node.value("height"),
2087 node.value("min-height"),
2088 node.value("box-sizing").unwrap_or_default(),
2089 margin_is_h_auto(node),
2090 node.value("aspect-ratio"),
2091 ),
2092 _ => return,
2093 };
2094
2095 let horizontal_non_content = self.dimensions.margin.left
2096 + self.dimensions.margin.right
2097 + self.dimensions.border.left
2098 + self.dimensions.border.right
2099 + self.dimensions.padding.left
2100 + self.dimensions.padding.right;
2101 let available_width = containing_block.content.width.max(1);
2102 let inner_non_content = self.dimensions.border.left
2104 + self.dimensions.border.right
2105 + self.dimensions.padding.left
2106 + self.dimensions.padding.right;
2107
2108 let parsed_width = parse_length_value(width_val.clone());
2110 let mut content_width = match parsed_width {
2111 Some(LengthValue::Auto) | None => (available_width - horizontal_non_content).max(1),
2112 Some(LengthValue::Px(px)) => px.max(1),
2113 Some(LengthValue::Percent(pct)) => {
2114 round_f32_to_i32(available_width as f32 * pct).max(1)
2115 }
2116 Some(LengthValue::MinContent) => {
2118 let (minc, _maxc) = self.block_intrinsic_widths();
2119 minc.max(1)
2120 }
2121 Some(LengthValue::MaxContent) => {
2122 let (_minc, maxc) = self.block_intrinsic_widths();
2123 maxc.max(1)
2124 }
2125 Some(LengthValue::FitContent) => {
2126 let avail = (available_width - horizontal_non_content).max(1);
2128 let (minc, maxc) = self.block_intrinsic_widths();
2129 maxc.min(avail).max(minc).max(1)
2130 }
2131 Some(LengthValue::MathExpr(expr)) => {
2132 crate::os_lib::css::eval_css_math(&expr, available_width)
2133 .unwrap_or((available_width - horizontal_non_content).max(1))
2134 .max(1)
2135 }
2136 };
2137 if box_sizing.trim() == "border-box" {
2139 content_width = (content_width - inner_non_content).max(1);
2140 }
2141
2142 if let Some(min_width) = self.resolve_sizing_value(min_width_val.clone(), available_width) {
2144 content_width = content_width.max(min_width);
2145 }
2146 if let Some(max_width) = self.resolve_sizing_value(max_width_val.clone(), available_width) {
2147 content_width = content_width.min(max_width);
2148 }
2149 self.dimensions.content.width = content_width.max(1);
2150
2151 if h_auto {
2153 let free = (available_width - content_width - inner_non_content).max(0);
2154 self.dimensions.margin.left = free / 2;
2155 self.dimensions.margin.right = free / 2;
2156 }
2157
2158 let flow_y = containing_block.content.y
2160 + containing_block.content.height
2161 + self.dimensions.margin.top;
2162 self.dimensions.content.x = containing_block.content.x
2163 + self.dimensions.margin.left
2164 + self.dimensions.border.left
2165 + self.dimensions.padding.left;
2166 self.dimensions.content.y =
2167 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
2168
2169 let self_pos = box_position(self);
2172 let saved_abs_cb = if matches!(self_pos.as_str(), "relative" | "absolute" | "fixed") {
2173 let prev = abs_cb_get();
2174 let exp_h = parse_length_value(height_val.clone())
2175 .and_then(|v| resolve_length_value(v, containing_block.content.height.max(0)))
2176 .unwrap_or(0);
2177 abs_cb_set(
2178 self.dimensions.content.x,
2179 self.dimensions.content.y,
2180 self.dimensions.content.width,
2181 exp_h,
2182 );
2183 Some(prev)
2184 } else {
2185 None
2186 };
2187
2188 let line_height_default = 20i32;
2190 let mut y_cursor = 0i32;
2191 let mut line_x = 0i32;
2192 let mut line_h = 0i32;
2193 let max_w = self.dimensions.content.width.max(1);
2194
2195 let mut left_float: Option<(i32, i32)> = None;
2199 let mut right_float: Option<(i32, i32)> = None;
2200
2201 fn float_avail(
2203 max_w: i32,
2204 left_float: Option<(i32, i32)>,
2205 right_float: Option<(i32, i32)>,
2206 y: i32,
2207 ) -> (i32, i32) {
2208 let l = left_float
2209 .filter(|&(_, yb)| yb > y)
2210 .map(|(w, _)| w)
2211 .unwrap_or(0);
2212 let r = right_float
2213 .filter(|&(_, yb)| yb > y)
2214 .map(|(w, _)| w)
2215 .unwrap_or(0);
2216 (l, (max_w - l - r).max(1))
2217 }
2218
2219 for child in &mut self.children {
2220 let float_side = box_float(child);
2221 if float_side != "none" {
2222 let float_y = y_cursor;
2224 let is_inline_kind =
2225 matches!(child.box_type, BoxType::InlineNode(_) | BoxType::InlineBlockNode(_));
2226 let (fw, fh) = if is_inline_kind {
2227 child.intrinsic_inline_size(max_w)
2228 } else {
2229 let mut cb = self.dimensions.clone();
2230 cb.content.height = float_y;
2231 child.layout(cb);
2232 (
2233 child.dimensions.margin_box().width,
2234 child.dimensions.margin_box().height,
2235 )
2236 };
2237
2238 let x = if float_side == "left" {
2239 let x_off = left_float
2240 .filter(|&(_, yb)| yb > float_y)
2241 .map(|(w, _)| w)
2242 .unwrap_or(0);
2243 left_float = Some((x_off + fw, float_y + fh));
2244 self.dimensions.content.x + x_off
2245 } else {
2246 let x_off = right_float
2247 .filter(|&(_, yb)| yb > float_y)
2248 .map(|(w, _)| w)
2249 .unwrap_or(0);
2250 right_float = Some((x_off + fw, float_y + fh));
2251 self.dimensions.content.x + max_w - x_off - fw
2252 };
2253
2254 if is_inline_kind {
2255 child.dimensions.content.x = x;
2256 child.dimensions.content.y = self.dimensions.content.y + float_y;
2257 child.dimensions.content.width = fw;
2258 child.dimensions.content.height = fh;
2259 let mut cb = child.dimensions.clone();
2260 cb.content.height = 0;
2261 child.layout(cb);
2262 child.dimensions.content.x = x;
2263 child.dimensions.content.y = self.dimensions.content.y + float_y;
2264 child.dimensions.content.width = fw;
2265 child.dimensions.content.height = fh;
2266 } else {
2267 let dx = x - child.dimensions.content.x;
2269 child.dimensions.content.x += dx;
2270 }
2271 continue;
2273 }
2274
2275 match child.box_type {
2276 BoxType::BlockNode(_)
2277 | BoxType::FlexNode(_)
2278 | BoxType::GridNode(_)
2279 | BoxType::AnonymousBlock
2280 | BoxType::TableNode(_)
2281 | BoxType::TableRowGroupNode(_)
2282 | BoxType::TableRowNode(_)
2283 | BoxType::TableCellNode(_) => {
2284 let out_of_flow = matches!(box_position(child).as_str(), "absolute" | "fixed");
2285 if out_of_flow {
2286 let mut cb = self.dimensions.clone();
2288 cb.content.height = y_cursor;
2289 child.layout(cb);
2290 } else {
2291 if line_x > 0 {
2292 y_cursor += if line_h > 0 { line_h } else { line_height_default };
2293 line_x = 0;
2294 line_h = 0;
2295 }
2296 let clear = box_clear(child);
2298 if clear == "left" || clear == "both" {
2299 if let Some((_, yb)) = left_float {
2300 y_cursor = y_cursor.max(yb);
2301 }
2302 }
2303 if clear == "right" || clear == "both" {
2304 if let Some((_, yb)) = right_float {
2305 y_cursor = y_cursor.max(yb);
2306 }
2307 }
2308 let (x_off, avail) = float_avail(max_w, left_float, right_float, y_cursor);
2309 let mut cb = self.dimensions.clone();
2310 cb.content.height = y_cursor;
2311 cb.content.x = self.dimensions.content.x + x_off;
2312 cb.content.width = avail;
2313 child.layout(cb);
2314 y_cursor += child.dimensions.margin_box().height;
2315 }
2316 }
2317 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
2318 if child.is_inline_line_break() {
2319 y_cursor += if line_h > 0 { line_h } else { line_height_default };
2320 line_x = 0;
2321 line_h = 0;
2322 child.dimensions.content.x = self.dimensions.content.x;
2323 child.dimensions.content.y = self.dimensions.content.y + y_cursor;
2324 child.dimensions.content.width = 0;
2325 child.dimensions.content.height = 0;
2326 continue;
2327 }
2328 let (mut x_off, mut eff_w) =
2329 float_avail(max_w, left_float, right_float, y_cursor);
2330 let (inline_w, inline_h) = child.intrinsic_inline_size(eff_w);
2331 if line_x > 0 && line_x + inline_w > eff_w {
2332 y_cursor += if line_h > 0 { line_h } else { line_height_default };
2333 line_x = 0;
2334 line_h = 0;
2335 let recomputed = float_avail(max_w, left_float, right_float, y_cursor);
2336 x_off = recomputed.0;
2337 eff_w = recomputed.1;
2338 }
2339 let _ = eff_w;
2340 child.dimensions.content.x = self.dimensions.content.x + x_off + line_x;
2341 child.dimensions.content.y = self.dimensions.content.y + y_cursor;
2342 child.dimensions.content.width = inline_w;
2343 child.dimensions.content.height = inline_h;
2344
2345 let mut cb = child.dimensions.clone();
2351 cb.content.height = 0;
2352 child.layout(cb);
2353 child.dimensions.content.x = self.dimensions.content.x + x_off + line_x;
2356 child.dimensions.content.y = self.dimensions.content.y + y_cursor;
2357 child.dimensions.content.width = inline_w;
2358 child.dimensions.content.height = inline_h;
2359
2360 line_x += inline_w;
2361 if inline_h > line_h {
2362 line_h = inline_h;
2363 }
2364 }
2365 }
2366 }
2367
2368 if line_x > 0 {
2369 y_cursor += if line_h > 0 { line_h } else { line_height_default };
2370 }
2371 if let Some((_, yb)) = left_float {
2374 y_cursor = y_cursor.max(yb);
2375 }
2376 if let Some((_, yb)) = right_float {
2377 y_cursor = y_cursor.max(yb);
2378 }
2379
2380 if let BoxType::BlockNode(node) = &self.box_type {
2392 let cc = node.value("column-count");
2393 let cw = node.value("column-width");
2394 let shorthand = node.value("columns");
2395 let (sh_w, sh_c) = shorthand
2396 .as_deref()
2397 .map(crate::os_lib::layout::multicol::parse_columns_shorthand)
2398 .unwrap_or((None, None));
2399 let parse_px = |v: &Option<String>| -> Option<i32> {
2400 v.as_deref()
2401 .and_then(|s| s.trim().strip_suffix("px"))
2402 .and_then(|n| n.trim().parse::<f32>().ok())
2403 .filter(|f| f.is_finite() && *f > 0.0)
2404 .map(|f| f as i32)
2405 };
2406 let count = cc
2407 .as_deref()
2408 .and_then(|s| s.trim().parse::<i32>().ok())
2409 .filter(|v| *v > 0)
2410 .or(sh_c);
2411 let width = parse_px(&cw).or(sh_w);
2412 if count.is_some() || width.is_some() {
2413 let gap = node
2416 .value("column-gap")
2417 .as_deref()
2418 .and_then(|s| s.trim().strip_suffix("px"))
2419 .and_then(|n| n.trim().parse::<f32>().ok())
2420 .filter(|f| f.is_finite() && *f >= 0.0)
2421 .map(|f| f as i32)
2422 .unwrap_or(16);
2423 let spec = crate::os_lib::layout::multicol::MultiColSpec { count, width, gap };
2424 let cols = crate::os_lib::layout::multicol::layout_columns(
2425 &spec,
2426 self.dimensions.content.width.max(0),
2427 );
2428 if cols.len() > 1 {
2429 let base_x = self.dimensions.content.x;
2430 let base_y = self.dimensions.content.y;
2431 let flow_idx: alloc::vec::Vec<usize> = self
2436 .children
2437 .iter()
2438 .enumerate()
2439 .filter(|(_, c)| box_float(c) == "none")
2440 .map(|(i, _)| i)
2441 .collect();
2442 let heights: alloc::vec::Vec<i32> = flow_idx
2443 .iter()
2444 .map(|&i| self.children[i].dimensions.margin_box().height)
2445 .collect();
2446 let places =
2447 crate::os_lib::layout::multicol::assign_to_columns(&heights, cols.len());
2448 for (slot, &ci) in flow_idx.iter().enumerate() {
2449 let place = places[slot];
2450 let col = cols[place.col];
2451 let child = &mut self.children[ci];
2452 let dx = base_x + col.x - child.dimensions.content.x
2455 + child.dimensions.margin.left
2456 + child.dimensions.border.left
2457 + child.dimensions.padding.left;
2458 let dy = base_y + place.y - child.dimensions.content.y
2459 + child.dimensions.margin.top
2460 + child.dimensions.border.top
2461 + child.dimensions.padding.top;
2462 child.translate(dx, dy);
2463 child.dimensions.content.width = (col.width
2464 - child.dimensions.margin.left
2465 - child.dimensions.margin.right
2466 - child.dimensions.border.left
2467 - child.dimensions.border.right
2468 - child.dimensions.padding.left
2469 - child.dimensions.padding.right)
2470 .max(0);
2471 }
2472 let totals =
2474 crate::os_lib::layout::multicol::column_heights(&heights, cols.len());
2475 y_cursor = totals.iter().copied().max().unwrap_or(y_cursor);
2476 }
2477 }
2478 }
2479
2480 self.dimensions.content.height = y_cursor;
2482 let cb_height = containing_block.content.height.max(0);
2483 if let Some(h) =
2484 parse_length_value(height_val.clone()).and_then(|v| resolve_length_value(v, cb_height))
2485 {
2486 let vertical_non_content = self.dimensions.border.top
2488 + self.dimensions.border.bottom
2489 + self.dimensions.padding.top
2490 + self.dimensions.padding.bottom;
2491 let content_h = if box_sizing.trim() == "border-box" {
2492 (h - vertical_non_content).max(0)
2493 } else {
2494 h.max(0)
2495 };
2496 self.dimensions.content.height = content_h;
2497 }
2498 if let Some(mh) =
2499 parse_length_value(min_height_val).and_then(|v| resolve_length_value(v, cb_height))
2500 {
2501 self.dimensions.content.height = self.dimensions.content.height.max(mh);
2502 }
2503
2504 if let Some((num, den)) = aspect_ratio_val.as_deref().and_then(parse_aspect_ratio) {
2507 let width_is_auto = matches!(
2508 parse_length_value(width_val.clone()),
2509 Some(LengthValue::Auto) | None
2510 );
2511 let height_is_auto = parse_length_value(height_val.clone()).is_none();
2512 if height_is_auto {
2513 self.dimensions.content.height =
2515 round_f32_to_i32(self.dimensions.content.width as f32 * den / num).max(1);
2516 } else if width_is_auto {
2517 let h = self.dimensions.content.height as f32;
2520 let mut w = round_f32_to_i32(h * num / den).max(1);
2521 if let Some(min_w) = parse_length_value(min_width_val.clone())
2522 .and_then(|v| resolve_length_value(v, available_width))
2523 {
2524 w = w.max(min_w);
2525 }
2526 if let Some(max_w) = parse_length_value(max_width_val.clone())
2527 .and_then(|v| resolve_length_value(v, available_width))
2528 {
2529 w = w.min(max_w);
2530 }
2531 self.dimensions.content.width = w.max(1);
2532 }
2533 }
2534
2535 let (text_align, direction, text_align_last) = match &self.box_type {
2539 BoxType::BlockNode(node) | BoxType::TableCellNode(node) => (
2540 node.value("text-align").unwrap_or_default(),
2541 node.value("direction").unwrap_or_default(),
2542 node.value("text-align-last").unwrap_or_default(),
2543 ),
2544 _ => (String::new(), String::new(), String::new()),
2545 };
2546 let is_rtl = direction.trim().eq_ignore_ascii_case("rtl");
2547 let mut ta = text_align.trim().to_lowercase();
2548 if ta.is_empty() && is_rtl {
2549 ta = String::from("right");
2550 }
2551 ta = match ta.as_str() {
2554 "start" => String::from(if is_rtl { "right" } else { "left" }),
2555 "end" => String::from(if is_rtl { "left" } else { "right" }),
2556 _ => ta,
2557 };
2558 let ta_last_resolved = match text_align_last.trim().to_lowercase().as_str() {
2562 "start" => String::from(if is_rtl { "right" } else { "left" }),
2563 "end" => String::from(if is_rtl { "left" } else { "right" }),
2564 other => String::from(other),
2565 };
2566 let ta_last_effective = if matches!(ta_last_resolved.as_str(), "left" | "center" | "right")
2567 {
2568 Some(ta_last_resolved)
2569 } else {
2570 None
2571 };
2572 if ta == "center" || ta == "right" || ta == "justify" || ta_last_effective.is_some() {
2573 let content_x = self.dimensions.content.x;
2574 let content_w = self.dimensions.content.width.max(1);
2575 let n = self.children.len();
2576
2577 let mut line_ys: Vec<i32> = Vec::new();
2579 for ci in 0..n {
2580 match self.children[ci].box_type {
2581 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
2582 let cy = self.children[ci].dimensions.content.y;
2583 if !line_ys.contains(&cy) {
2584 line_ys.push(cy);
2585 }
2586 }
2587 _ => {}
2588 }
2589 }
2590 let last_line_y = line_ys.iter().copied().max();
2591
2592 for line_y in line_ys {
2593 let mut line_indices = Vec::new();
2595 let mut total_w = 0i32;
2596 for ci in 0..n {
2597 match self.children[ci].box_type {
2598 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_)
2599 if self.children[ci].dimensions.content.y == line_y =>
2600 {
2601 line_indices.push(ci);
2602 total_w += self.children[ci].dimensions.content.width;
2603 }
2604 _ => {}
2605 }
2606 }
2607
2608 let is_last_line = last_line_y == Some(line_y);
2609 let effective_ta = if is_last_line {
2610 ta_last_effective.as_deref().unwrap_or(if ta == "justify" {
2611 if is_rtl { "right" } else { "left" }
2612 } else {
2613 ta.as_str()
2614 })
2615 } else {
2616 ta.as_str()
2617 };
2618
2619 if effective_ta == "justify" {
2620 let k = line_indices.len();
2621 if k > 1 {
2622 let remaining = (content_w - total_w).max(0);
2623 for (i, &ci) in line_indices.iter().enumerate() {
2624 let shift = (remaining * i as i32) / (k as i32 - 1);
2625 self.children[ci].dimensions.content.x = content_x
2626 + shift
2627 + (self.children[ci].dimensions.content.x - content_x);
2628 }
2629 }
2630 } else {
2631 let offset = match effective_ta {
2632 "center" => ((content_w - total_w) / 2).max(0),
2633 "right" => (content_w - total_w).max(0),
2634 _ => 0,
2635 };
2636 if offset > 0 {
2637 for &ci in &line_indices {
2638 self.children[ci].dimensions.content.x = content_x
2639 + offset
2640 + (self.children[ci].dimensions.content.x - content_x);
2641 }
2642 }
2643 }
2644 }
2645 }
2646
2647 self.apply_positioned_offsets();
2649
2650 if let Some(prev) = saved_abs_cb {
2652 abs_cb_set(prev.0, prev.1, prev.2, prev.3);
2653 }
2654 }
2655
2656 fn intrinsic_inline_size_ext(&self, avail_hint: i32, pure: bool) -> (i32, i32) {
2672 match &self.box_type {
2673 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
2674 if let Some(size) = get_replaced_element_size(node) {
2675 return size;
2676 }
2677 let font_size = node
2678 .value("font-size")
2679 .and_then(|v| parse_font_size_u32(&v))
2680 .unwrap_or(16);
2681 let line_height_val = node.value("line-height");
2682 let line_height = parse_line_height(line_height_val, font_size);
2683
2684 let css_w = node.value("width").and_then(|v| match parse_length_value(Some(v)) {
2692 Some(LengthValue::Px(px)) => Some(px),
2693 Some(LengthValue::Percent(pct)) => {
2694 Some(round_f32_to_i32(avail_hint as f32 * pct))
2695 }
2696 _ => None,
2697 });
2698 let css_h = node
2699 .value("height")
2700 .and_then(|v| match parse_length_value(Some(v)) {
2701 Some(LengthValue::Px(px)) => Some(px),
2702 _ => None,
2703 });
2704
2705 match &node.node.node_type {
2706 NodeType::Text(text) => {
2707 let nowrap = matches!(
2711 node.value("white-space").as_deref(),
2712 Some("nowrap") | Some("pre")
2713 );
2714 let pre_wrap = matches!(
2715 node.value("white-space").as_deref(),
2716 Some("pre-wrap") | Some("pre-line")
2717 );
2718 let break_all = matches!(
2719 node.value("word-break").as_deref(),
2720 Some("break-all")
2721 ) || matches!(
2722 node.value("overflow-wrap").as_deref(),
2723 Some("break-word")
2724 );
2725 let available = if nowrap {
2735 i32::MAX / 2
2736 } else {
2737 css_w.unwrap_or({
2738 if pure {
2739 if avail_hint > 0 {
2741 avail_hint
2742 } else {
2743 800
2744 }
2745 } else if self.dimensions.content.width > 0 {
2746 self.dimensions.content.width
2747 } else if avail_hint > 0 {
2748 avail_hint
2749 } else {
2750 800
2751 }
2752 })
2753 };
2754 let ls = parse_letter_spacing_ctx(node.value("letter-spacing").as_deref(), available);
2755 let ws = parse_word_spacing_ctx(node.value("word-spacing").as_deref(), available);
2756 let (w, h) = crate::kernel::vector_font::get_vector_string_wrapped_size_ext(
2757 text,
2758 font_size,
2759 available as u32,
2760 ls,
2761 ws,
2762 break_all,
2763 pre_wrap,
2764 );
2765 let lines = h as i32 / (font_size as i32 + 4).max(1);
2766 (
2767 css_w.unwrap_or(w as i32),
2768 css_h.unwrap_or(lines * line_height),
2769 )
2770 }
2771 NodeType::Element { tag_name, .. } => {
2772 if tag_name == "br" {
2773 (0, line_height)
2774 } else {
2775 let (content_w, content_h) = if self.children.is_empty() {
2776 (8, line_height)
2777 } else {
2778 let mut total_w = 0i32;
2779 let mut max_h = line_height;
2780 for child in &self.children {
2781 let (cw, ch) = child.intrinsic_inline_size(avail_hint);
2791 total_w += cw;
2792 if ch > max_h {
2793 max_h = ch;
2794 }
2795 }
2796 (total_w, max_h)
2797 };
2798 (css_w.unwrap_or(content_w), css_h.unwrap_or(content_h))
2799 }
2800 }
2801 }
2802 }
2803 _ => (0, 0),
2804 }
2805 }
2806
2807 fn intrinsic_inline_size(&self, avail_hint: i32) -> (i32, i32) {
2809 self.intrinsic_inline_size_ext(avail_hint, false)
2810 }
2811
2812 fn layout_inline(&mut self, containing_block: Dimensions, _block_like: bool) {
2813 self.apply_box_model_styles_with_base(containing_block.content.width);
2814
2815 let (width_val, min_width_val, max_width_val, height_val, min_height_val, max_height_val) =
2816 match &self.box_type {
2817 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => (
2818 node.value("width"),
2819 node.value("min-width"),
2820 node.value("max-width"),
2821 node.value("height"),
2822 node.value("min-height"),
2823 node.value("max-height"),
2824 ),
2825 _ => return,
2826 };
2827
2828 let available_width = containing_block.content.width.max(1);
2829 let available_height = containing_block.content.height.max(1);
2830 let (intrinsic_w, intrinsic_h) = self.inline_content_intrinsic_size(available_width);
2831
2832 let mut content_width = match parse_length_value(width_val) {
2833 Some(LengthValue::Auto) | None => intrinsic_w.max(1),
2834 Some(LengthValue::Px(px)) => px.max(1),
2835 Some(LengthValue::Percent(pct)) => {
2836 round_f32_to_i32(available_width as f32 * pct).max(1)
2837 }
2838 Some(LengthValue::MinContent) => {
2839 let (minc, _maxc) = self.block_intrinsic_widths();
2840 minc.max(1)
2841 }
2842 Some(LengthValue::MaxContent) => {
2843 let (_minc, maxc) = self.block_intrinsic_widths();
2844 maxc.max(1)
2845 }
2846 Some(LengthValue::FitContent) => {
2847 let (minc, maxc) = self.block_intrinsic_widths();
2848 available_width.min(maxc).max(minc).max(1)
2849 }
2850 Some(LengthValue::MathExpr(expr)) => {
2851 crate::os_lib::css::eval_css_math(&expr, available_width)
2852 .unwrap_or(intrinsic_w.max(1))
2853 .max(1)
2854 }
2855 };
2856 if let Some(min_width) =
2857 parse_length_value(min_width_val).and_then(|v| resolve_length_value(v, available_width))
2858 {
2859 content_width = content_width.max(min_width);
2860 }
2861 if let Some(max_width) =
2862 parse_length_value(max_width_val).and_then(|v| resolve_length_value(v, available_width))
2863 {
2864 content_width = content_width.min(max_width);
2865 }
2866
2867 let mut content_height = match parse_length_value(height_val) {
2868 Some(LengthValue::Auto)
2869 | Some(LengthValue::MinContent)
2870 | Some(LengthValue::MaxContent)
2871 | Some(LengthValue::FitContent)
2872 | None => intrinsic_h.max(1),
2873 Some(LengthValue::Px(px)) => px.max(1),
2874 Some(LengthValue::Percent(pct)) => {
2875 round_f32_to_i32(available_height as f32 * pct).max(1)
2876 }
2877 Some(LengthValue::MathExpr(expr)) => {
2878 crate::os_lib::css::eval_css_math(&expr, available_height)
2879 .unwrap_or(intrinsic_h.max(1))
2880 .max(1)
2881 }
2882 };
2883 if let Some(min_height) = parse_length_value(min_height_val)
2884 .and_then(|v| resolve_length_value(v, available_height))
2885 {
2886 content_height = content_height.max(min_height);
2887 }
2888 if let Some(max_height) = parse_length_value(max_height_val)
2889 .and_then(|v| resolve_length_value(v, available_height))
2890 {
2891 content_height = content_height.min(max_height);
2892 }
2893
2894 self.dimensions.content.width = content_width.max(1);
2895 self.dimensions.content.height = content_height.max(1);
2896 self.dimensions.content.x = containing_block.content.x
2897 + self.dimensions.margin.left
2898 + self.dimensions.border.left
2899 + self.dimensions.padding.left;
2900 self.dimensions.content.y = containing_block.content.y
2901 + containing_block.content.height
2902 + self.dimensions.margin.top
2903 + self.dimensions.border.top
2904 + self.dimensions.padding.top;
2905
2906 let mut child_flow_x = 0i32;
2909 let mut child_flow_y = 0i32;
2910 let mut line_h = 0i32;
2911 let max_w = self.dimensions.content.width.max(1);
2912 let mut line_start_idx = 0usize;
2916
2917 let mut i = 0usize;
2918 while i < self.children.len() {
2919 match self.children[i].box_type {
2920 BoxType::BlockNode(_)
2921 | BoxType::FlexNode(_)
2922 | BoxType::GridNode(_)
2923 | BoxType::TableNode(_)
2924 | BoxType::TableRowGroupNode(_)
2925 | BoxType::TableRowNode(_)
2926 | BoxType::TableCellNode(_) => {
2927 if child_flow_x > 0 {
2928 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
2929 child_flow_y += line_h;
2930 child_flow_x = 0;
2931 line_h = 0;
2932 }
2933 line_start_idx = i + 1;
2934 let child = &mut self.children[i];
2935 let mut cb = self.dimensions.clone();
2936 cb.content.height = child_flow_y;
2937 child.layout(cb);
2938 child_flow_y += child.dimensions.margin_box().height;
2939 }
2940 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
2941 if self.children[i].is_inline_line_break() {
2942 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
2943 child_flow_y += line_h.max(20);
2944 child_flow_x = 0;
2945 line_h = 0;
2946 line_start_idx = i + 1;
2947 i += 1;
2948 continue;
2949 }
2950 let (child_w, child_h) = self.children[i].intrinsic_inline_size(max_w);
2951 if child_flow_x > 0 && child_flow_x + child_w > max_w {
2952 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
2953 child_flow_y += line_h.max(20);
2954 child_flow_x = 0;
2955 line_h = 0;
2956 line_start_idx = i;
2957 }
2958 let child = &mut self.children[i];
2959 child.dimensions.content.x = self.dimensions.content.x + child_flow_x;
2960 child.dimensions.content.y = self.dimensions.content.y + child_flow_y;
2961 child.dimensions.content.width = child_w;
2962 child.dimensions.content.height = child_h;
2963
2964 let cb = child.dimensions.clone();
2971 child.layout(cb);
2972 child.dimensions.content.x = self.dimensions.content.x + child_flow_x;
2973 child.dimensions.content.y = self.dimensions.content.y + child_flow_y;
2974 child.dimensions.content.width = child_w;
2975 child.dimensions.content.height = child_h;
2976
2977 child_flow_x += child_w;
2978 if child_h > line_h {
2979 line_h = child_h;
2980 }
2981 i += 1;
2982 }
2983 BoxType::AnonymousBlock => {
2984 i += 1;
2985 }
2986 }
2987 }
2988 self.finalize_inline_line_vertical_align(line_start_idx, self.children.len(), line_h, max_w);
2990 }
2991
2992 fn finalize_inline_line_vertical_align(&mut self, start: usize, end: usize, line_h: i32, max_w: i32) {
2998 if line_h <= 0 || end <= start {
2999 return;
3000 }
3001 for idx in start..end {
3002 let Some(child) = self.children.get_mut(idx) else {
3003 continue;
3004 };
3005 if !matches!(
3006 child.box_type,
3007 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_)
3008 ) {
3009 continue;
3010 }
3011 let va = box_style_value(child, "vertical-align").unwrap_or_default();
3012 let offset = match va.trim() {
3013 "middle" => {
3014 let (_, child_h) = child.intrinsic_inline_size(max_w);
3015 (line_h - child_h) / 2
3016 }
3017 "bottom" | "text-bottom" => {
3018 let (_, child_h) = child.intrinsic_inline_size(max_w);
3019 line_h - child_h
3020 }
3021 _ => 0,
3022 };
3023 if offset != 0 {
3024 child.translate_y(offset);
3025 }
3026 }
3027 }
3028
3029 fn resolve_sizing_value(&self, value: Option<String>, base: i32) -> Option<i32> {
3032 match parse_length_value(value)? {
3033 LengthValue::Auto => None,
3034 LengthValue::Px(px) => Some(px),
3035 LengthValue::Percent(pct) => Some(round_f32_to_i32(base as f32 * pct)),
3036 LengthValue::MinContent => Some(self.block_intrinsic_widths().0),
3037 LengthValue::MaxContent => Some(self.block_intrinsic_widths().1),
3038 LengthValue::FitContent => {
3039 let (minc, maxc) = self.block_intrinsic_widths();
3040 Some(maxc.min(base.max(0)).max(minc))
3041 }
3042 LengthValue::MathExpr(expr) => crate::os_lib::css::eval_css_math(&expr, base),
3043 }
3044 }
3045
3046 fn block_intrinsic_widths(&self) -> (i32, i32) {
3050 if matches!(
3052 self.box_type,
3053 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_)
3054 ) {
3055 let max_c = self.intrinsic_inline_size_ext(1_000_000, true).0;
3056 let min_c = self.intrinsic_inline_size_ext(1, true).0;
3057 return (min_c.max(0), max_c.max(min_c).max(0));
3058 }
3059 if let BoxType::FlexNode(node) = &self.box_type {
3070 let dir = node
3071 .value("flex-direction")
3072 .unwrap_or_else(|| alloc::string::String::from("row"))
3073 .trim()
3074 .to_lowercase();
3075 let is_row = !dir.starts_with("column");
3076 let wraps = node
3077 .value("flex-wrap")
3078 .map(|w| w.trim().to_lowercase().starts_with("wrap"))
3079 .unwrap_or(false);
3080 if is_row {
3081 let mut sum_min = 0i32;
3082 let mut sum_max = 0i32;
3083 let mut max_min = 0i32;
3084 for child in &self.children {
3085 let (cmin, cmax) = child.block_intrinsic_widths();
3086 sum_min += cmin;
3087 sum_max += cmax;
3088 max_min = max_min.max(cmin);
3089 }
3090 let (rg, cg) = parse_flex_gap(node, self.dimensions.content.width);
3091 let _ = rg;
3092 let n = self.children.len() as i32;
3093 let gaps = if n > 1 { cg * (n - 1) } else { 0 };
3094 let min_c = if wraps { max_min } else { sum_min + gaps };
3096 return (min_c.max(0), (sum_max + gaps).max(min_c).max(0));
3097 }
3098 }
3099
3100 let mut inline_max = 0i32; let mut overall_max = 0i32;
3104 let mut overall_min = 0i32;
3105 for child in &self.children {
3106 match child.box_type {
3107 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
3108 let cmax = child.intrinsic_inline_size_ext(1_000_000, true).0;
3110 let cmin = child.intrinsic_inline_size_ext(1, true).0;
3111 inline_max += cmax;
3112 overall_min = overall_min.max(cmin);
3113 }
3114 _ => {
3115 overall_max = overall_max.max(inline_max);
3116 inline_max = 0;
3117 let (cmin, cmax) = child.block_intrinsic_widths();
3118 overall_min = overall_min.max(cmin);
3119 overall_max = overall_max.max(cmax);
3120 }
3121 }
3122 }
3123 overall_max = overall_max.max(inline_max);
3124 (overall_min.max(0), overall_max.max(overall_min).max(0))
3125 }
3126
3127 fn inline_content_intrinsic_size(&self, available_width: i32) -> (i32, i32) {
3128 match &self.box_type {
3129 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
3130 if let Some(size) = get_replaced_element_size(node) {
3131 return size;
3132 }
3133 match &node.node.node_type {
3134 NodeType::Text(text) => {
3135 let font_size = node
3136 .value("font-size")
3137 .and_then(|v| parse_font_size_u32(&v))
3138 .unwrap_or(16);
3139 let line_height_val = node.value("line-height");
3140 let line_height = parse_line_height(line_height_val, font_size);
3141 let pre_wrap = matches!(
3142 node.value("white-space").as_deref(),
3143 Some("pre-wrap") | Some("pre-line")
3144 );
3145 let break_all = matches!(
3146 node.value("word-break").as_deref(),
3147 Some("break-all")
3148 ) || matches!(
3149 node.value("overflow-wrap").as_deref(),
3150 Some("break-word")
3151 );
3152 let (w, h) = crate::kernel::vector_font::get_vector_string_wrapped_size_ext(
3153 text,
3154 font_size,
3155 available_width.max(1) as u32,
3156 0, 0, break_all,
3159 pre_wrap,
3160 );
3161 let lines = h as i32 / (font_size as i32 + 4).max(1);
3162 (w as i32, lines * line_height)
3163 }
3164 NodeType::Element { .. } => {
3165 let mut max_line_w = 0i32;
3166 let mut total_h = 0i32;
3167 let mut current_line_w = 0i32;
3168 let mut current_line_h = 0i32;
3169 for child in &self.children {
3170 if child.is_inline_line_break() {
3171 if current_line_w > max_line_w {
3172 max_line_w = current_line_w;
3173 }
3174 total_h += current_line_h.max(20);
3175 current_line_w = 0;
3176 current_line_h = 0;
3177 continue;
3178 }
3179 let (cw, ch) = child.intrinsic_inline_size(available_width.max(1));
3180 if current_line_w > 0 && current_line_w + cw > available_width.max(1) {
3181 if current_line_w > max_line_w {
3182 max_line_w = current_line_w;
3183 }
3184 total_h += current_line_h.max(20);
3185 current_line_w = 0;
3186 current_line_h = 0;
3187 }
3188 current_line_w += cw;
3189 if ch > current_line_h {
3190 current_line_h = ch;
3191 }
3192 }
3193 if current_line_w > max_line_w {
3194 max_line_w = current_line_w;
3195 }
3196 total_h += current_line_h.max(20);
3197 if total_h == 0 {
3198 let font_size = node
3199 .value("font-size")
3200 .and_then(|v| parse_font_size_u32(&v))
3201 .unwrap_or(16);
3202 return (8, (font_size as i32 + 4).max(12));
3203 }
3204 (max_line_w.max(1), total_h.max(1))
3205 }
3206 }
3207 }
3208 _ => (0, 0),
3209 }
3210 }
3211
3212 fn is_inline_line_break(&self) -> bool {
3213 match &self.box_type {
3214 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
3215 matches!(&node.node.node_type, NodeType::Element { tag_name, .. } if tag_name == "br")
3216 }
3217 _ => false,
3218 }
3219 }
3220
3221 fn apply_box_model_styles_with_base(&mut self, containing_width: i32) {
3222 let node = match &self.box_type {
3223 BoxType::BlockNode(n) => n,
3224 BoxType::InlineBlockNode(n) => n,
3225 _ => return,
3226 };
3227
3228 apply_box_value(
3229 node.value("margin"),
3230 &mut self.dimensions.margin,
3231 containing_width,
3232 );
3233 apply_box_value(
3234 node.value("padding"),
3235 &mut self.dimensions.padding,
3236 containing_width,
3237 );
3238
3239 apply_edge_value(
3240 node.value("margin-top"),
3241 &mut self.dimensions.margin.top,
3242 containing_width,
3243 );
3244 apply_edge_value(
3245 node.value("margin-right"),
3246 &mut self.dimensions.margin.right,
3247 containing_width,
3248 );
3249 apply_edge_value(
3250 node.value("margin-bottom"),
3251 &mut self.dimensions.margin.bottom,
3252 containing_width,
3253 );
3254 apply_edge_value(
3255 node.value("margin-left"),
3256 &mut self.dimensions.margin.left,
3257 containing_width,
3258 );
3259
3260 apply_edge_value(
3261 node.value("padding-top"),
3262 &mut self.dimensions.padding.top,
3263 containing_width,
3264 );
3265 apply_edge_value(
3266 node.value("padding-right"),
3267 &mut self.dimensions.padding.right,
3268 containing_width,
3269 );
3270 apply_edge_value(
3271 node.value("padding-bottom"),
3272 &mut self.dimensions.padding.bottom,
3273 containing_width,
3274 );
3275 apply_edge_value(
3276 node.value("padding-left"),
3277 &mut self.dimensions.padding.left,
3278 containing_width,
3279 );
3280
3281 apply_box_value(
3283 node.value("border-width"),
3284 &mut self.dimensions.border,
3285 containing_width,
3286 );
3287 if let Some(border) = node.value("border") {
3288 if let Some(px) = parse_px_like(&border) {
3289 self.dimensions.border.top = px;
3290 self.dimensions.border.right = px;
3291 self.dimensions.border.bottom = px;
3292 self.dimensions.border.left = px;
3293 }
3294 }
3295 apply_edge_value(
3296 node.value("border-top-width"),
3297 &mut self.dimensions.border.top,
3298 containing_width,
3299 );
3300 apply_edge_value(
3301 node.value("border-right-width"),
3302 &mut self.dimensions.border.right,
3303 containing_width,
3304 );
3305 apply_edge_value(
3306 node.value("border-bottom-width"),
3307 &mut self.dimensions.border.bottom,
3308 containing_width,
3309 );
3310 apply_edge_value(
3311 node.value("border-left-width"),
3312 &mut self.dimensions.border.left,
3313 containing_width,
3314 );
3315 }
3316
3317 fn apply_box_model_styles(&mut self) {
3318 self.apply_box_model_styles_with_base(0);
3319 }
3320}
3321