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" | "inline-flex" | "inline-grid" => BoxType::InlineBlockNode(node),
50 "flex" => BoxType::FlexNode(node),
51 "grid" => BoxType::GridNode(node),
52 "table" => BoxType::TableNode(node),
53 "table-row-group" => BoxType::TableRowGroupNode(node),
54 "table-row" => BoxType::TableRowNode(node),
55 "table-cell" => BoxType::TableCellNode(node),
56 _ => BoxType::InlineNode(node), }
58 }
59 NodeType::Text(_) => BoxType::InlineNode(node),
60 };
61
62 let mut layout_box = LayoutBox {
63 dimensions: Dimensions::default(),
64 box_type,
65 children: Vec::new(),
66 };
67
68 for child in &node.children {
69 if resolved_display(child) != "none" {
70 layout_box.children.push(build_layout_tree(child));
72 }
73 }
74
75 if matches!(layout_box.box_type, BoxType::TableNode(_)) {
78 let mut flattened: Vec<LayoutBox<'a>> = Vec::new();
79 for child in layout_box.children.drain(..) {
80 if matches!(child.box_type, BoxType::TableRowGroupNode(_)) {
81 for row in child.children {
82 flattened.push(row);
83 }
84 } else {
85 flattened.push(child);
86 }
87 }
88 layout_box.children = flattened;
89 }
90
91 layout_box
92}
93
94pub(crate) fn style_box_edges(
98 node: &StyledNode,
99 containing_width: i32,
100 init: &Dimensions,
101) -> (EdgeSizes, EdgeSizes, EdgeSizes) {
102 let mut margin = init.margin.clone();
103 let mut padding = init.padding.clone();
104 let mut border = init.border.clone();
105 apply_box_value(
106 node.value("margin"),
107 &mut margin,
108 containing_width,
109 );
110 apply_box_value(
111 node.value("padding"),
112 &mut padding,
113 containing_width,
114 );
115
116 apply_edge_value(
117 node.value("margin-top"),
118 &mut margin.top,
119 containing_width,
120 );
121 apply_edge_value(
122 node.value("margin-right"),
123 &mut margin.right,
124 containing_width,
125 );
126 apply_edge_value(
127 node.value("margin-bottom"),
128 &mut margin.bottom,
129 containing_width,
130 );
131 apply_edge_value(
132 node.value("margin-left"),
133 &mut margin.left,
134 containing_width,
135 );
136
137 apply_edge_value(
138 node.value("padding-top"),
139 &mut padding.top,
140 containing_width,
141 );
142 apply_edge_value(
143 node.value("padding-right"),
144 &mut padding.right,
145 containing_width,
146 );
147 apply_edge_value(
148 node.value("padding-bottom"),
149 &mut padding.bottom,
150 containing_width,
151 );
152 apply_edge_value(
153 node.value("padding-left"),
154 &mut padding.left,
155 containing_width,
156 );
157
158 apply_box_value(
160 node.value("border-width"),
161 &mut border,
162 containing_width,
163 );
164 if let Some(border_val) = node.value("border") {
165 if let Some(px) = parse_px_like(&border_val) {
166 border.top = px;
167 border.right = px;
168 border.bottom = px;
169 border.left = px;
170 }
171 }
172 apply_edge_value(
173 node.value("border-top-width"),
174 &mut border.top,
175 containing_width,
176 );
177 apply_edge_value(
178 node.value("border-right-width"),
179 &mut border.right,
180 containing_width,
181 );
182 apply_edge_value(
183 node.value("border-bottom-width"),
184 &mut border.bottom,
185 containing_width,
186 );
187 apply_edge_value(
188 node.value("border-left-width"),
189 &mut border.left,
190 containing_width,
191 );
192 (margin, padding, border)
193}
194
195pub(crate) fn clamp_contribution(b: &LayoutBox, cmin: i32, cmax: i32) -> (i32, i32) {
200 let px = |prop: &str| match parse_length_value(box_style_value(b, prop)) {
201 Some(LengthValue::Px(v)) => Some(v.max(0)),
202 _ => None,
203 };
204 let (mut lo, mut hi) = (cmin, cmax);
205 if let Some(mx) = px("max-width") {
206 lo = lo.min(mx);
207 hi = hi.min(mx);
208 }
209 if let Some(mn) = px("min-width") {
210 lo = lo.max(mn);
211 hi = hi.max(mn);
212 }
213 (lo, hi.max(lo))
214}
215
216pub(crate) fn intrinsic_h_edges(b: &LayoutBox, explicit_width: bool) -> i32 {
221 let node = match &b.box_type {
222 BoxType::BlockNode(n)
223 | BoxType::InlineBlockNode(n)
224 | BoxType::FlexNode(n)
225 | BoxType::GridNode(n)
226 | BoxType::TableNode(n) => *n,
227 _ => return 0,
228 };
229 let (m, p, bd) = style_box_edges(node, 0, &Dimensions::default());
231 let margins = m.left.max(0) + m.right.max(0);
232 let border_box = explicit_width
233 && node.value("box-sizing").map(|v| v.trim() == "border-box").unwrap_or(false);
234 if border_box {
235 margins
236 } else {
237 margins + p.left + p.right + bd.left + bd.right
238 }
239}
240
241fn text_font_of(node: &StyledNode) -> (String, bool) {
244 let fam = node.value("font-family").map(|v| String::from(v.trim())).unwrap_or_default();
245 let bold = node
246 .value("font-weight")
247 .map(|v| {
248 let t = v.trim().to_lowercase();
249 t == "bold" || t == "bolder" || t.parse::<u32>().unwrap_or(400) >= 700
250 })
251 .unwrap_or(false);
252 (fam, bold)
253}
254
255impl<'a> LayoutBox<'a> {
256 fn explicit_content_height_px(&self, height_val: Option<String>) -> Option<i32> {
261 let Some(LengthValue::Px(px)) = parse_length_value(height_val) else {
262 return None;
263 };
264 let border_box = box_style_value(self, "box-sizing")
265 .map(|v| v.trim() == "border-box")
266 .unwrap_or(false);
267 if border_box {
268 let d = &self.dimensions;
269 Some((px - d.padding.top - d.padding.bottom - d.border.top - d.border.bottom).max(0))
270 } else {
271 Some(px.max(0))
272 }
273 }
274
275 fn h_non_content(&self) -> i32 {
277 let d = &self.dimensions;
278 d.margin.left + d.margin.right + d.border.left + d.border.right + d.padding.left + d.padding.right
279 }
280
281 fn inline_block_edges(&self) -> (i32, i32, i32, i32) {
285 if !matches!(self.box_type, BoxType::InlineBlockNode(_)) {
286 return (0, 0, 0, 0);
287 }
288 let d = &self.dimensions;
289 (
290 d.margin.left + d.border.left + d.padding.left,
291 d.margin.right + d.border.right + d.padding.right,
292 d.margin.top + d.border.top + d.padding.top,
293 d.margin.bottom + d.border.bottom + d.padding.bottom,
294 )
295 }
296
297 fn is_inline_flex(&self) -> bool {
299 match &self.box_type {
300 BoxType::InlineBlockNode(n) => resolved_display(n).trim() == "inline-flex",
301 _ => false,
302 }
303 }
304
305 fn is_inline_grid(&self) -> bool {
307 match &self.box_type {
308 BoxType::InlineBlockNode(n) => resolved_display(n).trim() == "inline-grid",
309 _ => false,
310 }
311 }
312
313 fn inline_flex_content_width(&self, avail: i32) -> i32 {
316 if let BoxType::InlineBlockNode(node) = &self.box_type {
317 if let Some(w) = parse_length_value(node.value("width"))
318 .and_then(|v| resolve_length_value(v, avail.max(0)))
319 {
320 return w.max(0);
321 }
322 }
323 let (minc, maxc) = self.block_intrinsic_widths();
324 let d = &self.dimensions;
325 let non_content = d.margin.left + d.margin.right + d.border.left + d.border.right
326 + d.padding.left + d.padding.right;
327 maxc.min((avail - non_content).max(0)).max(minc).max(0)
328 }
329
330 fn layout_as_flex_at(&mut self, x: i32, y: i32, content_w: i32) {
333 let BoxType::InlineBlockNode(node) = self.box_type else {
334 return;
335 };
336 let as_grid = self.is_inline_grid();
337 self.box_type = if as_grid { BoxType::GridNode(node) } else { BoxType::FlexNode(node) };
338 let d = &self.dimensions;
339 let left = d.margin.left + d.border.left + d.padding.left;
340 let top = d.margin.top + d.border.top + d.padding.top;
341 let h_non = left + d.padding.right + d.border.right + d.margin.right;
342 let cb = Dimensions {
343 content: Rect { x: x - left, y: y - top, width: content_w + h_non, height: 0 },
344 ..Default::default()
345 };
346 if as_grid {
347 self.layout_grid(cb);
348 } else {
349 self.layout_flex(cb);
350 }
351 self.box_type = BoxType::InlineBlockNode(node);
352 }
353
354 fn layout_inline_flex(&mut self, containing_block: Dimensions) {
358 self.apply_box_model_styles_with_base(containing_block.content.width);
359 let w = self.inline_flex_content_width(containing_block.content.width.max(1));
360 let d = &self.dimensions;
361 let x = containing_block.content.x + d.margin.left + d.border.left + d.padding.left;
362 let y = containing_block.content.y
363 + containing_block.content.height
364 + d.margin.top
365 + d.border.top
366 + d.padding.top;
367 self.layout_as_flex_at(x, y, w);
368 }
369
370 fn translate_y(&mut self, dy: i32) {
374 if dy == 0 {
375 return;
376 }
377 self.dimensions.content.y += dy;
378 for child in &mut self.children {
379 child.translate_y(dy);
380 }
381 }
382
383 fn translate(&mut self, dx: i32, dy: i32) {
389 if dx == 0 && dy == 0 {
390 return;
391 }
392 self.dimensions.content.x += dx;
393 self.dimensions.content.y += dy;
394 for child in &mut self.children {
395 child.translate(dx, dy);
396 }
397 }
398
399 fn layout_captions_at(&mut self, indices: &[usize], base_x: i32, y: i32, width: i32) -> i32 {
402 let mut consumed = 0i32;
403 for &ci in indices {
404 let cap_y = y + consumed;
405 let cap = &mut self.children[ci];
406 let mut cb = Dimensions::default();
407 cb.content.x = base_x;
408 cb.content.y = cap_y;
409 cb.content.height = 0;
410 cb.content.width = width;
411 cap.layout(cb);
412 consumed += cap.dimensions.margin_box().height;
413 }
414 consumed
415 }
416
417 fn layout(&mut self, containing_block: Dimensions) {
418 match &self.box_type {
419 BoxType::BlockNode(_) => self.layout_block(containing_block),
420 BoxType::InlineNode(_) => self.layout_inline(containing_block, false),
421 BoxType::InlineBlockNode(_) if self.is_inline_flex() || self.is_inline_grid() => {
422 self.layout_inline_flex(containing_block)
423 }
424 BoxType::InlineBlockNode(_) => self.layout_inline(containing_block, true),
425 BoxType::FlexNode(_) => self.layout_flex(containing_block),
426 BoxType::GridNode(_) => self.layout_grid(containing_block),
427 BoxType::TableNode(_) => self.layout_table(containing_block),
428 BoxType::TableRowGroupNode(_)
431 | BoxType::TableRowNode(_)
432 | BoxType::TableCellNode(_) => self.layout_block(containing_block),
433 BoxType::AnonymousBlock => {}
434 }
435 }
436
437 fn establish_abs_cb(&self) -> Option<(i32, i32, i32, i32)> {
442 if matches!(
443 box_position(self).as_str(),
444 "relative" | "absolute" | "fixed"
445 ) {
446 let prev = abs_cb_get();
447 abs_cb_set(
448 self.dimensions.content.x,
449 self.dimensions.content.y,
450 self.dimensions.content.width,
451 self.dimensions.content.height,
452 );
453 Some(prev)
454 } else {
455 None
456 }
457 }
458
459 fn restore_abs_cb(saved: Option<(i32, i32, i32, i32)>) {
461 if let Some(prev) = saved {
462 abs_cb_set(prev.0, prev.1, prev.2, prev.3);
463 }
464 }
465
466 fn child_is_out_of_flow(child: &LayoutBox) -> bool {
468 matches!(box_position(child).as_str(), "absolute" | "fixed")
469 }
470
471 fn apply_positioned_offsets(&mut self) {
479 let cw = self.dimensions.content.width;
480 let ch = self.dimensions.content.height;
481 for child in &mut self.children {
482 match box_position(child).as_str() {
483 "relative" => {
484 let dx = box_inset_len(child, "left", cw)
485 .or_else(|| box_inset_len(child, "right", cw).map(|r| -r))
486 .unwrap_or(0);
487 let dy = box_inset_len(child, "top", ch)
488 .or_else(|| box_inset_len(child, "bottom", ch).map(|b| -b))
489 .unwrap_or(0);
490 if dx != 0 || dy != 0 {
491 shift_layout_box(child, dx, dy);
492 }
493 }
494 pos @ ("absolute" | "fixed") => {
495 let (bx, by, bw, bh) = if pos == "fixed" {
497 let (vx, vy) = vp_origin_get();
498 (
499 vx,
500 vy,
501 VIEWPORT_HINT_W.load(AtomicOrdering::Relaxed),
502 VIEWPORT_HINT_H.load(AtomicOrdering::Relaxed),
503 )
504 } else {
505 abs_cb_get()
506 };
507 let block_like = matches!(
512 child.box_type,
513 BoxType::BlockNode(_) | BoxType::FlexNode(_) | BoxType::GridNode(_)
514 );
515 if block_like && !box_has_explicit(child, "width") {
516 let h_non = child.h_non_content();
517 let l = box_inset_len(child, "left", bw);
518 let r = box_inset_len(child, "right", bw);
519 let target = match (l, r) {
520 (Some(l), Some(r)) => (bw - l - r - h_non).max(0),
521 _ => {
522 let (minc, maxc) = child.block_intrinsic_widths();
523 let avail = (bw - l.or(r).unwrap_or(0) - h_non).max(0);
524 maxc.min(avail).max(minc).max(0)
525 }
526 };
527 if target != child.dimensions.content.width {
528 let mb0 = child.dimensions.margin_box();
529 let cb = Dimensions {
530 content: Rect { x: mb0.x, y: mb0.y, width: target + h_non, height: 0 },
531 ..Default::default()
532 };
533 child.layout(cb);
534 }
535 }
536 let mb = child.dimensions.margin_box();
537 let tx = if let Some(l) = box_inset_len(child, "left", bw) {
538 bx + l
539 } else if let Some(r) = box_inset_len(child, "right", bw) {
540 bx + bw - r - mb.width
541 } else {
542 bx
543 };
544 let ty = if let Some(t) = box_inset_len(child, "top", bh) {
545 by + t
546 } else if let Some(b) = box_inset_len(child, "bottom", bh) {
547 by + bh - b - mb.height
548 } else {
549 by
550 };
551 shift_layout_box(child, tx - mb.x, ty - mb.y);
552 }
553 _ => {}
554 }
555
556 let (txx, tyy) = box_translate(child);
559 if txx != 0 || tyy != 0 {
560 shift_layout_box(child, txx, tyy);
561 }
562 }
563 }
564
565 fn layout_grid(&mut self, containing_block: Dimensions) {
572 self.apply_box_model_styles_with_base(containing_block.content.width);
573
574 let node = match &self.box_type {
575 BoxType::GridNode(n) => *n,
576 _ => return,
577 };
578 let avail_w = containing_block.content.width.max(1);
579
580 let inner_non_content = self.dimensions.border.left
582 + self.dimensions.border.right
583 + self.dimensions.padding.left
584 + self.dimensions.padding.right;
585 let h_non_content =
586 self.dimensions.margin.left + self.dimensions.margin.right + inner_non_content;
587 let width_val = node.value("width");
588 let box_sizing = node.value("box-sizing").unwrap_or_default();
589 let h_auto = margin_is_h_auto(node);
590 let explicit_w =
591 !matches!(parse_length_value(width_val.clone()), Some(LengthValue::Auto) | None);
592 let mut content_w = match parse_length_value(width_val) {
593 Some(LengthValue::Auto) | None => (avail_w - h_non_content).max(1),
594 Some(LengthValue::Px(px)) => px.max(1),
595 Some(LengthValue::Percent(pct)) => round_f32_to_i32(avail_w as f32 * pct).max(1),
596 Some(LengthValue::MinContent) => {
597 let (minc, _maxc) = self.block_intrinsic_widths();
598 minc.max(1)
599 }
600 Some(LengthValue::MaxContent) => {
601 let (_minc, maxc) = self.block_intrinsic_widths();
602 maxc.max(1)
603 }
604 Some(LengthValue::FitContent) => {
605 let (minc, maxc) = self.block_intrinsic_widths();
606 let avail = (avail_w - h_non_content).max(0);
607 avail.min(maxc).max(minc).max(1)
608 }
609 Some(LengthValue::MathExpr(expr)) => {
610 crate::os_lib::css::eval_css_math(&expr, avail_w)
611 .unwrap_or((avail_w - h_non_content).max(1))
612 .max(1)
613 }
614 };
615 if explicit_w && box_sizing.trim() == "border-box" {
619 content_w = (content_w - inner_non_content).max(1);
620 }
621 if let Some(min_w) = self.resolve_sizing_value(node.value("min-width"), avail_w) {
622 content_w = content_w.max(min_w);
623 }
624 if let Some(max_w) = self.resolve_sizing_value(node.value("max-width"), avail_w) {
625 content_w = content_w.min(max_w);
626 }
627 let content_w = content_w.max(1);
628 self.dimensions.content.width = content_w;
629 if h_auto {
630 let free = (avail_w - content_w - inner_non_content).max(0);
631 self.dimensions.margin.left = free / 2;
632 self.dimensions.margin.right = free / 2;
633 }
634
635 let flow_y = containing_block.content.y
636 + containing_block.content.height
637 + self.dimensions.margin.top;
638 self.dimensions.content.x = containing_block.content.x
639 + self.dimensions.margin.left
640 + self.dimensions.border.left
641 + self.dimensions.padding.left;
642 self.dimensions.content.y =
643 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
644
645 let (row_gap, col_gap) = {
646 let gpx = |s: &str| -> i32 {
648 match parse_length_value(Some(String::from(s.trim()))) {
649 Some(LengthValue::Px(p)) => p,
650 Some(LengthValue::Percent(pct)) => {
651 round_f32_to_i32(content_w as f32 * pct).max(0)
652 }
653 _ => 0,
654 }
655 };
656
657 let gap_all = node.value("gap").unwrap_or_default();
658 if !gap_all.is_empty() {
659 let toks: Vec<&str> = gap_all.split_whitespace().collect();
660 let rg = gpx(toks.first().copied().unwrap_or("0"));
661 let cg = gpx(toks
662 .get(1)
663 .copied()
664 .unwrap_or(toks.first().copied().unwrap_or("0")));
665 (rg, cg)
666 } else {
667 let rg = node
668 .value("row-gap")
669 .or_else(|| node.value("grid-row-gap"))
670 .map(|v| gpx(&v))
671 .unwrap_or(0);
672 let cg = node
673 .value("column-gap")
674 .or_else(|| node.value("grid-column-gap"))
675 .map(|v| gpx(&v))
676 .unwrap_or(0);
677 (rg, cg)
678 }
679 };
680
681 let mut col_widths = parse_grid_template_columns_gap(
684 node.value("grid-template-columns").as_deref().unwrap_or(""),
685 content_w,
686 col_gap,
687 );
688 let n_cols = col_widths.len().max(1);
689
690 let auto_col_tracks = parse_grid_tracks(
696 node.value("grid-auto-columns").as_deref().unwrap_or(""),
697 content_w,
698 );
699 let auto_col_fallback_px = col_widths.last().copied().unwrap_or(100).max(1);
700 let resolve_auto_col_px = |idx: usize| -> i32 {
701 if auto_col_tracks.is_empty() {
702 return auto_col_fallback_px;
703 }
704 match auto_col_tracks[idx % auto_col_tracks.len()] {
705 GridTrack::Px(px) => px.max(1),
706 GridTrack::MinMax { min_px, .. } => min_px.max(1),
707 _ => auto_col_fallback_px,
708 }
709 };
710
711 let template_areas =
713 parse_grid_template_areas(node.value("grid-template-areas").as_deref().unwrap_or(""));
714
715 let row_base = match parse_length_value(node.value("height")) {
722 Some(LengthValue::Px(px)) => px.max(0),
723 Some(LengthValue::Percent(pct)) => {
724 round_f32_to_i32(containing_block.content.height as f32 * pct).max(0)
725 }
726 _ => 0,
727 };
728
729 let row_tracks = parse_grid_tracks(
731 node.value("grid-template-rows").as_deref().unwrap_or(""),
732 row_base,
733 );
734
735 let auto_row_tracks = parse_grid_tracks(
737 node.value("grid-auto-rows").as_deref().unwrap_or(""),
738 row_base,
739 );
740
741 let eff_row_track = |r: usize| -> Option<GridTrack> {
743 if let Some(t) = row_tracks.get(r) {
744 return Some(*t);
745 }
746 if auto_row_tracks.is_empty() {
747 return None;
748 }
749 let implicit_idx = r - row_tracks.len();
750 auto_row_tracks
751 .get(implicit_idx % auto_row_tracks.len())
752 .copied()
753 };
754
755 let justify_items = node
758 .value("justify-items")
759 .unwrap_or_else(|| String::from("stretch"))
760 .trim()
761 .to_lowercase();
762 let align_items = node
763 .value("align-items")
764 .unwrap_or_else(|| String::from("stretch"))
765 .trim()
766 .to_lowercase();
767
768 let auto_flow_col = node
772 .value("grid-auto-flow")
773 .map(|v| v.to_lowercase().contains("column"))
774 .unwrap_or(false);
775 let flow_rows = row_tracks.len().max(1);
777 let mut occ: Vec<Vec<bool>> = Vec::new();
778 let mut n_cols_dyn = n_cols;
784 let n_children = self.children.len();
786 let mut placement: Vec<Option<(usize, usize, usize, usize)>> =
787 alloc::vec![None; n_children];
788 let mut cursor_r = 0usize;
789 let mut cursor_c = 0usize;
790 for (idx, child) in self.children.iter().enumerate() {
791 if Self::child_is_out_of_flow(child) {
792 continue;
793 }
794 let (mut col_start, mut col_span_raw) =
795 grid_axis_placement(child, "grid-column", "grid-column-start", "grid-column-end");
796 let (mut row_start, mut row_span_raw) =
797 grid_axis_placement(child, "grid-row", "grid-row-start", "grid-row-end");
798 if col_start.is_none() && row_start.is_none() {
801 if let Some(area_name) = box_style_value(child, "grid-area") {
802 let area_name = area_name.trim();
803 if !area_name.is_empty() {
804 if let Some((ar, ac, arspan, acspan)) =
805 find_grid_area(&template_areas, area_name)
806 {
807 row_start = Some(ar);
808 col_start = Some(ac);
809 row_span_raw = arspan;
810 col_span_raw = acspan;
811 }
812 }
813 }
814 }
815 let col_span = col_span_raw.min(n_cols).max(1);
816 let row_span = row_span_raw.max(1);
817
818 let (r, c) = match (row_start, col_start) {
820 (Some(r), Some(c)) => (r, c.min(n_cols.saturating_sub(1))),
821 (Some(r), None) => {
822 grid_ensure_row(&mut occ, r + row_span - 1, n_cols);
824 let mut cc = 0usize;
825 while cc < n_cols && !grid_cells_free(&occ, r, cc, row_span, col_span, n_cols) {
826 cc += 1;
827 }
828 (r, cc.min(n_cols.saturating_sub(1)))
829 }
830 (None, Some(c)) => {
831 let c = c.min(n_cols.saturating_sub(1));
833 let mut rr = cursor_r;
834 loop {
835 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols);
836 if grid_cells_free(&occ, rr, c, row_span, col_span, n_cols) {
837 break;
838 }
839 rr += 1;
840 }
841 (rr, c)
842 }
843 (None, None) => {
844 if auto_flow_col {
845 let mut rr = cursor_r;
852 let mut cc = cursor_c;
853 loop {
854 if rr + row_span > flow_rows {
855 cc += 1;
856 rr = 0;
857 }
858 if cc + col_span > n_cols_dyn {
859 n_cols_dyn = cc + col_span;
860 grid_ensure_col(&mut occ, n_cols_dyn);
861 }
862 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols_dyn);
863 if grid_cells_free(&occ, rr, cc, row_span, col_span, n_cols_dyn) {
864 break;
865 }
866 rr += 1;
867 }
868 cursor_r = rr + row_span;
869 cursor_c = cc;
870 (rr, cc)
871 } else {
872 let mut rr = cursor_r;
874 let mut cc = cursor_c;
875 loop {
876 if cc + col_span > n_cols {
877 rr += 1;
878 cc = 0;
879 }
880 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols);
881 if grid_cells_free(&occ, rr, cc, row_span, col_span, n_cols) {
882 break;
883 }
884 cc += 1;
885 }
886 cursor_r = rr;
887 cursor_c = cc + col_span;
888 (rr, cc)
889 }
890 }
891 };
892 grid_ensure_row(&mut occ, r + row_span - 1, n_cols_dyn);
893 grid_mark(&mut occ, r, c, row_span, col_span, n_cols_dyn);
894 placement[idx] = Some((r, c, row_span, col_span));
895 }
896
897 while col_widths.len() < n_cols_dyn {
900 let idx = col_widths.len() - n_cols;
901 col_widths.push(resolve_auto_col_px(idx));
902 }
903
904 let n_cols_eff = col_widths.len();
909 let cols_total: i32 =
910 col_widths.iter().sum::<i32>() + (n_cols_eff.saturating_sub(1) as i32) * col_gap;
911 let cols_free = (content_w - cols_total).max(0);
912 let justify_content_grid = node
913 .value("justify-content")
914 .unwrap_or_default()
915 .trim()
916 .to_lowercase();
917 let (col_start_offset, col_extra_gap) = if cols_free > 0 && n_cols_eff > 0 {
918 match justify_content_grid.as_str() {
919 "end" | "flex-end" => (cols_free, 0),
920 "center" => (cols_free / 2, 0),
921 "space-between" if n_cols_eff > 1 => (0, cols_free / (n_cols_eff as i32 - 1)),
922 "space-around" if n_cols_eff > 0 => {
923 let g = cols_free / n_cols_eff as i32;
924 (g / 2, g)
925 }
926 "space-evenly" if n_cols_eff > 0 => {
927 let g = cols_free / (n_cols_eff as i32 + 1);
928 (g, g)
929 }
930 _ => (0, 0),
931 }
932 } else {
933 (0, 0)
934 };
935 let col_gap_eff = col_gap + col_extra_gap;
936
937 let n_rows = occ.len().max(row_tracks.len());
938
939 let mut row_heights: Vec<i32> = alloc::vec![0; n_rows];
942 for (r, h) in row_heights.iter_mut().enumerate() {
944 match eff_row_track(r) {
945 Some(GridTrack::Px(px)) => *h = px.max(0),
946 Some(GridTrack::MinMax { min_px, .. }) => *h = min_px.max(0),
947 _ => {}
948 }
949 }
950 let base_x = self.dimensions.content.x;
953 let base_y = self.dimensions.content.y;
954 for (idx, child) in self.children.iter_mut().enumerate() {
955 if Self::child_is_out_of_flow(child) {
956 continue;
957 }
958 let Some((_r, c, _rs, cs)) = placement[idx] else {
959 continue;
960 };
961 let cell_w = grid_span_width(&col_widths, col_gap_eff, c, cs);
962 let cell_cb = Dimensions {
963 content: Rect {
964 x: base_x + col_start_offset + grid_col_x(&col_widths, col_gap_eff, c),
965 y: base_y,
966 width: cell_w,
967 height: 0,
968 },
969 ..Dimensions::default()
970 };
971 child.layout(cell_cb);
972 }
973 for (idx, child) in self.children.iter().enumerate() {
975 let Some((r, _c, rs, _cs)) = placement[idx] else {
976 continue;
977 };
978 if rs != 1 {
979 continue;
980 }
981 let explicit_px = matches!(eff_row_track(r), Some(GridTrack::Px(_)));
982 if explicit_px {
983 continue;
984 }
985 let ch = child.dimensions.margin_box().height;
986 if let Some(h) = row_heights.get_mut(r) {
987 *h = (*h).max(ch);
988 }
989 if let Some(GridTrack::MinMax {
991 max_px,
992 max_is_fr,
993 max_is_auto,
994 ..
995 }) = eff_row_track(r)
996 {
997 if !max_is_fr && !max_is_auto {
998 if let Some(h) = row_heights.get_mut(r) {
999 *h = (*h).min(max_px);
1000 }
1001 }
1002 }
1003 }
1004 for (idx, child) in self.children.iter().enumerate() {
1006 let Some((r, _c, rs, _cs)) = placement[idx] else {
1007 continue;
1008 };
1009 if rs <= 1 {
1010 continue;
1011 }
1012 let ch = child.dimensions.margin_box().height;
1013 let mut covered = 0i32;
1014 for rr in r..(r + rs).min(row_heights.len()) {
1015 covered += row_heights.get(rr).copied().unwrap_or(0);
1016 }
1017 covered += (rs.saturating_sub(1) as i32) * row_gap;
1018 if ch > covered {
1019 let last = (r + rs - 1).min(row_heights.len().saturating_sub(1));
1020 if let Some(h) = row_heights.get_mut(last) {
1021 *h += ch - covered;
1022 }
1023 }
1024 }
1025
1026 let mut container_h_explicit: Option<i32> = None;
1028 let height_val = node.value("height");
1029 if let Some(h) = parse_length_value(height_val.clone())
1030 .and_then(|v| resolve_length_value(v, containing_block.content.height.max(0)))
1031 {
1032 let vert_non = self.dimensions.border.top
1033 + self.dimensions.border.bottom
1034 + self.dimensions.padding.top
1035 + self.dimensions.padding.bottom;
1036 container_h_explicit = Some(if box_sizing.trim() == "border-box" {
1037 (h - vert_non).max(0)
1038 } else {
1039 h.max(0)
1040 });
1041 }
1042 if let Some(ch) = container_h_explicit {
1043 let track_fr = |t: &GridTrack| -> f32 {
1045 match t {
1046 GridTrack::Fr(f) => *f,
1047 GridTrack::MinMax {
1048 max_fr, max_is_fr, ..
1049 } if *max_is_fr => *max_fr,
1050 _ => 0.0,
1051 }
1052 };
1053 let mut fr_total = 0.0f32;
1055 for r in 0..n_rows {
1056 if let Some(t) = eff_row_track(r) {
1057 fr_total += track_fr(&t);
1058 }
1059 }
1060 if fr_total > 0.0 {
1061 let mut used = 0i32;
1062 for (r, h) in row_heights.iter().enumerate() {
1063 let is_fr = eff_row_track(r).map(|t| track_fr(&t)).unwrap_or(0.0) > 0.0;
1064 if !is_fr {
1065 used += *h;
1066 }
1067 }
1068 used += (n_rows.saturating_sub(1) as i32) * row_gap;
1069 let free = (ch - used).max(0);
1070 for r in 0..n_rows {
1071 let Some(t) = eff_row_track(r) else { continue };
1072 let f = track_fr(&t);
1073 if f > 0.0 {
1074 let share = round_f32_to_i32(free as f32 * f / fr_total);
1075 let floor = match t {
1077 GridTrack::MinMax { min_px, .. } => min_px,
1078 _ => 0,
1079 };
1080 if let Some(rh) = row_heights.get_mut(r) {
1081 *rh = share.max(floor);
1082 }
1083 }
1084 }
1085 }
1086 }
1087
1088 let rows_total_h: i32 =
1093 row_heights.iter().sum::<i32>() + (n_rows.saturating_sub(1) as i32) * row_gap;
1094 let rows_free = container_h_explicit
1095 .map(|ch| (ch - rows_total_h).max(0))
1096 .unwrap_or(0);
1097 let align_content_grid = node
1098 .value("align-content")
1099 .unwrap_or_default()
1100 .trim()
1101 .to_lowercase();
1102 let (row_start_offset, row_extra_gap) = if rows_free > 0 && n_rows > 0 {
1103 match align_content_grid.as_str() {
1104 "end" | "flex-end" => (rows_free, 0),
1105 "center" => (rows_free / 2, 0),
1106 "space-between" if n_rows > 1 => (0, rows_free / (n_rows as i32 - 1)),
1107 "space-around" if n_rows > 0 => {
1108 let g = rows_free / n_rows as i32;
1109 (g / 2, g)
1110 }
1111 "space-evenly" if n_rows > 0 => {
1112 let g = rows_free / (n_rows as i32 + 1);
1113 (g, g)
1114 }
1115 _ => (0, 0),
1116 }
1117 } else {
1118 (0, 0)
1119 };
1120 let row_gap_eff = row_gap + row_extra_gap;
1121
1122 let mut row_tops: Vec<i32> = alloc::vec![0; n_rows + 1];
1124 for r in 0..n_rows {
1125 let h = row_heights.get(r).copied().unwrap_or(0);
1126 row_tops[r + 1] = row_tops[r] + h + if r + 1 < n_rows { row_gap_eff } else { 0 };
1127 }
1128
1129 for (idx, child) in self.children.iter_mut().enumerate() {
1131 if Self::child_is_out_of_flow(child) {
1132 let oof_cb = Dimensions {
1135 content: Rect {
1136 x: base_x,
1137 y: base_y,
1138 width: content_w,
1139 height: 0,
1140 },
1141 ..Dimensions::default()
1142 };
1143 child.layout(oof_cb);
1144 continue;
1145 }
1146 let Some((r, c, rs, cs)) = placement[idx] else {
1147 continue;
1148 };
1149 let cell_w = grid_span_width(&col_widths, col_gap_eff, c, cs);
1150 let cell_x = base_x + col_start_offset + grid_col_x(&col_widths, col_gap_eff, c);
1151 let cell_y = base_y + row_start_offset + row_tops.get(r).copied().unwrap_or(0);
1152 let mut cell_h = 0i32;
1154 for rr in r..(r + rs).min(row_heights.len()) {
1155 cell_h += row_heights.get(rr).copied().unwrap_or(0);
1156 }
1157 cell_h += (rs.saturating_sub(1) as i32) * row_gap;
1158
1159 let j = grid_item_align(child, "justify-self", &justify_items);
1161 let a = grid_item_align(child, "align-self", &align_items);
1162
1163 let cell_cb = Dimensions {
1165 content: Rect {
1166 x: cell_x,
1167 y: cell_y,
1168 width: cell_w,
1169 height: 0,
1170 },
1171 ..Dimensions::default()
1172 };
1173 child.layout(cell_cb);
1174
1175 if a == "stretch" && !box_has_explicit(child, "height") && cell_h > 0 {
1177 let mb = child.dimensions.margin_box();
1178 let non_content = mb.height - child.dimensions.content.height;
1179 child.dimensions.content.height = (cell_h - non_content).max(0);
1180 }
1181
1182 let mb = child.dimensions.margin_box();
1186 let dx = if j == "stretch" || !box_has_explicit(child, "width") {
1187 0
1188 } else {
1189 let free = (cell_w - mb.width).max(0);
1190 match j {
1191 "center" => free / 2,
1192 "end" => free,
1193 _ => 0, }
1195 };
1196 let dy = if a == "stretch" || !box_has_explicit(child, "height") {
1198 0
1199 } else {
1200 let free = (cell_h - mb.height).max(0);
1201 match a {
1202 "center" => free / 2,
1203 "end" => free,
1204 _ => 0, }
1206 };
1207 if dx != 0 || dy != 0 {
1208 shift_layout_box(child, dx, dy);
1209 }
1210 }
1211
1212 let natural_h = row_tops.get(n_rows).copied().unwrap_or(0).max(0);
1214 self.dimensions.content.height = container_h_explicit.unwrap_or(natural_h);
1215
1216 let saved_abs_cb = self.establish_abs_cb();
1218 self.apply_positioned_offsets();
1219 Self::restore_abs_cb(saved_abs_cb);
1220 }
1221
1222 fn layout_table(&mut self, containing_block: Dimensions) {
1223 self.apply_box_model_styles_with_base(containing_block.content.width);
1224
1225 let h_non_content = self.dimensions.margin.left
1226 + self.dimensions.margin.right
1227 + self.dimensions.border.left
1228 + self.dimensions.border.right
1229 + self.dimensions.padding.left
1230 + self.dimensions.padding.right;
1231 let avail = (containing_block.content.width - h_non_content).max(1);
1232
1233 let table_width = match &self.box_type {
1235 BoxType::TableNode(node) => match parse_length_value(node.value("width")) {
1236 Some(LengthValue::Px(px)) => px.max(1).min(avail),
1237 Some(LengthValue::Percent(pct)) => round_f32_to_i32(avail as f32 * pct).max(1),
1238 _ => avail,
1239 },
1240 _ => avail,
1241 };
1242
1243 let flow_y = containing_block.content.y
1245 + containing_block.content.height
1246 + self.dimensions.margin.top;
1247 self.dimensions.content.x = containing_block.content.x
1248 + self.dimensions.margin.left
1249 + self.dimensions.border.left
1250 + self.dimensions.padding.left;
1251 self.dimensions.content.y =
1252 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
1253 self.dimensions.content.width = table_width;
1254
1255 let base_x = self.dimensions.content.x;
1256
1257 let collapse = match &self.box_type {
1259 BoxType::TableNode(node) => node
1260 .value("border-collapse")
1261 .map(|v| v.trim().eq_ignore_ascii_case("collapse"))
1262 .unwrap_or(false),
1263 _ => false,
1264 };
1265 let collapse_px: i32 = if collapse { 1 } else { 0 };
1266
1267 let table_layout_fixed = match &self.box_type {
1271 BoxType::TableNode(node) => node
1272 .value("table-layout")
1273 .map(|v| v.trim().eq_ignore_ascii_case("fixed"))
1274 .unwrap_or(false),
1275 _ => false,
1276 };
1277
1278 let (h_spacing, v_spacing): (i32, i32) = if collapse {
1281 (0, 0)
1282 } else {
1283 match &self.box_type {
1284 BoxType::TableNode(node) => match node.value("border-spacing") {
1285 Some(v) => {
1286 let toks: Vec<&str> = v.split_whitespace().collect();
1287 let px = |s: &str| -> i32 {
1288 match parse_length_value(Some(String::from(s))) {
1289 Some(LengthValue::Px(px)) => px.max(0),
1290 _ => 0,
1291 }
1292 };
1293 match toks.as_slice() {
1294 [h] => (px(h), px(h)),
1295 [h, w] => (px(h), px(w)),
1296 _ => (0, 0),
1297 }
1298 }
1299 None => (0, 0),
1300 },
1301 _ => (0, 0),
1302 }
1303 };
1304
1305 let caption_indices: Vec<usize> = (0..self.children.len())
1309 .filter(|&i| box_tag_name(&self.children[i]) == Some("caption"))
1310 .collect();
1311 let caption_side_bottom = caption_indices
1312 .first()
1313 .and_then(|&ci| box_style_value(&self.children[ci], "caption-side"))
1314 .map(|v| v.trim().eq_ignore_ascii_case("bottom"))
1315 .unwrap_or(false);
1316 let mut top_offset = 0i32;
1317 if !caption_side_bottom {
1318 let y = self.dimensions.content.y;
1319 top_offset = self.layout_captions_at(&caption_indices, base_x, y, table_width);
1320 }
1321 let rows_top = self.dimensions.content.y + top_offset;
1322
1323 let row_indices: Vec<usize> = (0..self.children.len())
1327 .filter(|&i| {
1328 matches!(self.children[i].box_type, BoxType::TableRowNode(_))
1329 && box_style_value(&self.children[i], "visibility")
1330 .map(|v| !v.trim().eq_ignore_ascii_case("collapse"))
1331 .unwrap_or(true)
1332 })
1333 .collect();
1334 let nrows = row_indices.len();
1335
1336 struct CellPlace {
1338 row_ord: usize,
1339 cell_ord: usize,
1340 col: usize,
1341 colspan: usize,
1342 rowspan: usize,
1343 }
1344 let mut occ: Vec<Vec<bool>> = Vec::new();
1345 let mut places: Vec<CellPlace> = Vec::new();
1346 let mut ncols = 0usize;
1347 for (row_ord, &ri) in row_indices.iter().enumerate() {
1348 let ncells = self.children[ri].children.len();
1349 let mut c = 0usize;
1350 for cell_ord in 0..ncells {
1351 while occ_is(&occ, row_ord, c) {
1353 c += 1;
1354 }
1355 let (cs, rs) = cell_spans(&self.children[ri].children[cell_ord]);
1356 let rs = rs.min(nrows - row_ord); for rr in row_ord..(row_ord + rs) {
1358 for cc in c..(c + cs) {
1359 occ_mark(&mut occ, rr, cc);
1360 }
1361 }
1362 places.push(CellPlace {
1363 row_ord,
1364 cell_ord,
1365 col: c,
1366 colspan: cs,
1367 rowspan: rs.max(1),
1368 });
1369 c += cs;
1370 ncols = ncols.max(c);
1371 }
1372 }
1373
1374 if ncols == 0 {
1375 if caption_side_bottom {
1376 let y = self.dimensions.content.y + top_offset;
1377 top_offset += self.layout_captions_at(&caption_indices, base_x, y, table_width);
1378 }
1379 self.dimensions.content.height = top_offset;
1380 return;
1381 }
1382
1383 let mut col_w = alloc::vec![0i32; ncols];
1385 let mut is_fixed_col = alloc::vec![false; ncols];
1386 let pad = 24; let usable_width = (table_width - h_spacing * (ncols as i32 - 1).max(0)).max(1);
1388
1389 if table_layout_fixed {
1390 for p in &places {
1393 if p.row_ord != 0 || p.colspan != 1 {
1394 continue;
1395 }
1396 let cell = &self.children[row_indices[p.row_ord]].children[p.cell_ord];
1397 if let Some(w) = box_style_value(cell, "width") {
1398 match parse_length_value(Some(w)) {
1399 Some(LengthValue::Px(px)) => {
1400 col_w[p.col] = px.max(1);
1401 is_fixed_col[p.col] = true;
1402 }
1403 Some(LengthValue::Percent(pct)) => {
1404 col_w[p.col] = round_f32_to_i32(usable_width as f32 * pct).max(1);
1405 is_fixed_col[p.col] = true;
1406 }
1407 _ => {}
1408 }
1409 }
1410 }
1411 let fixed_sum: i32 = (0..ncols).filter(|&c| is_fixed_col[c]).map(|c| col_w[c]).sum();
1413 let auto_cols: alloc::vec::Vec<usize> =
1414 (0..ncols).filter(|&c| !is_fixed_col[c]).collect();
1415 if !auto_cols.is_empty() {
1416 let remaining = (usable_width - fixed_sum).max(0);
1417 let auto_w = (remaining / auto_cols.len() as i32).max(1);
1418 for &c in &auto_cols {
1419 col_w[c] = auto_w;
1420 }
1421 }
1422 } else {
1423 for p in &places {
1424 if p.colspan != 1 {
1425 continue;
1426 }
1427 let (cw, _) = self.children[row_indices[p.row_ord]].children[p.cell_ord]
1428 .intrinsic_inline_size(table_width);
1429 col_w[p.col] = col_w[p.col].max(cw + pad);
1430 }
1431 for p in &places {
1432 if p.colspan <= 1 {
1433 continue;
1434 }
1435 let (cw, _) = self.children[row_indices[p.row_ord]].children[p.cell_ord]
1436 .intrinsic_inline_size(table_width);
1437 let need = cw + pad;
1438 let cur: i32 = col_w[p.col..p.col + p.colspan].iter().sum();
1439 if need > cur {
1440 let deficit = need - cur;
1441 let add = deficit / p.colspan as i32;
1442 for cc in p.col..p.col + p.colspan {
1443 col_w[cc] += add;
1444 }
1445 col_w[p.col] += deficit - add * p.colspan as i32; }
1447 }
1448
1449 let total: i32 = col_w.iter().sum::<i32>().max(1);
1452 if total > usable_width {
1453 for w in col_w.iter_mut() {
1454 *w = ((*w as i64 * usable_width as i64) / total as i64).max(1) as i32;
1455 }
1456 } else {
1457 let extra = (usable_width - total) / ncols as i32;
1458 for w in col_w.iter_mut() {
1459 *w += extra;
1460 }
1461 }
1462 }
1463 let mut col_x = alloc::vec![0i32; ncols];
1465 let mut acc = 0i32;
1466 for c in 0..ncols {
1467 col_x[c] = acc;
1468 acc += col_w[c];
1469 if c + 1 < ncols {
1470 acc += h_spacing;
1471 }
1472 }
1473 let span_w = |col: usize, colspan: usize| -> i32 {
1474 col_w[col..(col + colspan).min(ncols)].iter().sum::<i32>()
1475 + h_spacing * colspan.saturating_sub(1) as i32
1476 };
1477
1478 let mut place_h = alloc::vec![0i32; places.len()];
1480 for (pi, p) in places.iter().enumerate() {
1481 let cw_total = span_w(p.col, p.colspan);
1482 let cell = &mut self.children[row_indices[p.row_ord]].children[p.cell_ord];
1483 let mut cb = Dimensions::default();
1484 cb.content.x = base_x + col_x[p.col];
1485 cb.content.y = rows_top;
1486 cb.content.height = 0;
1487 cb.content.width = cw_total;
1488 cell.layout(cb);
1489 place_h[pi] = cell.dimensions.margin_box().height;
1490 }
1491 let mut row_h = alloc::vec![0i32; nrows];
1493 for (pi, p) in places.iter().enumerate() {
1494 if p.rowspan == 1 {
1495 row_h[p.row_ord] = row_h[p.row_ord].max(place_h[pi]);
1496 }
1497 }
1498 for (pi, p) in places.iter().enumerate() {
1499 if p.rowspan <= 1 {
1500 continue;
1501 }
1502 let end = (p.row_ord + p.rowspan).min(nrows);
1503 let cur: i32 = row_h[p.row_ord..end].iter().sum();
1504 if place_h[pi] > cur {
1505 row_h[end - 1] += place_h[pi] - cur;
1506 }
1507 }
1508 let mut row_y = alloc::vec![0i32; nrows];
1510 let mut accy = 0i32;
1511 for r in 0..nrows {
1512 row_y[r] = accy;
1513 accy += row_h[r];
1514 if r + 1 < nrows {
1515 accy += v_spacing;
1516 }
1517 }
1518 let rows_total = accy;
1519
1520 for p in places.iter() {
1522 let cw_total = span_w(p.col, p.colspan);
1523 let end_r = (p.row_ord + p.rowspan).min(nrows);
1524 let span_h: i32 = row_h[p.row_ord..end_r].iter().sum();
1525 let x0 = base_x + col_x[p.col] - collapse_px * p.col as i32;
1526 let y0 = rows_top + row_y[p.row_ord] - collapse_px * p.row_ord as i32;
1527 let cell = &mut self.children[row_indices[p.row_ord]].children[p.cell_ord];
1528 let mut cb = Dimensions::default();
1529 cb.content.x = x0;
1530 cb.content.y = y0;
1531 cb.content.height = 0;
1532 cb.content.width = cw_total;
1533 cell.layout(cb); let w_extra = cell.dimensions.padding.left
1535 + cell.dimensions.padding.right
1536 + cell.dimensions.border.left
1537 + cell.dimensions.border.right;
1538 let h_extra = cell.dimensions.padding.top
1539 + cell.dimensions.padding.bottom
1540 + cell.dimensions.border.top
1541 + cell.dimensions.border.bottom;
1542 let natural_h = cell.dimensions.content.height;
1545 let target_h = (span_h - h_extra).max(natural_h);
1546 let extra_space = (target_h - natural_h).max(0);
1547 if extra_space > 0 {
1548 let va = match &cell.box_type {
1549 BoxType::TableCellNode(node) => node.value("vertical-align").unwrap_or_default(),
1550 _ => String::new(),
1551 };
1552 let dy = match va.trim() {
1553 "middle" | "center" => extra_space / 2,
1554 "bottom" => extra_space,
1555 _ => 0, };
1557 for child in &mut cell.children {
1559 child.translate_y(dy);
1560 }
1561 }
1562 cell.dimensions.content.width = (cw_total - w_extra).max(1);
1563 cell.dimensions.content.height = target_h;
1564 }
1565
1566 for (row_ord, &ri) in row_indices.iter().enumerate() {
1568 let row = &mut self.children[ri];
1569 row.dimensions.content.x = base_x;
1570 row.dimensions.content.y = rows_top + row_y[row_ord] - collapse_px * row_ord as i32;
1571 row.dimensions.content.width = table_width;
1572 row.dimensions.content.height = row_h[row_ord];
1573 let saved_row_cb = row.establish_abs_cb();
1576 row.apply_positioned_offsets();
1577 Self::restore_abs_cb(saved_row_cb);
1578 }
1579
1580 let collapse_shrink = collapse_px * (nrows.saturating_sub(1)) as i32;
1582 let rows_height = (rows_total - collapse_shrink).max(0);
1583 if caption_side_bottom {
1584 let y = self.dimensions.content.y + top_offset + rows_height;
1585 top_offset += self.layout_captions_at(&caption_indices, base_x, y, table_width);
1586 }
1587 self.dimensions.content.height = top_offset + rows_height;
1588
1589 let saved_abs_cb = self.establish_abs_cb();
1593 self.apply_positioned_offsets();
1594 Self::restore_abs_cb(saved_abs_cb);
1595 }
1596
1597 fn layout_flex(&mut self, containing_block: Dimensions) {
1598 self.apply_box_model_styles_with_base(containing_block.content.width);
1599
1600 let (width_val, min_width_val, max_width_val, height_val, box_sizing, h_auto) =
1601 match &self.box_type {
1602 BoxType::FlexNode(node) => (
1603 node.value("width"),
1604 node.value("min-width"),
1605 node.value("max-width"),
1606 node.value("height"),
1607 node.value("box-sizing").unwrap_or_default(),
1608 margin_is_h_auto(node),
1609 ),
1610 _ => return,
1611 };
1612 let inner_non_content = self.dimensions.border.left
1614 + self.dimensions.border.right
1615 + self.dimensions.padding.left
1616 + self.dimensions.padding.right;
1617
1618 let horizontal_non_content = self.dimensions.margin.left
1619 + self.dimensions.margin.right
1620 + self.dimensions.border.left
1621 + self.dimensions.border.right
1622 + self.dimensions.padding.left
1623 + self.dimensions.padding.right;
1624 let available_width = containing_block.content.width.max(1);
1625 let explicit_width =
1626 !matches!(parse_length_value(width_val.clone()), Some(LengthValue::Auto) | None);
1627
1628 let mut content_width = match parse_length_value(width_val) {
1629 Some(LengthValue::Auto) | None => (available_width - horizontal_non_content).max(1),
1630 Some(LengthValue::Px(px)) => px.max(1),
1631 Some(LengthValue::Percent(pct)) => {
1632 round_f32_to_i32(available_width as f32 * pct).max(1)
1633 }
1634 Some(LengthValue::MinContent) => {
1635 let (minc, _maxc) = self.block_intrinsic_widths();
1636 minc.max(1)
1637 }
1638 Some(LengthValue::MaxContent) => {
1639 let (_minc, maxc) = self.block_intrinsic_widths();
1640 maxc.max(1)
1641 }
1642 Some(LengthValue::FitContent) => {
1643 let (minc, maxc) = self.block_intrinsic_widths();
1644 let avail = (available_width - horizontal_non_content).max(0);
1645 avail.min(maxc).max(minc).max(1)
1646 }
1647 Some(LengthValue::MathExpr(expr)) => {
1648 crate::os_lib::css::eval_css_math(&expr, available_width)
1649 .unwrap_or((available_width - horizontal_non_content).max(1))
1650 .max(1)
1651 }
1652 };
1653 if explicit_width && box_sizing.trim() == "border-box" {
1655 content_width = (content_width - inner_non_content).max(1);
1656 }
1657
1658 if let Some(min_width) =
1659 parse_length_value(min_width_val).and_then(|v| resolve_length_value(v, available_width))
1660 {
1661 content_width = content_width.max(min_width);
1662 }
1663 if let Some(max_width) =
1664 parse_length_value(max_width_val).and_then(|v| resolve_length_value(v, available_width))
1665 {
1666 content_width = content_width.min(max_width);
1667 }
1668 self.dimensions.content.width = content_width.max(1);
1669
1670 if h_auto {
1673 let free = (available_width - self.dimensions.content.width - inner_non_content).max(0);
1674 self.dimensions.margin.left = free / 2;
1675 self.dimensions.margin.right = free / 2;
1676 }
1677
1678 let flow_y = containing_block.content.y
1679 + containing_block.content.height
1680 + self.dimensions.margin.top;
1681 self.dimensions.content.x = containing_block.content.x
1682 + self.dimensions.margin.left
1683 + self.dimensions.border.left
1684 + self.dimensions.padding.left;
1685 self.dimensions.content.y =
1686 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
1687
1688 let definite_h = self.explicit_content_height_px(height_val.clone()).unwrap_or(-1);
1690 let _pct_h_guard = PctBaseHGuard::set(definite_h);
1691
1692 let (flex_dir, justify_content, align_items, align_content, row_gap, col_gap, flex_wrap) =
1693 match &self.box_type {
1694 BoxType::FlexNode(node) => {
1695 let (rg, cg) = parse_flex_gap(node, self.dimensions.content.width);
1696 (
1697 node.value("flex-direction")
1698 .unwrap_or_else(|| String::from("row"))
1699 .trim()
1700 .to_lowercase(),
1701 node.value("justify-content")
1702 .unwrap_or_else(|| String::from("flex-start"))
1703 .trim()
1704 .to_lowercase(),
1705 node.value("align-items")
1706 .unwrap_or_else(|| String::from("stretch"))
1707 .trim()
1708 .to_lowercase(),
1709 node.value("align-content")
1710 .unwrap_or_else(|| String::from("stretch"))
1711 .trim()
1712 .to_lowercase(),
1713 rg,
1714 cg,
1715 node.value("flex-wrap")
1716 .unwrap_or_else(|| String::from("nowrap"))
1717 .trim()
1718 .to_lowercase(),
1719 )
1720 }
1721 _ => return,
1722 };
1723
1724 let is_row = flex_dir == "row" || flex_dir == "row-reverse";
1726 let is_reverse = flex_dir == "row-reverse" || flex_dir == "column-reverse";
1727 let do_wrap = flex_wrap == "wrap" || flex_wrap == "wrap-reverse";
1729 let gap_main = if is_row { col_gap } else { row_gap };
1731 let gap_cross = if is_row { row_gap } else { col_gap };
1733
1734 if do_wrap {
1735 self.layout_flex_wrapped(
1736 is_row,
1737 is_reverse,
1738 flex_wrap == "wrap-reverse",
1739 &justify_content,
1740 &align_items,
1741 &align_content,
1742 gap_main,
1743 gap_cross,
1744 height_val.clone(),
1745 );
1746 return;
1747 }
1748
1749 let mut total_main = 0;
1751 let mut max_cross = 0;
1752 let oof: Vec<bool> = self
1755 .children
1756 .iter()
1757 .map(|c| Self::child_is_out_of_flow(c))
1758 .collect();
1759 let n = oof.iter().filter(|&&b| !b).count();
1760
1761 let basis_main_ref = if is_row {
1763 self.dimensions.content.width
1764 } else {
1765 match parse_length_value(height_val.clone()) {
1766 Some(LengthValue::Px(px)) => px,
1767 _ => 0,
1768 }
1769 };
1770
1771 for (ci, child) in self.children.iter_mut().enumerate() {
1772 let mut cb = self.dimensions.clone();
1773 cb.content.x = 0;
1774 cb.content.y = 0;
1775 cb.content.height = 0; child.layout(cb);
1777 if oof.get(ci).copied().unwrap_or(false) {
1778 continue;
1779 }
1780 {
1785 let (lead, trail) = box_main_auto_margins(child, is_row);
1786 if lead || trail {
1787 if is_row {
1788 child.dimensions.margin.left = 0;
1789 child.dimensions.margin.right = 0;
1790 } else {
1791 child.dimensions.margin.top = 0;
1792 child.dimensions.margin.bottom = 0;
1793 }
1794 }
1795 }
1796 if let Some(basis) = box_flex_basis(child, basis_main_ref) {
1798 if is_row {
1799 child.dimensions.content.width = basis;
1800 } else {
1801 child.dimensions.content.height = basis;
1802 }
1803 }
1804 if is_row
1816 && box_flex_basis(child, basis_main_ref).is_none()
1817 && !box_has_explicit(child, "width")
1818 {
1819 let mut max_content = child.block_intrinsic_widths().1;
1820 let container_w = self.dimensions.content.width;
1824 if let Some(mw) = box_len(child, "max-width", container_w) {
1825 max_content = max_content.min(mw.max(0));
1826 }
1827 if let Some(mw) = box_len(child, "min-width", container_w) {
1828 max_content = max_content.max(mw);
1829 }
1830 if max_content > 0 && max_content != child.dimensions.content.width {
1831 let mut cb2 = self.dimensions.clone();
1836 cb2.content.x = 0;
1837 cb2.content.y = 0;
1838 cb2.content.width = max_content + child.h_non_content();
1842 cb2.content.height = 0; child.layout(cb2);
1844 child.dimensions.content.width = max_content;
1845 }
1846 }
1847 let child_w = child.dimensions.margin_box().width;
1848 let child_h = child.dimensions.margin_box().height;
1849 if is_row {
1850 total_main += child_w;
1851 max_cross = max_cross.max(child_h);
1852 } else {
1853 total_main += child_h;
1854 max_cross = max_cross.max(child_w);
1855 }
1856 }
1857 if n > 1 {
1859 total_main += gap_main * (n as i32 - 1);
1860 }
1861
1862 let container_main = if is_row {
1863 self.dimensions.content.width
1864 } else {
1865 self.explicit_content_height_px(height_val.clone()).unwrap_or(total_main)
1866 };
1867
1868 let mut container_cross = if is_row {
1869 self.explicit_content_height_px(height_val.clone()).unwrap_or(max_cross)
1870 } else {
1871 self.dimensions.content.width
1872 };
1873
1874 if !is_row {
1875 self.dimensions.content.height = container_main;
1876 } else {
1877 self.dimensions.content.height = container_cross;
1878 }
1879
1880 let grows: Vec<i32> = self
1883 .children
1884 .iter()
1885 .enumerate()
1886 .map(|(i, c)| {
1887 if oof.get(i).copied().unwrap_or(false) {
1888 0
1889 } else {
1890 box_flex_grow(c)
1891 }
1892 })
1893 .collect();
1894 let total_grow: i32 = grows.iter().sum();
1895 if total_grow > 0 {
1896 let mut base_main = total_main;
1898 for (i, child) in self.children.iter().enumerate() {
1899 if grows.get(i).copied().unwrap_or(0) <= 0 {
1900 continue;
1901 }
1902 base_main -= if is_row {
1903 child.dimensions.content.width
1904 } else {
1905 child.dimensions.content.height
1906 };
1907 }
1908 let free = (container_main - base_main).max(0);
1909 if free > 0 {
1910 let last_grow_idx = grows.iter().rposition(|g| *g > 0);
1911 let mut remaining = free;
1912 for (i, child) in self.children.iter_mut().enumerate() {
1913 let g = grows.get(i).copied().unwrap_or(0);
1914 if g <= 0 {
1915 continue;
1916 }
1917 let extra = if Some(i) == last_grow_idx {
1919 remaining
1920 } else {
1921 free * g / total_grow
1922 };
1923 remaining -= extra;
1924 if is_row {
1925 child.dimensions.content.width = extra.max(0);
1926 } else {
1927 child.dimensions.content.height = extra.max(0);
1928 }
1929 }
1930 total_main = base_main + free; }
1932 }
1933
1934 let overflow = total_main - container_main;
1938 if total_grow == 0 && overflow > 0 {
1939 let shrinks: Vec<i32> = self
1940 .children
1941 .iter()
1942 .enumerate()
1943 .map(|(i, c)| {
1944 if oof.get(i).copied().unwrap_or(false) {
1945 0
1946 } else {
1947 box_flex_shrink(c)
1948 }
1949 })
1950 .collect();
1951 let total_shrink: i32 = shrinks.iter().sum();
1952 if total_shrink > 0 {
1953 let items: Vec<crate::os_lib::layout::flex_min::ShrinkItem> = self
1959 .children
1960 .iter()
1961 .enumerate()
1962 .map(|(i, c)| {
1963 let base = if is_row {
1964 c.dimensions.content.width
1965 } else {
1966 c.dimensions.content.height
1967 };
1968 let min = if is_row {
1970 c.block_intrinsic_widths().0
1971 } else {
1972 0
1973 };
1974 crate::os_lib::layout::flex_min::ShrinkItem {
1975 base: base.max(0),
1976 min: min.clamp(0, base.max(0)),
1977 shrink: shrinks.get(i).copied().unwrap_or(0).max(0),
1978 }
1979 })
1980 .collect();
1981
1982 match crate::os_lib::layout::flex_min::distribute_shrink(&items, overflow.max(0)) {
1983 Ok(sizes) => {
1984 for (i, child) in self.children.iter_mut().enumerate() {
1985 if let Some(&s) = sizes.get(i) {
1986 if is_row {
1987 child.dimensions.content.width = s;
1988 } else {
1989 child.dimensions.content.height = s;
1990 }
1991 }
1992 }
1993 if is_row {
2004 for (i, child) in self.children.iter_mut().enumerate() {
2005 if oof.get(i).copied().unwrap_or(false) {
2006 continue;
2007 }
2008 let Some(&s) = sizes.get(i) else {
2009 continue;
2010 };
2011 let base = items.get(i).map(|it| it.base).unwrap_or(s);
2012 if s >= base {
2014 continue;
2015 }
2016 let mut cb = self.dimensions.clone();
2017 cb.content.x = 0;
2018 cb.content.y = 0;
2019 cb.content.width = s + child.h_non_content();
2022 cb.content.height = 0; child.layout(cb);
2024 child.dimensions.content.width = s;
2026 }
2027 }
2028 total_main = sizes.iter().sum::<i32>().max(container_main.min(total_main));
2031 }
2032 Err(_) => {
2033 }
2036 }
2037 }
2038 }
2039
2040 if is_row && !matches!(parse_length_value(height_val.clone()), Some(LengthValue::Px(_))) {
2050 let mut recomputed = 0;
2051 for (ci, child) in self.children.iter().enumerate() {
2052 if oof.get(ci).copied().unwrap_or(false) {
2053 continue;
2054 }
2055 recomputed = recomputed.max(child.dimensions.margin_box().height);
2056 }
2057 if recomputed > container_cross {
2058 container_cross = recomputed;
2059 self.dimensions.content.height = container_cross;
2060 }
2061 }
2062
2063 if align_items == "stretch" {
2066 let cross_prop = if is_row { "height" } else { "width" };
2067 for (ci, child) in self.children.iter_mut().enumerate() {
2068 if oof.get(ci).copied().unwrap_or(false) {
2069 continue;
2070 }
2071 if box_has_explicit(child, cross_prop) {
2072 continue;
2073 }
2074 if let Some(s) = box_align_self(child) {
2076 if s != "stretch" {
2077 continue;
2078 }
2079 }
2080 let mb = child.dimensions.margin_box();
2081 let (cur_cross, cur_content) = if is_row {
2082 (mb.height, child.dimensions.content.height)
2083 } else {
2084 (mb.width, child.dimensions.content.width)
2085 };
2086 let non_content = cur_cross - cur_content; let new_content = (container_cross - non_content).max(0);
2088 if is_row {
2089 child.dimensions.content.height = new_content;
2090 } else {
2091 child.dimensions.content.width = new_content;
2092 }
2093 }
2094 }
2095
2096 let main_free_space = container_main - total_main;
2098
2099 let mut total_auto_margins = 0i32;
2102 for (ci, child) in self.children.iter().enumerate() {
2103 if oof.get(ci).copied().unwrap_or(false) {
2104 continue;
2105 }
2106 let (a, b) = box_main_auto_margins(child, is_row);
2107 total_auto_margins += a as i32 + b as i32;
2108 }
2109 let auto_margin_unit = if total_auto_margins > 0 && main_free_space > 0 {
2110 main_free_space / total_auto_margins
2111 } else {
2112 0
2113 };
2114 let use_auto_margins = total_auto_margins > 0 && main_free_space > 0;
2115
2116 let main_offset = if use_auto_margins {
2117 0
2118 } else {
2119 match justify_content.as_str() {
2120 "flex-end" | "end" => main_free_space,
2125 "center" => main_free_space / 2,
2126 "space-around" if n > 0 => main_free_space / (n as i32 * 2),
2127 "space-evenly" if n > 0 => main_free_space / (n as i32 + 1),
2131 _ => 0,
2133 }
2134 };
2135
2136 let spacing = if use_auto_margins {
2137 0
2138 } else {
2139 match justify_content.as_str() {
2140 "space-between" if n > 1 => main_free_space / (n as i32 - 1),
2141 "space-around" if n > 0 => main_free_space / (n as i32),
2142 "space-evenly" if n > 0 => main_free_space / (n as i32 + 1),
2143 _ => 0,
2144 }
2145 };
2146
2147 let mut order_idx: Vec<usize> = (0..self.children.len()).collect();
2151 order_idx.sort_by_key(|&i| (box_order(&self.children[i]), i));
2152 let mut cursor = main_offset;
2153 for &ci in &order_idx {
2154 if oof.get(ci).copied().unwrap_or(false) {
2156 continue;
2157 }
2158 let (lead_auto, trail_auto) = if use_auto_margins {
2160 box_main_auto_margins(&self.children[ci], is_row)
2161 } else {
2162 (false, false)
2163 };
2164 if lead_auto {
2165 cursor += auto_margin_unit;
2166 }
2167 let child_w = self.children[ci].dimensions.margin_box().width;
2168 let child_h = self.children[ci].dimensions.margin_box().height;
2169 let child_main = if is_row { child_w } else { child_h };
2170
2171 let cross_free_space = container_cross - if is_row { child_h } else { child_w };
2172 let effective_align =
2174 box_align_self(&self.children[ci]).unwrap_or_else(|| align_items.clone());
2175 let cross_offset = match effective_align.as_str() {
2176 "flex-end" | "end" | "self-end" => cross_free_space,
2180 "center" => cross_free_space / 2,
2181 _ => 0, };
2183
2184 let main_start = if is_reverse {
2186 container_main - cursor - child_main
2187 } else {
2188 cursor
2189 };
2190
2191 let start_x = self.dimensions.content.x;
2193 let start_y = self.dimensions.content.y;
2194
2195 let target_x = if is_row {
2196 start_x + main_start
2197 } else {
2198 start_x + cross_offset
2199 };
2200 let target_y = if is_row {
2201 start_y + cross_offset
2202 } else {
2203 start_y + main_start
2204 };
2205
2206 let shift_x = target_x - self.children[ci].dimensions.margin_box().x;
2207 let shift_y = target_y - self.children[ci].dimensions.margin_box().y;
2208
2209 shift_layout_box(&mut self.children[ci], shift_x, shift_y);
2210
2211 cursor += child_main + spacing + gap_main;
2212 if trail_auto {
2213 cursor += auto_margin_unit;
2214 }
2215 }
2216
2217 let saved_abs_cb = self.establish_abs_cb();
2220 self.apply_positioned_offsets();
2221 Self::restore_abs_cb(saved_abs_cb);
2222 }
2223
2224 #[allow(clippy::too_many_arguments)]
2228 fn layout_flex_wrapped(
2229 &mut self,
2230 is_row: bool,
2231 is_reverse: bool,
2232 wrap_reverse: bool,
2233 justify_content: &str,
2234 align_items: &str,
2235 align_content: &str,
2236 gap_main: i32,
2237 gap_cross: i32,
2238 height_val: Option<String>,
2239 ) {
2240 let n = self.children.len();
2241 if n == 0 {
2242 return;
2243 }
2244
2245 let oof: Vec<bool> = self
2249 .children
2250 .iter()
2251 .map(|c| Self::child_is_out_of_flow(c))
2252 .collect();
2253 let mut sizes: Vec<(i32, i32)> = Vec::with_capacity(n); for child in &mut self.children {
2255 let mut cb = self.dimensions.clone();
2256 cb.content.x = 0;
2257 cb.content.y = 0;
2258 cb.content.height = 0;
2259 child.layout(cb);
2260 let w = child.dimensions.margin_box().width;
2261 let h = child.dimensions.margin_box().height;
2262 if is_row {
2263 sizes.push((w, h));
2264 } else {
2265 sizes.push((h, w));
2266 }
2267 }
2268 let flow_idx: Vec<usize> = (0..n).filter(|&i| !oof[i]).collect();
2270
2271 let container_main = if is_row {
2272 self.dimensions.content.width
2273 } else {
2274 self.explicit_content_height_px(height_val.clone()).unwrap_or(i32::MAX / 4)
2276 };
2277
2278 let fln = flow_idx.len();
2280 let mut lines: Vec<(usize, usize, i32, i32)> = Vec::new(); let mut line_start = 0usize;
2282 let mut line_main = 0i32;
2283 let mut line_cross = 0i32;
2284 for pos in 0..fln {
2285 let (m, c) = sizes[flow_idx[pos]];
2286 let add = if pos == line_start { m } else { m + gap_main };
2287 if pos > line_start && line_main + add > container_main {
2288 lines.push((line_start, pos, line_main, line_cross));
2289 line_start = pos;
2290 line_main = m;
2291 line_cross = c;
2292 } else {
2293 line_main += add;
2294 line_cross = line_cross.max(c);
2295 }
2296 }
2297 lines.push((line_start, fln, line_main, line_cross));
2298
2299 if wrap_reverse {
2301 lines.reverse();
2302 }
2303
2304 let total_cross: i32 =
2306 lines.iter().map(|l| l.3).sum::<i32>() + gap_cross * (lines.len() as i32 - 1).max(0);
2307 let container_cross = if is_row {
2308 self.explicit_content_height_px(height_val.clone()).unwrap_or(total_cross)
2309 } else {
2310 self.dimensions.content.width
2311 };
2312
2313 if is_row {
2314 self.dimensions.content.height = container_cross.max(total_cross);
2315 } else {
2316 self.dimensions.content.height = container_main.min(total_cross).max(total_cross);
2317 }
2318
2319 let base_x = self.dimensions.content.x;
2320 let base_y = self.dimensions.content.y;
2321
2322 let nlines = lines.len() as i32;
2327 let cross_free = (container_cross - total_cross).max(0);
2328 let mut line_cross_size: Vec<i32> = lines.iter().map(|l| l.3).collect();
2329 if (align_content == "stretch" || align_content.is_empty()) && cross_free > 0 && nlines > 0
2331 {
2332 let per = cross_free / nlines;
2333 let mut rem = cross_free - per * nlines;
2334 for sz in line_cross_size.iter_mut() {
2335 *sz += per;
2336 if rem > 0 {
2337 *sz += 1;
2338 rem -= 1;
2339 }
2340 }
2341 }
2342 let (ac_offset, ac_spacing) = match align_content {
2344 "flex-end" | "end" => (cross_free, 0),
2345 "center" => (cross_free / 2, 0),
2346 "space-between" if nlines > 1 => (0, cross_free / (nlines - 1)),
2347 "space-around" if nlines > 0 => (cross_free / (nlines * 2), cross_free / nlines),
2348 "space-evenly" if nlines > 0 => (cross_free / (nlines + 1), cross_free / (nlines + 1)),
2349 _ => (0, 0),
2351 };
2352 let mut line_cross_start: Vec<i32> = Vec::with_capacity(lines.len());
2353 let mut cc = ac_offset;
2354 for sz in &line_cross_size {
2355 line_cross_start.push(cc);
2356 cc += sz + gap_cross + ac_spacing;
2357 }
2358
2359 for (li, &(start, end_excl, line_main_used, _line_cross_nat)) in lines.iter().enumerate() {
2360 let count = end_excl - start;
2361 let main_free = container_main - line_main_used;
2362 let line_cross = line_cross_size.get(li).copied().unwrap_or(0);
2363 let cross_cursor = line_cross_start.get(li).copied().unwrap_or(0);
2364
2365 let mut main_offset = match justify_content {
2367 "flex-end" | "end" => main_free,
2368 "center" => main_free / 2,
2369 "space-around" if count > 0 => main_free / (count as i32 * 2),
2370 "space-evenly" if count > 0 => main_free / (count as i32 + 1),
2371 _ => 0,
2372 };
2373 let spacing = match justify_content {
2374 "space-between" if count > 1 => main_free / (count as i32 - 1),
2375 "space-around" if count > 0 => main_free / (count as i32),
2376 "space-evenly" if count > 0 => main_free / (count as i32 + 1),
2377 _ => 0,
2378 };
2379
2380 let pos_list: Vec<usize> = if is_reverse {
2382 (start..end_excl).rev().collect()
2383 } else {
2384 (start..end_excl).collect()
2385 };
2386
2387 for &pos in &pos_list {
2388 let ci = flow_idx[pos];
2389 let (m, c) = sizes[ci];
2390 let cross_free_item = line_cross - c;
2391 let effective_align =
2392 box_align_self(&self.children[ci]).unwrap_or_else(|| String::from(align_items));
2393 let cross_in_line = match effective_align.as_str() {
2394 "flex-end" | "end" | "self-end" => cross_free_item,
2395 "center" => cross_free_item / 2,
2396 _ => 0, };
2398 let cross_pos = cross_cursor + cross_in_line;
2399
2400 let (target_x, target_y) = if is_row {
2401 (base_x + main_offset, base_y + cross_pos)
2402 } else {
2403 (base_x + cross_pos, base_y + main_offset)
2404 };
2405
2406 let shift_x = target_x - self.children[ci].dimensions.margin_box().x;
2407 let shift_y = target_y - self.children[ci].dimensions.margin_box().y;
2408 shift_layout_box(&mut self.children[ci], shift_x, shift_y);
2409
2410 main_offset += m + spacing + gap_main;
2411 }
2412 }
2413
2414 let saved_abs_cb = self.establish_abs_cb();
2416 self.apply_positioned_offsets();
2417 Self::restore_abs_cb(saved_abs_cb);
2418 }
2419
2420 fn layout_block(&mut self, containing_block: Dimensions) {
2421 self.apply_box_model_styles_with_base(containing_block.content.width);
2422
2423 let (
2426 width_val,
2427 min_width_val,
2428 max_width_val,
2429 height_val,
2430 min_height_val,
2431 box_sizing,
2432 h_auto,
2433 aspect_ratio_val,
2434 ) = match &self.box_type {
2435 BoxType::BlockNode(node)
2436 | BoxType::TableRowGroupNode(node)
2437 | BoxType::TableRowNode(node)
2438 | BoxType::TableCellNode(node) => (
2439 node.value("width"),
2440 node.value("min-width"),
2441 node.value("max-width"),
2442 node.value("height"),
2443 node.value("min-height"),
2444 node.value("box-sizing").unwrap_or_default(),
2445 margin_is_h_auto(node),
2446 node.value("aspect-ratio"),
2447 ),
2448 _ => return,
2449 };
2450
2451 let horizontal_non_content = self.dimensions.margin.left
2452 + self.dimensions.margin.right
2453 + self.dimensions.border.left
2454 + self.dimensions.border.right
2455 + self.dimensions.padding.left
2456 + self.dimensions.padding.right;
2457 let available_width = containing_block.content.width.max(1);
2458 let inner_non_content = self.dimensions.border.left
2460 + self.dimensions.border.right
2461 + self.dimensions.padding.left
2462 + self.dimensions.padding.right;
2463
2464 let parsed_width = parse_length_value(width_val.clone());
2466 let explicit_width = !matches!(parsed_width, Some(LengthValue::Auto) | None);
2467 let mut content_width = match parsed_width {
2468 Some(LengthValue::Auto) | None => (available_width - horizontal_non_content).max(1),
2469 Some(LengthValue::Px(px)) => px.max(1),
2470 Some(LengthValue::Percent(pct)) => {
2471 round_f32_to_i32(available_width as f32 * pct).max(1)
2472 }
2473 Some(LengthValue::MinContent) => {
2475 let (minc, _maxc) = self.block_intrinsic_widths();
2476 minc.max(1)
2477 }
2478 Some(LengthValue::MaxContent) => {
2479 let (_minc, maxc) = self.block_intrinsic_widths();
2480 maxc.max(1)
2481 }
2482 Some(LengthValue::FitContent) => {
2483 let avail = (available_width - horizontal_non_content).max(1);
2485 let (minc, maxc) = self.block_intrinsic_widths();
2486 maxc.min(avail).max(minc).max(1)
2487 }
2488 Some(LengthValue::MathExpr(expr)) => {
2489 crate::os_lib::css::eval_css_math(&expr, available_width)
2490 .unwrap_or((available_width - horizontal_non_content).max(1))
2491 .max(1)
2492 }
2493 };
2494 if explicit_width && box_sizing.trim() == "border-box" {
2498 content_width = (content_width - inner_non_content).max(1);
2499 }
2500
2501 if let Some(min_width) = self.resolve_sizing_value(min_width_val.clone(), available_width) {
2503 content_width = content_width.max(min_width);
2504 }
2505 if let Some(max_width) = self.resolve_sizing_value(max_width_val.clone(), available_width) {
2506 content_width = content_width.min(max_width);
2507 }
2508 self.dimensions.content.width = content_width.max(1);
2509
2510 if h_auto {
2512 let free = (available_width - content_width - inner_non_content).max(0);
2513 self.dimensions.margin.left = free / 2;
2514 self.dimensions.margin.right = free / 2;
2515 }
2516
2517 let flow_y = containing_block.content.y
2519 + containing_block.content.height
2520 + self.dimensions.margin.top;
2521 self.dimensions.content.x = containing_block.content.x
2522 + self.dimensions.margin.left
2523 + self.dimensions.border.left
2524 + self.dimensions.padding.left;
2525 self.dimensions.content.y =
2526 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
2527
2528 let definite_h = match parse_length_value(height_val.clone()) {
2531 Some(LengthValue::Px(px)) => {
2532 if box_sizing.trim() == "border-box" {
2533 let d = &self.dimensions;
2534 (px - d.padding.top - d.padding.bottom - d.border.top - d.border.bottom).max(0)
2535 } else {
2536 px.max(0)
2537 }
2538 }
2539 None | Some(LengthValue::Auto) => aspect_ratio_val
2540 .as_deref()
2541 .and_then(parse_aspect_ratio)
2542 .map(|(num, den)| round_f32_to_i32(self.dimensions.content.width as f32 * den / num).max(0))
2543 .unwrap_or(-1),
2544 _ => -1,
2545 };
2546 let _pct_h_guard = PctBaseHGuard::set(definite_h);
2547
2548 let self_pos = box_position(self);
2551 let saved_abs_cb = if matches!(self_pos.as_str(), "relative" | "absolute" | "fixed") {
2552 let prev = abs_cb_get();
2553 let exp_h = parse_length_value(height_val.clone())
2554 .and_then(|v| resolve_length_value(v, containing_block.content.height.max(0)))
2555 .unwrap_or(0);
2556 abs_cb_set(
2557 self.dimensions.content.x,
2558 self.dimensions.content.y,
2559 self.dimensions.content.width,
2560 exp_h,
2561 );
2562 Some(prev)
2563 } else {
2564 None
2565 };
2566
2567 let line_height_default = 20i32;
2569 let mut y_cursor = 0i32;
2570 let mut line_x = 0i32;
2571 let mut line_h = 0i32;
2572 let max_w = self.dimensions.content.width.max(1);
2573
2574 let mut left_float: Option<(i32, i32)> = None;
2578 let mut right_float: Option<(i32, i32)> = None;
2579
2580 fn float_avail(
2582 max_w: i32,
2583 left_float: Option<(i32, i32)>,
2584 right_float: Option<(i32, i32)>,
2585 y: i32,
2586 ) -> (i32, i32) {
2587 let l = left_float
2588 .filter(|&(_, yb)| yb > y)
2589 .map(|(w, _)| w)
2590 .unwrap_or(0);
2591 let r = right_float
2592 .filter(|&(_, yb)| yb > y)
2593 .map(|(w, _)| w)
2594 .unwrap_or(0);
2595 (l, (max_w - l - r).max(1))
2596 }
2597
2598 for child in &mut self.children {
2599 let float_side = box_float(child);
2600 if float_side != "none" {
2601 let float_y = y_cursor;
2603 let is_inline_kind =
2604 matches!(child.box_type, BoxType::InlineNode(_) | BoxType::InlineBlockNode(_));
2605 let (fw, fh) = if is_inline_kind {
2606 child.intrinsic_inline_size(max_w)
2607 } else {
2608 let mut cb = self.dimensions.clone();
2609 cb.content.height = float_y;
2610 child.layout(cb);
2611 (
2612 child.dimensions.margin_box().width,
2613 child.dimensions.margin_box().height,
2614 )
2615 };
2616
2617 let x = if float_side == "left" {
2618 let x_off = left_float
2619 .filter(|&(_, yb)| yb > float_y)
2620 .map(|(w, _)| w)
2621 .unwrap_or(0);
2622 left_float = Some((x_off + fw, float_y + fh));
2623 self.dimensions.content.x + x_off
2624 } else {
2625 let x_off = right_float
2626 .filter(|&(_, yb)| yb > float_y)
2627 .map(|(w, _)| w)
2628 .unwrap_or(0);
2629 right_float = Some((x_off + fw, float_y + fh));
2630 self.dimensions.content.x + max_w - x_off - fw
2631 };
2632
2633 if is_inline_kind {
2634 child.dimensions.content.x = x;
2635 child.dimensions.content.y = self.dimensions.content.y + float_y;
2636 child.dimensions.content.width = fw;
2637 child.dimensions.content.height = fh;
2638 let mut cb = child.dimensions.clone();
2639 cb.content.height = 0;
2640 child.layout(cb);
2641 child.dimensions.content.x = x;
2642 child.dimensions.content.y = self.dimensions.content.y + float_y;
2643 child.dimensions.content.width = fw;
2644 child.dimensions.content.height = fh;
2645 } else {
2646 let dx = x - child.dimensions.content.x;
2648 child.dimensions.content.x += dx;
2649 }
2650 continue;
2652 }
2653
2654 match child.box_type {
2655 BoxType::BlockNode(_)
2656 | BoxType::FlexNode(_)
2657 | BoxType::GridNode(_)
2658 | BoxType::AnonymousBlock
2659 | BoxType::TableNode(_)
2660 | BoxType::TableRowGroupNode(_)
2661 | BoxType::TableRowNode(_)
2662 | BoxType::TableCellNode(_) => {
2663 let out_of_flow = matches!(box_position(child).as_str(), "absolute" | "fixed");
2664 if out_of_flow {
2665 let mut cb = self.dimensions.clone();
2667 cb.content.height = y_cursor;
2668 child.layout(cb);
2669 } else {
2670 if line_x > 0 {
2671 y_cursor += if line_h > 0 { line_h } else { line_height_default };
2672 line_x = 0;
2673 line_h = 0;
2674 }
2675 let clear = box_clear(child);
2677 if clear == "left" || clear == "both" {
2678 if let Some((_, yb)) = left_float {
2679 y_cursor = y_cursor.max(yb);
2680 }
2681 }
2682 if clear == "right" || clear == "both" {
2683 if let Some((_, yb)) = right_float {
2684 y_cursor = y_cursor.max(yb);
2685 }
2686 }
2687 let (x_off, avail) = float_avail(max_w, left_float, right_float, y_cursor);
2688 let mut cb = self.dimensions.clone();
2689 cb.content.height = y_cursor;
2690 cb.content.x = self.dimensions.content.x + x_off;
2691 cb.content.width = avail;
2692 child.layout(cb);
2693 y_cursor += child.dimensions.margin_box().height;
2694 }
2695 }
2696 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
2697 if child.is_inline_line_break() {
2698 y_cursor += if line_h > 0 { line_h } else { line_height_default };
2699 line_x = 0;
2700 line_h = 0;
2701 child.dimensions.content.x = self.dimensions.content.x;
2702 child.dimensions.content.y = self.dimensions.content.y + y_cursor;
2703 child.dimensions.content.width = 0;
2704 child.dimensions.content.height = 0;
2705 continue;
2706 }
2707 let (mut x_off, mut eff_w) =
2708 float_avail(max_w, left_float, right_float, y_cursor);
2709 let (inline_w, inline_h) = child.intrinsic_inline_size(eff_w);
2710 let edges_w = if matches!(child.box_type, BoxType::InlineBlockNode(_)) {
2712 intrinsic_h_edges(child, false)
2713 } else {
2714 0
2715 };
2716 if line_x > 0 && line_x + inline_w + edges_w > eff_w {
2717 y_cursor += if line_h > 0 { line_h } else { line_height_default };
2718 line_x = 0;
2719 line_h = 0;
2720 let recomputed = float_avail(max_w, left_float, right_float, y_cursor);
2721 x_off = recomputed.0;
2722 eff_w = recomputed.1;
2723 }
2724 let _ = eff_w;
2725 child.dimensions.content.x = self.dimensions.content.x + x_off + line_x;
2726 child.dimensions.content.y = self.dimensions.content.y + y_cursor;
2727 child.dimensions.content.width = inline_w;
2728 child.dimensions.content.height = inline_h;
2729
2730 let mut cb = child.dimensions.clone();
2736 cb.content.height = 0;
2737 child.layout(cb);
2738 let (el, er, et, eb) = child.inline_block_edges();
2748 let slot_x = self.dimensions.content.x + x_off + line_x + el;
2749 let slot_y = self.dimensions.content.y + y_cursor + et;
2750 let (dx, dy) = (slot_x - child.dimensions.content.x, slot_y - child.dimensions.content.y);
2751 child.translate(dx, dy);
2752 child.dimensions.content.width = inline_w;
2753 child.dimensions.content.height = inline_h;
2754
2755 line_x += el + inline_w + er;
2756 let outer_h = et + inline_h + eb;
2757 if outer_h > line_h {
2758 line_h = outer_h;
2759 }
2760 }
2761 }
2762 }
2763
2764 if line_x > 0 {
2765 y_cursor += if line_h > 0 { line_h } else { line_height_default };
2766 }
2767 if let Some((_, yb)) = left_float {
2770 y_cursor = y_cursor.max(yb);
2771 }
2772 if let Some((_, yb)) = right_float {
2773 y_cursor = y_cursor.max(yb);
2774 }
2775
2776 if let BoxType::BlockNode(node) = &self.box_type {
2788 let cc = node.value("column-count");
2789 let cw = node.value("column-width");
2790 let shorthand = node.value("columns");
2791 let (sh_w, sh_c) = shorthand
2792 .as_deref()
2793 .map(crate::os_lib::layout::multicol::parse_columns_shorthand)
2794 .unwrap_or((None, None));
2795 let parse_px = |v: &Option<String>| -> Option<i32> {
2796 v.as_deref()
2797 .and_then(|s| s.trim().strip_suffix("px"))
2798 .and_then(|n| n.trim().parse::<f32>().ok())
2799 .filter(|f| f.is_finite() && *f > 0.0)
2800 .map(|f| f as i32)
2801 };
2802 let count = cc
2803 .as_deref()
2804 .and_then(|s| s.trim().parse::<i32>().ok())
2805 .filter(|v| *v > 0)
2806 .or(sh_c);
2807 let width = parse_px(&cw).or(sh_w);
2808 if count.is_some() || width.is_some() {
2809 let gap = node
2812 .value("column-gap")
2813 .as_deref()
2814 .and_then(|s| s.trim().strip_suffix("px"))
2815 .and_then(|n| n.trim().parse::<f32>().ok())
2816 .filter(|f| f.is_finite() && *f >= 0.0)
2817 .map(|f| f as i32)
2818 .unwrap_or(16);
2819 let spec = crate::os_lib::layout::multicol::MultiColSpec { count, width, gap };
2820 let cols = crate::os_lib::layout::multicol::layout_columns(
2821 &spec,
2822 self.dimensions.content.width.max(0),
2823 );
2824 if cols.len() > 1 {
2825 let base_x = self.dimensions.content.x;
2826 let base_y = self.dimensions.content.y;
2827 let flow_idx: alloc::vec::Vec<usize> = self
2832 .children
2833 .iter()
2834 .enumerate()
2835 .filter(|(_, c)| box_float(c) == "none")
2836 .map(|(i, _)| i)
2837 .collect();
2838 let heights: alloc::vec::Vec<i32> = flow_idx
2839 .iter()
2840 .map(|&i| self.children[i].dimensions.margin_box().height)
2841 .collect();
2842 let places =
2843 crate::os_lib::layout::multicol::assign_to_columns(&heights, cols.len());
2844 for (slot, &ci) in flow_idx.iter().enumerate() {
2845 let place = places[slot];
2846 let col = cols[place.col];
2847 let child = &mut self.children[ci];
2848 let dx = base_x + col.x - child.dimensions.content.x
2851 + child.dimensions.margin.left
2852 + child.dimensions.border.left
2853 + child.dimensions.padding.left;
2854 let dy = base_y + place.y - child.dimensions.content.y
2855 + child.dimensions.margin.top
2856 + child.dimensions.border.top
2857 + child.dimensions.padding.top;
2858 child.translate(dx, dy);
2859 child.dimensions.content.width = (col.width
2860 - child.dimensions.margin.left
2861 - child.dimensions.margin.right
2862 - child.dimensions.border.left
2863 - child.dimensions.border.right
2864 - child.dimensions.padding.left
2865 - child.dimensions.padding.right)
2866 .max(0);
2867 }
2868 let totals =
2870 crate::os_lib::layout::multicol::column_heights(&heights, cols.len());
2871 y_cursor = totals.iter().copied().max().unwrap_or(y_cursor);
2872 }
2873 }
2874 }
2875
2876 self.dimensions.content.height = y_cursor;
2878 let cb_height = containing_block.content.height.max(0);
2879 if let Some(h) =
2880 parse_length_value(height_val.clone()).and_then(|v| resolve_length_value(v, cb_height))
2881 {
2882 let vertical_non_content = self.dimensions.border.top
2884 + self.dimensions.border.bottom
2885 + self.dimensions.padding.top
2886 + self.dimensions.padding.bottom;
2887 let content_h = if box_sizing.trim() == "border-box" {
2888 (h - vertical_non_content).max(0)
2889 } else {
2890 h.max(0)
2891 };
2892 self.dimensions.content.height = content_h;
2893 }
2894 if let Some(mh) =
2895 parse_length_value(min_height_val).and_then(|v| resolve_length_value(v, cb_height))
2896 {
2897 self.dimensions.content.height = self.dimensions.content.height.max(mh);
2898 }
2899
2900 if let Some((num, den)) = aspect_ratio_val.as_deref().and_then(parse_aspect_ratio) {
2903 let width_is_auto = matches!(
2904 parse_length_value(width_val.clone()),
2905 Some(LengthValue::Auto) | None
2906 );
2907 let height_is_auto = parse_length_value(height_val.clone()).is_none();
2908 if height_is_auto {
2909 self.dimensions.content.height =
2911 round_f32_to_i32(self.dimensions.content.width as f32 * den / num).max(1);
2912 } else if width_is_auto {
2913 let h = self.dimensions.content.height as f32;
2916 let mut w = round_f32_to_i32(h * num / den).max(1);
2917 if let Some(min_w) = parse_length_value(min_width_val.clone())
2918 .and_then(|v| resolve_length_value(v, available_width))
2919 {
2920 w = w.max(min_w);
2921 }
2922 if let Some(max_w) = parse_length_value(max_width_val.clone())
2923 .and_then(|v| resolve_length_value(v, available_width))
2924 {
2925 w = w.min(max_w);
2926 }
2927 self.dimensions.content.width = w.max(1);
2928 }
2929 }
2930
2931 let (text_align, direction, text_align_last) = match &self.box_type {
2935 BoxType::BlockNode(node) | BoxType::TableCellNode(node) => (
2936 node.value("text-align").unwrap_or_default(),
2937 node.value("direction").unwrap_or_default(),
2938 node.value("text-align-last").unwrap_or_default(),
2939 ),
2940 _ => (String::new(), String::new(), String::new()),
2941 };
2942 let is_rtl = direction.trim().eq_ignore_ascii_case("rtl");
2943 let mut ta = text_align.trim().to_lowercase();
2944 if ta.is_empty() && is_rtl {
2945 ta = String::from("right");
2946 }
2947 ta = match ta.as_str() {
2950 "start" => String::from(if is_rtl { "right" } else { "left" }),
2951 "end" => String::from(if is_rtl { "left" } else { "right" }),
2952 _ => ta,
2953 };
2954 let ta_last_resolved = match text_align_last.trim().to_lowercase().as_str() {
2958 "start" => String::from(if is_rtl { "right" } else { "left" }),
2959 "end" => String::from(if is_rtl { "left" } else { "right" }),
2960 other => String::from(other),
2961 };
2962 let ta_last_effective = if matches!(ta_last_resolved.as_str(), "left" | "center" | "right")
2963 {
2964 Some(ta_last_resolved)
2965 } else {
2966 None
2967 };
2968 if ta == "center" || ta == "right" || ta == "justify" || ta_last_effective.is_some() {
2969 let content_w = self.dimensions.content.width.max(1);
2970 let n = self.children.len();
2971
2972 let mut line_ys: Vec<i32> = Vec::new();
2974 for ci in 0..n {
2975 match self.children[ci].box_type {
2976 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
2977 let cy = self.children[ci].dimensions.content.y
2980 - self.children[ci].inline_block_edges().2;
2981 if !line_ys.contains(&cy) {
2982 line_ys.push(cy);
2983 }
2984 }
2985 _ => {}
2986 }
2987 }
2988 let last_line_y = line_ys.iter().copied().max();
2989
2990 for line_y in line_ys {
2991 let mut line_indices = Vec::new();
2993 let mut total_w = 0i32;
2994 for ci in 0..n {
2995 match self.children[ci].box_type {
2996 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_)
2997 if self.children[ci].dimensions.content.y
2998 - self.children[ci].inline_block_edges().2
2999 == line_y =>
3000 {
3001 line_indices.push(ci);
3002 let (el, er, _, _) = self.children[ci].inline_block_edges();
3006 total_w += el + self.children[ci].dimensions.content.width + er;
3007 }
3008 _ => {}
3009 }
3010 }
3011
3012 let is_last_line = last_line_y == Some(line_y);
3013 let effective_ta = if is_last_line {
3014 ta_last_effective.as_deref().unwrap_or(if ta == "justify" {
3015 if is_rtl { "right" } else { "left" }
3016 } else {
3017 ta.as_str()
3018 })
3019 } else {
3020 ta.as_str()
3021 };
3022
3023 if effective_ta == "justify" {
3024 let k = line_indices.len();
3025 if k > 1 {
3026 let remaining = (content_w - total_w).max(0);
3027 for (i, &ci) in line_indices.iter().enumerate() {
3028 let shift = (remaining * i as i32) / (k as i32 - 1);
3029 self.children[ci].translate(shift, 0);
3031 }
3032 }
3033 } else {
3034 let offset = match effective_ta {
3035 "center" => ((content_w - total_w) / 2).max(0),
3036 "right" => (content_w - total_w).max(0),
3037 _ => 0,
3038 };
3039 if offset > 0 {
3040 for &ci in &line_indices {
3044 self.children[ci].translate(offset, 0);
3045 }
3046 }
3047 }
3048 }
3049 }
3050
3051 self.apply_positioned_offsets();
3053
3054 if let Some(prev) = saved_abs_cb {
3056 abs_cb_set(prev.0, prev.1, prev.2, prev.3);
3057 }
3058 }
3059
3060 fn intrinsic_inline_size_ext(&self, avail_hint: i32, pure: bool) -> (i32, i32) {
3076 if self.is_inline_flex() || self.is_inline_grid() {
3078 if pure {
3079 let (minc, maxc) = self.block_intrinsic_widths();
3080 return (if avail_hint <= 1 { minc } else { maxc }, 0);
3081 }
3082 let w = self.inline_flex_content_width(avail_hint);
3083 let mut probe = self.clone();
3084 probe.layout_as_flex_at(0, 0, w);
3085 return (w, probe.dimensions.content.height.max(0));
3086 }
3087 match &self.box_type {
3088 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
3089 let base = if pure { None } else { Some(avail_hint) };
3092 if let Some(size) = get_replaced_element_size_in(node, base) {
3093 return size;
3094 }
3095 let font_size = node
3096 .value("font-size")
3097 .and_then(|v| parse_font_size_u32(&v))
3098 .unwrap_or(16);
3099 let line_height_val = node.value("line-height");
3100 let line_height = parse_line_height(line_height_val, font_size);
3101
3102 let css_w = node.value("width").and_then(|v| match parse_length_value(Some(v)) {
3110 Some(LengthValue::Px(px)) => Some(px),
3111 Some(LengthValue::Percent(pct)) => {
3112 Some(round_f32_to_i32(avail_hint as f32 * pct))
3113 }
3114 _ => None,
3115 });
3116 let css_h = node
3117 .value("height")
3118 .and_then(|v| match parse_length_value(Some(v)) {
3119 Some(LengthValue::Px(px)) => Some(px),
3120 _ => None,
3121 });
3122
3123 match &node.node.node_type {
3124 NodeType::Text(text) => {
3125 let nowrap = matches!(
3129 node.value("white-space").as_deref(),
3130 Some("nowrap") | Some("pre")
3131 );
3132 let pre_wrap = matches!(
3133 node.value("white-space").as_deref(),
3134 Some("pre-wrap") | Some("pre-line")
3135 );
3136 let break_all = matches!(
3137 node.value("word-break").as_deref(),
3138 Some("break-all")
3139 ) || matches!(
3140 node.value("overflow-wrap").as_deref(),
3141 Some("break-word")
3142 );
3143 let available = if nowrap {
3153 i32::MAX / 2
3154 } else {
3155 css_w.unwrap_or({
3156 if pure {
3157 if avail_hint > 0 {
3159 avail_hint
3160 } else {
3161 800
3162 }
3163 } else if self.dimensions.content.width > 0 {
3164 if avail_hint > 0 {
3171 self.dimensions.content.width.min(avail_hint)
3172 } else {
3173 self.dimensions.content.width
3174 }
3175 } else if avail_hint > 0 {
3176 avail_hint
3177 } else {
3178 800
3179 }
3180 })
3181 };
3182 let ls = parse_letter_spacing_ctx(node.value("letter-spacing").as_deref(), available);
3183 let ws = parse_word_spacing_ctx(node.value("word-spacing").as_deref(), available);
3184 let text = &crate::os_lib::layout::line_break::collapse_white_space(
3187 text,
3188 &node.value("white-space").unwrap_or_default(),
3189 );
3190 let (fam, bold) = text_font_of(node);
3192 let (w, h) = crate::kernel::vector_font::get_vector_string_wrapped_size_family(
3193 text,
3194 font_size,
3195 available as u32,
3196 ls,
3197 ws,
3198 break_all,
3199 pre_wrap,
3200 &fam,
3201 bold,
3202 );
3203 let lines = h as i32 / (font_size as i32 + 4).max(1);
3204 (
3205 css_w.unwrap_or(w as i32),
3206 css_h.unwrap_or(lines * line_height),
3207 )
3208 }
3209 NodeType::Element { tag_name, .. } => {
3210 if tag_name == "br" {
3211 (0, line_height)
3212 } else {
3213 let (content_w, content_h) = if self.children.is_empty() {
3214 (8, line_height)
3215 } else {
3216 let mut total_w = 0i32;
3217 let mut max_h = line_height;
3218 let avail_hint = if matches!(self.box_type, BoxType::InlineBlockNode(_))
3222 && avail_hint > 0
3223 && avail_hint < 1_000_000
3224 {
3225 (avail_hint - intrinsic_h_edges(self, css_w.is_some())).max(1)
3226 } else {
3227 avail_hint
3228 };
3229 for child in &self.children {
3230 let (cw, ch) = child.intrinsic_inline_size(avail_hint);
3240 total_w += cw;
3241 if ch > max_h {
3242 max_h = ch;
3243 }
3244 }
3245 (total_w, max_h)
3246 };
3247 (css_w.unwrap_or(content_w), css_h.unwrap_or(content_h))
3248 }
3249 }
3250 }
3251 }
3252 _ => (0, 0),
3253 }
3254 }
3255
3256 fn intrinsic_inline_size(&self, avail_hint: i32) -> (i32, i32) {
3258 self.intrinsic_inline_size_ext(avail_hint, false)
3259 }
3260
3261 fn layout_inline(&mut self, containing_block: Dimensions, _block_like: bool) {
3262 self.apply_box_model_styles_with_base(containing_block.content.width);
3263
3264 let (width_val, min_width_val, max_width_val, height_val, min_height_val, max_height_val) =
3265 match &self.box_type {
3266 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => (
3267 node.value("width"),
3268 node.value("min-width"),
3269 node.value("max-width"),
3270 node.value("height"),
3271 node.value("min-height"),
3272 node.value("max-height"),
3273 ),
3274 _ => return,
3275 };
3276
3277 let available_width = containing_block.content.width.max(1);
3278 let available_height = containing_block.content.height.max(1);
3279 let (intrinsic_w, intrinsic_h) = self.inline_content_intrinsic_size(available_width);
3280
3281 let mut content_width = match parse_length_value(width_val) {
3282 Some(LengthValue::Auto) | None => intrinsic_w.max(1),
3283 Some(LengthValue::Px(px)) => px.max(1),
3284 Some(LengthValue::Percent(pct)) => {
3285 round_f32_to_i32(available_width as f32 * pct).max(1)
3286 }
3287 Some(LengthValue::MinContent) => {
3288 let (minc, _maxc) = self.block_intrinsic_widths();
3289 minc.max(1)
3290 }
3291 Some(LengthValue::MaxContent) => {
3292 let (_minc, maxc) = self.block_intrinsic_widths();
3293 maxc.max(1)
3294 }
3295 Some(LengthValue::FitContent) => {
3296 let (minc, maxc) = self.block_intrinsic_widths();
3297 available_width.min(maxc).max(minc).max(1)
3298 }
3299 Some(LengthValue::MathExpr(expr)) => {
3300 crate::os_lib::css::eval_css_math(&expr, available_width)
3301 .unwrap_or(intrinsic_w.max(1))
3302 .max(1)
3303 }
3304 };
3305 if let Some(min_width) =
3306 parse_length_value(min_width_val).and_then(|v| resolve_length_value(v, available_width))
3307 {
3308 content_width = content_width.max(min_width);
3309 }
3310 if let Some(max_width) =
3311 parse_length_value(max_width_val).and_then(|v| resolve_length_value(v, available_width))
3312 {
3313 content_width = content_width.min(max_width);
3314 }
3315
3316 let mut content_height = match parse_length_value(height_val) {
3317 Some(LengthValue::Auto)
3318 | Some(LengthValue::MinContent)
3319 | Some(LengthValue::MaxContent)
3320 | Some(LengthValue::FitContent)
3321 | None => intrinsic_h.max(1),
3322 Some(LengthValue::Px(px)) => px.max(1),
3323 Some(LengthValue::Percent(_)) => intrinsic_h.max(1),
3328 Some(LengthValue::MathExpr(expr)) => {
3329 crate::os_lib::css::eval_css_math(&expr, available_height)
3330 .unwrap_or(intrinsic_h.max(1))
3331 .max(1)
3332 }
3333 };
3334 if let Some(min_height) = parse_length_value(min_height_val)
3335 .filter(|v| !matches!(v, LengthValue::Percent(_)))
3336 .and_then(|v| resolve_length_value(v, available_height))
3337 {
3338 content_height = content_height.max(min_height);
3339 }
3340 if let Some(max_height) = parse_length_value(max_height_val)
3341 .filter(|v| !matches!(v, LengthValue::Percent(_)))
3342 .and_then(|v| resolve_length_value(v, available_height))
3343 {
3344 content_height = content_height.min(max_height);
3345 }
3346
3347 self.dimensions.content.width = content_width.max(1);
3348 self.dimensions.content.height = content_height.max(1);
3349 self.dimensions.content.x = containing_block.content.x
3350 + self.dimensions.margin.left
3351 + self.dimensions.border.left
3352 + self.dimensions.padding.left;
3353 self.dimensions.content.y = containing_block.content.y
3354 + containing_block.content.height
3355 + self.dimensions.margin.top
3356 + self.dimensions.border.top
3357 + self.dimensions.padding.top;
3358
3359 let mut child_flow_x = 0i32;
3362 let mut child_flow_y = 0i32;
3363 let mut line_h = 0i32;
3364 let max_w = self.dimensions.content.width.max(1);
3365 let mut line_start_idx = 0usize;
3369
3370 let mut i = 0usize;
3371 while i < self.children.len() {
3372 match self.children[i].box_type {
3373 BoxType::BlockNode(_)
3374 | BoxType::FlexNode(_)
3375 | BoxType::GridNode(_)
3376 | BoxType::TableNode(_)
3377 | BoxType::TableRowGroupNode(_)
3378 | BoxType::TableRowNode(_)
3379 | BoxType::TableCellNode(_) => {
3380 if child_flow_x > 0 {
3381 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
3382 child_flow_y += line_h;
3383 child_flow_x = 0;
3384 line_h = 0;
3385 }
3386 line_start_idx = i + 1;
3387 let child = &mut self.children[i];
3388 let mut cb = self.dimensions.clone();
3389 cb.content.height = child_flow_y;
3390 child.layout(cb);
3391 child_flow_y += child.dimensions.margin_box().height;
3392 i += 1;
3396 }
3397 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
3398 if self.children[i].is_inline_line_break() {
3399 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
3400 child_flow_y += line_h.max(20);
3401 child_flow_x = 0;
3402 line_h = 0;
3403 line_start_idx = i + 1;
3404 i += 1;
3405 continue;
3406 }
3407 let (child_w, child_h) = self.children[i].intrinsic_inline_size(max_w);
3408 if child_flow_x > 0 && child_flow_x + child_w > max_w {
3409 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
3410 child_flow_y += line_h.max(20);
3411 child_flow_x = 0;
3412 line_h = 0;
3413 line_start_idx = i;
3414 }
3415 let child = &mut self.children[i];
3416 child.dimensions.content.x = self.dimensions.content.x + child_flow_x;
3417 child.dimensions.content.y = self.dimensions.content.y + child_flow_y;
3418 child.dimensions.content.width = child_w;
3419 child.dimensions.content.height = child_h;
3420
3421 let cb = child.dimensions.clone();
3428 child.layout(cb);
3429 let (el, er, et, eb) = child.inline_block_edges();
3431 let slot_x = self.dimensions.content.x + child_flow_x + el;
3432 let slot_y = self.dimensions.content.y + child_flow_y + et;
3433 let (dx, dy) = (slot_x - child.dimensions.content.x, slot_y - child.dimensions.content.y);
3434 child.translate(dx, dy);
3435 child.dimensions.content.width = child_w;
3436 child.dimensions.content.height = child_h;
3437
3438 child_flow_x += el + child_w + er;
3439 let outer_h = et + child_h + eb;
3440 if outer_h > line_h {
3441 line_h = outer_h;
3442 }
3443 i += 1;
3444 }
3445 BoxType::AnonymousBlock => {
3446 i += 1;
3447 }
3448 }
3449 }
3450 self.finalize_inline_line_vertical_align(line_start_idx, self.children.len(), line_h, max_w);
3452 }
3453
3454 fn finalize_inline_line_vertical_align(&mut self, start: usize, end: usize, line_h: i32, max_w: i32) {
3460 if line_h <= 0 || end <= start {
3461 return;
3462 }
3463 for idx in start..end {
3464 let Some(child) = self.children.get_mut(idx) else {
3465 continue;
3466 };
3467 if !matches!(
3468 child.box_type,
3469 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_)
3470 ) {
3471 continue;
3472 }
3473 let va = box_style_value(child, "vertical-align").unwrap_or_default();
3474 let offset = match va.trim() {
3475 "middle" => {
3476 let (_, child_h) = child.intrinsic_inline_size(max_w);
3477 (line_h - child_h) / 2
3478 }
3479 "bottom" | "text-bottom" => {
3480 let (_, child_h) = child.intrinsic_inline_size(max_w);
3481 line_h - child_h
3482 }
3483 _ => 0,
3484 };
3485 if offset != 0 {
3486 child.translate_y(offset);
3487 }
3488 }
3489 }
3490
3491 fn resolve_sizing_value(&self, value: Option<String>, base: i32) -> Option<i32> {
3494 match parse_length_value(value)? {
3495 LengthValue::Auto => None,
3496 LengthValue::Px(px) => Some(px),
3497 LengthValue::Percent(pct) => Some(round_f32_to_i32(base as f32 * pct)),
3498 LengthValue::MinContent => Some(self.block_intrinsic_widths().0),
3499 LengthValue::MaxContent => Some(self.block_intrinsic_widths().1),
3500 LengthValue::FitContent => {
3501 let (minc, maxc) = self.block_intrinsic_widths();
3502 Some(maxc.min(base.max(0)).max(minc))
3503 }
3504 LengthValue::MathExpr(expr) => crate::os_lib::css::eval_css_math(&expr, base),
3505 }
3506 }
3507
3508 fn block_intrinsic_widths(&self) -> (i32, i32) {
3512 if matches!(
3515 self.box_type,
3516 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_)
3517 ) && !self.is_inline_flex()
3518 && !self.is_inline_grid()
3519 {
3520 let max_c = self.intrinsic_inline_size_ext(1_000_000, true).0;
3521 let min_c = self.intrinsic_inline_size_ext(1, true).0;
3522 return (min_c.max(0), max_c.max(min_c).max(0));
3523 }
3524 if let BoxType::InlineBlockNode(node) = &self.box_type {
3537 if self.is_inline_grid() {
3538 let cols = parse_grid_tracks(
3539 node.value("grid-template-columns").as_deref().unwrap_or(""),
3540 0,
3541 );
3542 let all_px: Option<i32> = if cols.is_empty() {
3543 None
3544 } else {
3545 cols.iter().try_fold(0i32, |acc, t| match t {
3546 GridTrack::Px(px) => Some(acc + (*px).max(0)),
3547 _ => None,
3548 })
3549 };
3550 if let Some(sum) = all_px {
3551 let (_, cg) = parse_flex_gap(node, 0);
3552 let w = sum + cg.max(0) * (cols.len() as i32 - 1).max(0);
3553 return (w, w);
3554 }
3555 }
3556 }
3557 let flex_node = match &self.box_type {
3558 BoxType::FlexNode(n) => Some(*n),
3559 BoxType::InlineBlockNode(n) if self.is_inline_flex() => Some(*n),
3560 _ => None,
3561 };
3562 if let Some(node) = flex_node {
3563 let dir = node
3564 .value("flex-direction")
3565 .unwrap_or_else(|| alloc::string::String::from("row"))
3566 .trim()
3567 .to_lowercase();
3568 let is_row = !dir.starts_with("column");
3569 let wraps = node
3570 .value("flex-wrap")
3571 .map(|w| w.trim().to_lowercase().starts_with("wrap"))
3572 .unwrap_or(false);
3573 if is_row {
3574 let mut sum_min = 0i32;
3575 let mut sum_max = 0i32;
3576 let mut max_min = 0i32;
3577 for child in &self.children {
3578 let explicit = match parse_length_value(box_style_value(child, "width")) {
3584 Some(LengthValue::Px(px)) => Some(px.max(0)),
3585 _ => None,
3586 };
3587 let (cmin, cmax) = explicit.map(|w| (w, w)).unwrap_or_else(|| child.block_intrinsic_widths());
3588 let (cmin, cmax) = clamp_contribution(child, cmin, cmax);
3589 let edges = intrinsic_h_edges(child, explicit.is_some());
3590 let (cmin, cmax) = (cmin + edges, cmax + edges);
3591 sum_min += cmin;
3592 sum_max += cmax;
3593 max_min = max_min.max(cmin);
3594 }
3595 let (rg, cg) = parse_flex_gap(node, self.dimensions.content.width);
3596 let _ = rg;
3597 let n = self.children.len() as i32;
3598 let gaps = if n > 1 { cg * (n - 1) } else { 0 };
3599 let min_c = if wraps { max_min } else { sum_min + gaps };
3601 return (min_c.max(0), (sum_max + gaps).max(min_c).max(0));
3602 }
3603 }
3604
3605 let mut inline_max = 0i32; let mut overall_max = 0i32;
3609 let mut overall_min = 0i32;
3610 for child in &self.children {
3611 match child.box_type {
3612 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
3613 let cmax = child.intrinsic_inline_size_ext(1_000_000, true).0;
3615 let cmin = child.intrinsic_inline_size_ext(1, true).0;
3616 inline_max += cmax;
3617 overall_min = overall_min.max(cmin);
3618 }
3619 _ => {
3620 overall_max = overall_max.max(inline_max);
3621 inline_max = 0;
3622 let (cmin, cmax, explicit) = match parse_length_value(box_style_value(child, "width")) {
3624 Some(LengthValue::Px(px)) => (px.max(0), px.max(0), true),
3625 _ => {
3626 let (a, b) = child.block_intrinsic_widths();
3627 (a, b, false)
3628 }
3629 };
3630 let (cmin, cmax) = clamp_contribution(child, cmin, cmax);
3631 let edges = intrinsic_h_edges(child, explicit);
3632 let (cmin, cmax) = (cmin + edges, cmax + edges);
3633 overall_min = overall_min.max(cmin);
3634 overall_max = overall_max.max(cmax);
3635 }
3636 }
3637 }
3638 overall_max = overall_max.max(inline_max);
3639 (overall_min.max(0), overall_max.max(overall_min).max(0))
3640 }
3641
3642 fn inline_content_intrinsic_size(&self, available_width: i32) -> (i32, i32) {
3643 match &self.box_type {
3644 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
3645 if let Some(size) = get_replaced_element_size_in(node, Some(available_width)) {
3646 return size;
3647 }
3648 match &node.node.node_type {
3649 NodeType::Text(text) => {
3650 let font_size = node
3651 .value("font-size")
3652 .and_then(|v| parse_font_size_u32(&v))
3653 .unwrap_or(16);
3654 let line_height_val = node.value("line-height");
3655 let line_height = parse_line_height(line_height_val, font_size);
3656 let pre_wrap = matches!(
3657 node.value("white-space").as_deref(),
3658 Some("pre-wrap") | Some("pre-line")
3659 );
3660 let break_all = matches!(
3661 node.value("word-break").as_deref(),
3662 Some("break-all")
3663 ) || matches!(
3664 node.value("overflow-wrap").as_deref(),
3665 Some("break-word")
3666 );
3667 let text = &crate::os_lib::layout::line_break::collapse_white_space(
3670 text,
3671 &node.value("white-space").unwrap_or_default(),
3672 );
3673 let (fam, bold) = text_font_of(node);
3674 let (w, h) = crate::kernel::vector_font::get_vector_string_wrapped_size_family(
3675 text,
3676 font_size,
3677 available_width.max(1) as u32,
3678 0, 0, break_all,
3681 pre_wrap,
3682 &fam,
3683 bold,
3684 );
3685 let lines = h as i32 / (font_size as i32 + 4).max(1);
3686 (w as i32, lines * line_height)
3687 }
3688 NodeType::Element { .. } => {
3689 let mut max_line_w = 0i32;
3690 let mut total_h = 0i32;
3691 let mut current_line_w = 0i32;
3692 let mut current_line_h = 0i32;
3693 for child in &self.children {
3694 if child.is_inline_line_break() {
3695 if current_line_w > max_line_w {
3696 max_line_w = current_line_w;
3697 }
3698 total_h += current_line_h.max(20);
3699 current_line_w = 0;
3700 current_line_h = 0;
3701 continue;
3702 }
3703 let (cw, ch) = child.intrinsic_inline_size(available_width.max(1));
3704 if current_line_w > 0 && current_line_w + cw > available_width.max(1) {
3705 if current_line_w > max_line_w {
3706 max_line_w = current_line_w;
3707 }
3708 total_h += current_line_h.max(20);
3709 current_line_w = 0;
3710 current_line_h = 0;
3711 }
3712 current_line_w += cw;
3713 if ch > current_line_h {
3714 current_line_h = ch;
3715 }
3716 }
3717 if current_line_w > max_line_w {
3718 max_line_w = current_line_w;
3719 }
3720 total_h += current_line_h.max(20);
3721 if total_h == 0 {
3722 let font_size = node
3723 .value("font-size")
3724 .and_then(|v| parse_font_size_u32(&v))
3725 .unwrap_or(16);
3726 return (8, (font_size as i32 + 4).max(12));
3727 }
3728 (max_line_w.max(1), total_h.max(1))
3729 }
3730 }
3731 }
3732 _ => (0, 0),
3733 }
3734 }
3735
3736 fn is_inline_line_break(&self) -> bool {
3737 match &self.box_type {
3738 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
3739 matches!(&node.node.node_type, NodeType::Element { tag_name, .. } if tag_name == "br")
3740 }
3741 _ => false,
3742 }
3743 }
3744
3745 fn apply_box_model_styles_with_base(&mut self, containing_width: i32) {
3746 let node = match &self.box_type {
3751 BoxType::BlockNode(n)
3752 | BoxType::InlineBlockNode(n)
3753 | BoxType::FlexNode(n)
3754 | BoxType::GridNode(n)
3755 | BoxType::TableNode(n) => n,
3756 _ => return,
3757 };
3758 let (m, p, b) = style_box_edges(node, containing_width, &self.dimensions);
3759 self.dimensions.margin = m;
3760 self.dimensions.padding = p;
3761 self.dimensions.border = b;
3762 }
3763
3764 fn apply_box_model_styles(&mut self) {
3765 self.apply_box_model_styles_with_base(0);
3766 }
3767}
3768