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 trim_leading_space: false,
67 };
68
69 let drops_space = match &layout_box.box_type {
72 BoxType::FlexNode(_)
73 | BoxType::GridNode(_)
74 | BoxType::TableNode(_)
75 | BoxType::TableRowGroupNode(_)
76 | BoxType::TableRowNode(_) => true,
77 BoxType::InlineBlockNode(n) => {
78 matches!(resolved_display(n), "inline-flex" | "inline-grid")
79 }
80 _ => false,
81 };
82 for child in &node.children {
83 if drops_space && matches!(&child.node.node_type, NodeType::Text(t) if t.trim().is_empty()) {
84 continue;
85 }
86 if resolved_display(child) != "none" {
87 let mut b = build_layout_tree(child);
89 if drops_space && matches!(&child.node.node_type, NodeType::Text(_)) {
91 b.trim_leading_space = true;
92 }
93 layout_box.children.push(b);
94 }
95 }
96
97 if matches!(layout_box.box_type, BoxType::TableNode(_)) {
100 let mut flattened: Vec<LayoutBox<'a>> = Vec::new();
101 for child in layout_box.children.drain(..) {
102 if matches!(child.box_type, BoxType::TableRowGroupNode(_)) {
103 for row in child.children {
104 flattened.push(row);
105 }
106 } else {
107 flattened.push(child);
108 }
109 }
110 layout_box.children = flattened;
111 }
112
113 layout_box
114}
115
116pub(crate) fn style_box_edges(
120 node: &StyledNode,
121 containing_width: i32,
122 init: &Dimensions,
123) -> (EdgeSizes, EdgeSizes, EdgeSizes) {
124 let pack = |m: &EdgeSizes, p: &EdgeSizes, b: &EdgeSizes| -> [i32; 12] {
126 [m.left, m.right, m.top, m.bottom, p.left, p.right, p.top, p.bottom, b.left, b.right, b.top, b.bottom]
127 };
128 let key = pack(&init.margin, &init.padding, &init.border);
129 if let Ok(cache) = node.edges_cache.try_borrow() {
130 if let Some((_, _, v)) = cache.iter().find(|(cw, k, _)| *cw == containing_width && *k == key) {
131 let e = |i: usize| EdgeSizes { left: v[i], right: v[i + 1], top: v[i + 2], bottom: v[i + 3] };
132 return (e(0), e(4), e(8));
133 }
134 }
135 let r = style_box_edges_uncached(node, containing_width, init);
136 if let Ok(mut cache) = node.edges_cache.try_borrow_mut() {
137 if cache.len() >= 8 {
138 cache.remove(0);
139 }
140 cache.push((containing_width, key, pack(&r.0, &r.1, &r.2)));
141 }
142 r
143}
144
145fn style_box_edges_uncached(
146 node: &StyledNode,
147 containing_width: i32,
148 init: &Dimensions,
149) -> (EdgeSizes, EdgeSizes, EdgeSizes) {
150 let mut margin = init.margin.clone();
151 let mut padding = init.padding.clone();
152 let mut border = init.border.clone();
153 let font_px = node
158 .value("font-size")
159 .and_then(|v| parse_font_size_u32(&v))
160 .unwrap_or(16) as f32;
161 let val = |name: &str| -> Option<String> {
163 node.value_ref(name).map(|s| {
164 if s.as_bytes().windows(2).any(|w| w.eq_ignore_ascii_case(b"em")) {
165 crate::os_lib::web_engine::css_units::resolve_em_lengths(s, font_px)
166 } else {
167 String::from(s)
168 }
169 })
170 };
171 apply_box_value(
172 val("margin"),
173 &mut margin,
174 containing_width,
175 );
176 apply_box_value(
177 val("padding"),
178 &mut padding,
179 containing_width,
180 );
181
182 apply_edge_value(
183 val("margin-top"),
184 &mut margin.top,
185 containing_width,
186 );
187 apply_edge_value(
188 val("margin-right"),
189 &mut margin.right,
190 containing_width,
191 );
192 apply_edge_value(
193 val("margin-bottom"),
194 &mut margin.bottom,
195 containing_width,
196 );
197 apply_edge_value(
198 val("margin-left"),
199 &mut margin.left,
200 containing_width,
201 );
202
203 apply_edge_value(
204 val("padding-top"),
205 &mut padding.top,
206 containing_width,
207 );
208 apply_edge_value(
209 val("padding-right"),
210 &mut padding.right,
211 containing_width,
212 );
213 apply_edge_value(
214 val("padding-bottom"),
215 &mut padding.bottom,
216 containing_width,
217 );
218 apply_edge_value(
219 val("padding-left"),
220 &mut padding.left,
221 containing_width,
222 );
223
224 apply_box_value(
226 val("border-width"),
227 &mut border,
228 containing_width,
229 );
230 if let Some(border_val) = val("border") {
231 if let Some(px) = parse_px_like(&border_val) {
232 border.top = px;
233 border.right = px;
234 border.bottom = px;
235 border.left = px;
236 }
237 }
238 apply_edge_value(
239 val("border-top-width"),
240 &mut border.top,
241 containing_width,
242 );
243 apply_edge_value(
244 val("border-right-width"),
245 &mut border.right,
246 containing_width,
247 );
248 apply_edge_value(
249 val("border-bottom-width"),
250 &mut border.bottom,
251 containing_width,
252 );
253 apply_edge_value(
254 val("border-left-width"),
255 &mut border.left,
256 containing_width,
257 );
258 (margin, padding, border)
259}
260
261pub(crate) fn clamp_contribution(b: &LayoutBox, cmin: i32, cmax: i32, content_based: bool) -> (i32, i32) {
267 let inner = if content_based { border_box_inner_w(b) } else { 0 };
268 let px = |prop: &str| match parse_length_value(box_style_value(b, prop)) {
269 Some(LengthValue::Px(v)) => Some((v - inner).max(0)),
270 _ => None,
271 };
272 let (mut lo, mut hi) = (cmin, cmax);
273 if let Some(mx) = px("max-width") {
274 lo = lo.min(mx);
275 hi = hi.min(mx);
276 }
277 if let Some(mn) = px("min-width") {
278 lo = lo.max(mn);
279 hi = hi.max(mn);
280 }
281 (lo, hi.max(lo))
282}
283
284pub(crate) fn border_box_inner_w(b: &LayoutBox) -> i32 {
291 let node = match &b.box_type {
292 BoxType::BlockNode(n)
293 | BoxType::InlineBlockNode(n)
294 | BoxType::FlexNode(n)
295 | BoxType::GridNode(n)
296 | BoxType::TableNode(n) => *n,
297 BoxType::InlineNode(n) if b.is_replaced_inline() => *n,
298 _ => return 0,
299 };
300 if !node.value("box-sizing").is_some_and(|v| v.trim() == "border-box") {
301 return 0;
302 }
303 let (_, p, bd) = style_box_edges(node, 0, &Dimensions::default());
304 (p.left + p.right + bd.left + bd.right).max(0)
305}
306
307fn is_length_sizing(v: &Option<String>) -> bool {
310 matches!(
311 parse_length_value(v.clone()),
312 Some(LengthValue::Px(_) | LengthValue::Percent(_) | LengthValue::MathExpr(_))
313 )
314}
315
316fn inline_h_margins(b: &LayoutBox) -> i32 {
322 let node = match &b.box_type {
323 BoxType::InlineBlockNode(n) | BoxType::InlineNode(n) => *n,
324 _ => return 0,
325 };
326 let (m, _, _) = style_box_edges(node, 0, &Dimensions::default());
327 m.left.max(0) + m.right.max(0)
328}
329
330pub(crate) fn intrinsic_h_edges(b: &LayoutBox, explicit_width: bool) -> i32 {
331 let node = match &b.box_type {
332 BoxType::BlockNode(n)
333 | BoxType::InlineBlockNode(n)
334 | BoxType::FlexNode(n)
335 | BoxType::GridNode(n)
336 | BoxType::TableNode(n) => *n,
337 BoxType::InlineNode(n) if b.is_replaced_inline() => *n,
338 _ => return 0,
339 };
340 let (m, p, bd) = style_box_edges(node, 0, &Dimensions::default());
342 let margins = m.left.max(0) + m.right.max(0);
343 let border_box = explicit_width
344 && node.value("box-sizing").map(|v| v.trim() == "border-box").unwrap_or(false);
345 if border_box {
346 margins
347 } else {
348 margins + p.left + p.right + bd.left + bd.right
349 }
350}
351
352fn text_font_of(node: &StyledNode) -> (String, u16) {
355 let fam = node.value("font-family").map(|v| String::from(v.trim())).unwrap_or_default();
356 let weight = node
357 .value("font-weight")
358 .map(|v| crate::kernel::font_select::parse_font_weight(&v))
359 .unwrap_or(400);
360 (fam, weight)
361}
362
363fn pseudo_content_size(node: &StyledNode) -> Option<(i32, i32)> {
370 let only_ws = node.node.children.iter().all(|c| match &c.node_type {
371 NodeType::Text(t) => t.trim().is_empty(),
372 _ => false,
373 });
374 if !only_ws {
375 return None;
376 }
377 let part = |which: &str| -> String {
378 if node.value(&alloc::format!("x-{which}-display")).as_deref() == Some("none") {
379 return String::new();
380 }
381 node.value(&alloc::format!("x-{which}-content")).unwrap_or_default()
382 };
383 let mut text = part("before");
384 text.push_str(&part("after"));
385 if text.is_empty() {
386 return None;
387 }
388 let font_size = node
389 .value("font-size")
390 .and_then(|v| parse_font_size_u32(&v))
391 .unwrap_or(16);
392 let fam = node
393 .value("x-before-font-family")
394 .or_else(|| node.value("font-family"))
395 .map(|v| String::from(v.trim()))
396 .unwrap_or_default();
397 let (_, weight) = text_font_of(node);
398 let (w, _) = crate::kernel::vector_font::get_vector_string_wrapped_size_family(
399 &text, font_size, 1_000_000, 0, 0, false, false, false, &fam, weight,
400 );
401 let line_h = parse_line_height(node.value("line-height"), font_size);
402 Some((w as i32, line_h.max(1)))
403}
404
405impl<'a> LayoutBox<'a> {
406 fn explicit_content_height_px(&self, height_val: Option<String>) -> Option<i32> {
411 let Some(LengthValue::Px(px)) = parse_length_value(height_val) else {
412 return None;
413 };
414 let border_box = box_style_value(self, "box-sizing")
415 .map(|v| v.trim() == "border-box")
416 .unwrap_or(false);
417 if border_box {
418 let d = &self.dimensions;
419 Some((px - d.padding.top - d.padding.bottom - d.border.top - d.border.bottom).max(0))
420 } else {
421 Some(px.max(0))
422 }
423 }
424
425 fn h_non_content(&self) -> i32 {
427 let d = &self.dimensions;
428 d.margin.left + d.margin.right + d.border.left + d.border.right + d.padding.left + d.padding.right
429 }
430
431 fn inline_block_edges(&self) -> (i32, i32, i32, i32) {
438 if self.is_replaced_inline() || matches!(self.box_type, BoxType::InlineBlockNode(_)) {
439 let d = &self.dimensions;
440 return (
441 d.margin.left + d.border.left + d.padding.left,
442 d.margin.right + d.border.right + d.padding.right,
443 d.margin.top + d.border.top + d.padding.top,
444 d.margin.bottom + d.border.bottom + d.padding.bottom,
445 );
446 }
447 (0, 0, 0, 0)
448 }
449
450 fn is_replaced_inline(&self) -> bool {
452 match &self.box_type {
453 BoxType::InlineNode(n) => matches!(
454 &n.node.node_type,
455 NodeType::Element { tag_name, .. }
456 if matches!(tag_name.as_str(), "img" | "video" | "canvas" | "iframe" | "embed" | "object")
457 ),
458 _ => false,
459 }
460 }
461
462 fn box_baseline(&self) -> i32 {
469 let (_, _, et, eb) = self.inline_block_edges();
470 let outer_h = et + self.dimensions.content.height + eb;
471 if self.is_replaced_inline() {
472 return outer_h;
473 }
474 match &self.box_type {
475 BoxType::InlineNode(node) => {
476 let font_px = node
477 .value("font-size")
478 .and_then(|v| parse_font_size_u32(&v))
479 .unwrap_or(16) as i32;
480 let ch = self.dimensions.content.height.max(font_px);
481 let leading = (ch - font_px).max(0) / 2;
482 let ascent = (font_px * 85) / 100;
483 et + leading + ascent
484 }
485 BoxType::InlineBlockNode(_) => {
486 let clips = box_style_value(self, "overflow")
487 .is_some_and(|v| v.split_whitespace().any(|t| t != "visible"));
488 if clips {
489 return outer_h;
490 }
491 if let Some(inner_bl) = self.last_inflow_baseline() {
492 et + inner_bl
493 } else {
494 outer_h
495 }
496 }
497 _ => outer_h,
498 }
499 }
500
501 fn last_inflow_baseline(&self) -> Option<i32> {
504 for c in self.children.iter().rev() {
505 if c.is_whitespace_text()
506 || box_float(c) != "none"
507 || matches!(box_position(c), "absolute" | "fixed")
508 {
509 continue;
510 }
511 match &c.box_type {
512 BoxType::InlineNode(_) => {
513 let (_, _, cet, _) = c.inline_block_edges();
514 let cy = (c.dimensions.content.y - cet) - self.dimensions.content.y;
515 return Some(cy + c.box_baseline());
516 }
517 BoxType::InlineBlockNode(_) => {
518 let (_, _, cet, _) = c.inline_block_edges();
519 let cy = (c.dimensions.content.y - cet) - self.dimensions.content.y;
520 return Some(cy + c.box_baseline());
521 }
522 BoxType::BlockNode(_) | BoxType::AnonymousBlock => {
523 if let Some(b) = c.last_inflow_baseline() {
524 let (_, _, cet, _) = c.inline_block_edges();
525 let cy = (c.dimensions.content.y - cet) - self.dimensions.content.y;
526 return Some(cy + b);
527 }
528 }
529 _ => {}
530 }
531 }
532 None
533 }
534
535 fn has_inflow_text(&self) -> bool {
537 if let BoxType::InlineNode(n) = &self.box_type {
538 if let NodeType::Text(t) = &n.node.node_type {
539 return !t.trim().is_empty();
540 }
541 }
542 self.children.iter().any(|c| c.has_inflow_text())
543 }
544
545 fn is_collapsible_space(&self) -> bool {
547 if !self.is_whitespace_text() {
548 return false;
549 }
550 !matches!(
551 box_style_value(self, "white-space").as_deref().map(str::trim),
552 Some("pre" | "pre-wrap" | "break-spaces")
553 )
554 }
555
556 fn collapsed_space_width(&self) -> i32 {
558 let BoxType::InlineNode(node) = &self.box_type else {
559 return 0;
560 };
561 let font_px = node
562 .value("font-size")
563 .and_then(|v| parse_font_size_u32(&v))
564 .unwrap_or(16);
565 let ls = parse_letter_spacing_with_font(node.value("letter-spacing").as_deref(), 0, font_px as f32);
566 let ws = parse_word_spacing_with_font(node.value("word-spacing").as_deref(), 0, font_px as f32);
567 let (fam, weight) = text_font_of(node);
568 crate::kernel::vector_font::get_vector_string_width_family(" ", font_px, &fam, weight, ls, ws) as i32
569 }
570
571 fn is_blank_inline(&self) -> bool {
573 matches!(self.box_type, BoxType::InlineNode(_))
574 && !self.is_replaced_inline()
575 && !self.has_inflow_text()
576 && self.children.iter().all(|c| c.is_whitespace_text() || c.is_blank_inline())
577 }
578
579 fn is_whitespace_text(&self) -> bool {
581 matches!(&self.box_type, BoxType::InlineNode(n)
582 if matches!(&n.node.node_type, NodeType::Text(t) if t.trim().is_empty()))
583 }
584
585 fn text_ends_with_space(&self) -> bool {
587 if let BoxType::InlineNode(n) = &self.box_type {
588 if let NodeType::Text(t) = &n.node.node_type {
589 return t.ends_with(' ') || t.ends_with('\t') || t.ends_with('\n');
590 }
591 }
592 false
593 }
594
595 fn text_starts_with_space(&self) -> bool {
597 if let BoxType::InlineNode(n) = &self.box_type {
598 if let NodeType::Text(t) = &n.node.node_type {
599 return t.starts_with(' ') || t.starts_with('\t') || t.starts_with('\n');
600 }
601 }
602 false
603 }
604
605 fn align_line_baselines(children: &mut [LayoutBox], start: usize, end: usize) -> i32 {
609 let Some(line) = children.get_mut(start..end) else {
610 return 0;
611 };
612
613 let mut baseline_aligned: Vec<(usize, i32, i32)> = Vec::new();
615 for (i, c) in line.iter().enumerate() {
616 if c.is_whitespace_text()
617 || !matches!(c.box_type, BoxType::InlineNode(_) | BoxType::InlineBlockNode(_))
618 || box_float(c) != "none"
619 || matches!(box_position(c), "absolute" | "fixed")
620 {
621 continue;
622 }
623 let va = box_style_value(c, "vertical-align").unwrap_or_default();
624 let va = va.trim();
625 if va.is_empty() || va == "baseline" {
626 let b = c.box_baseline();
627 let (_, _, et, eb) = c.inline_block_edges();
628 let outer_h = et + c.dimensions.content.height + eb;
629 baseline_aligned.push((i, b, outer_h));
630 }
631 }
632
633 let mut max_baseline = 0i32;
634 let mut max_descent = 0i32;
635 if !baseline_aligned.is_empty() {
636 max_baseline = baseline_aligned.iter().map(|&(_, b, _)| b).max().unwrap_or(0);
637 for &(i, b, outer_h) in &baseline_aligned {
638 let descent = outer_h - b;
639 if descent > max_descent {
640 max_descent = descent;
641 }
642 let shift = max_baseline - b;
643 if shift > 0 {
644 if let Some(c) = line.get_mut(i) {
645 c.translate_y(shift);
646 }
647 }
648 }
649 }
650
651 let mut line_h = (max_baseline + max_descent).max(0);
653 for c in line.iter() {
654 if c.is_whitespace_text()
655 || !matches!(c.box_type, BoxType::InlineNode(_) | BoxType::InlineBlockNode(_))
656 || box_float(c) != "none"
657 || matches!(box_position(c), "absolute" | "fixed")
658 {
659 continue;
660 }
661 let (_, _, et, eb) = c.inline_block_edges();
662 let ch = et + c.dimensions.content.height + eb;
663 if ch > line_h {
664 line_h = ch;
665 }
666 }
667
668 if line_h > 0 {
669 for c in line.iter_mut() {
670 if c.is_whitespace_text()
671 || !matches!(c.box_type, BoxType::InlineNode(_) | BoxType::InlineBlockNode(_))
672 || box_float(c) != "none"
673 || matches!(box_position(c), "absolute" | "fixed")
674 {
675 continue;
676 }
677 let va = box_style_value(c, "vertical-align").unwrap_or_default();
678 let va = va.trim();
679 let (_, _, et, eb) = c.inline_block_edges();
680 let ch = et + c.dimensions.content.height + eb;
681 let offset = match va {
682 "middle" => (line_h - ch) / 2,
683 "bottom" | "text-bottom" => line_h - ch,
684 _ => 0,
685 };
686 if offset > 0 {
687 c.translate_y(offset);
688 }
689 }
690 }
691
692 line_h
693 }
694
695 fn is_inline_flex(&self) -> bool {
697 match &self.box_type {
698 BoxType::InlineBlockNode(n) => resolved_display(n).trim() == "inline-flex",
699 _ => false,
700 }
701 }
702
703 fn is_inline_grid(&self) -> bool {
705 match &self.box_type {
706 BoxType::InlineBlockNode(n) => resolved_display(n).trim() == "inline-grid",
707 _ => false,
708 }
709 }
710
711 fn inline_flex_content_width(&self, avail: i32) -> i32 {
714 if let BoxType::InlineBlockNode(node) = &self.box_type {
715 if let Some(w) = parse_length_value(len_value(node, "width"))
716 .and_then(|v| resolve_length_value(v, avail.max(0)))
717 {
718 return w.max(0);
719 }
720 }
721 let (minc, maxc) = self.block_intrinsic_widths();
722 let d = &self.dimensions;
723 let non_content = d.margin.left + d.margin.right + d.border.left + d.border.right
724 + d.padding.left + d.padding.right;
725 maxc.min((avail - non_content).max(0)).max(minc).max(0)
726 }
727
728 fn layout_as_flex_at(&mut self, x: i32, y: i32, content_w: i32) {
731 let BoxType::InlineBlockNode(node) = self.box_type else {
732 return;
733 };
734 let as_grid = self.is_inline_grid();
735 self.box_type = if as_grid { BoxType::GridNode(node) } else { BoxType::FlexNode(node) };
736 let d = &self.dimensions;
737 let left = d.margin.left + d.border.left + d.padding.left;
738 let top = d.margin.top + d.border.top + d.padding.top;
739 let h_non = left + d.padding.right + d.border.right + d.margin.right;
740 let cb = Dimensions {
741 content: Rect { x: x - left, y: y - top, width: content_w + h_non, height: 0 },
742 ..Default::default()
743 };
744 if as_grid {
745 self.layout_grid(cb);
746 } else {
747 self.layout_flex(cb);
748 }
749 self.box_type = BoxType::InlineBlockNode(node);
750 }
751
752 fn layout_inline_flex(&mut self, containing_block: Dimensions) {
756 self.apply_box_model_styles_with_base(containing_block.content.width);
757 let w = self.inline_flex_content_width(containing_block.content.width.max(1));
758 let d = &self.dimensions;
759 let x = containing_block.content.x + d.margin.left + d.border.left + d.padding.left;
760 let y = containing_block.content.y
761 + containing_block.content.height
762 + d.margin.top
763 + d.border.top
764 + d.padding.top;
765 self.layout_as_flex_at(x, y, w);
766 }
767
768 fn translate_y(&mut self, dy: i32) {
772 if dy == 0 {
773 return;
774 }
775 self.dimensions.content.y += dy;
776 for child in &mut self.children {
777 child.translate_y(dy);
778 }
779 }
780
781 fn translate(&mut self, dx: i32, dy: i32) {
787 if dx == 0 && dy == 0 {
788 return;
789 }
790 self.dimensions.content.x += dx;
791 self.dimensions.content.y += dy;
792 for child in &mut self.children {
793 child.translate(dx, dy);
794 }
795 }
796
797 fn layout_captions_at(&mut self, indices: &[usize], base_x: i32, y: i32, width: i32) -> i32 {
800 let mut consumed = 0i32;
801 for &ci in indices {
802 let cap_y = y + consumed;
803 let cap = &mut self.children[ci];
804 let mut cb = Dimensions::default();
805 cb.content.x = base_x;
806 cb.content.y = cap_y;
807 cb.content.height = 0;
808 cb.content.width = width;
809 cap.layout(cb);
810 consumed += cap.dimensions.margin_box().height;
811 }
812 consumed
813 }
814
815 fn layout(&mut self, containing_block: Dimensions) {
816 match &self.box_type {
817 BoxType::BlockNode(_) => self.layout_block(containing_block),
818 BoxType::InlineNode(_) => self.layout_inline(containing_block, false),
819 BoxType::InlineBlockNode(_) if self.is_inline_flex() || self.is_inline_grid() => {
820 self.layout_inline_flex(containing_block)
821 }
822 BoxType::InlineBlockNode(_) => self.layout_inline(containing_block, true),
823 BoxType::FlexNode(_) => self.layout_flex(containing_block),
824 BoxType::GridNode(_) => self.layout_grid(containing_block),
825 BoxType::TableNode(_) => self.layout_table(containing_block),
826 BoxType::TableRowGroupNode(_)
829 | BoxType::TableRowNode(_)
830 | BoxType::TableCellNode(_) => self.layout_block(containing_block),
831 BoxType::AnonymousBlock => {}
832 }
833 }
834
835 fn establish_abs_cb(&self) -> Option<(i32, i32, i32, i32)> {
840 if matches!(
841 box_position(self),
842 "relative" | "absolute" | "fixed"
843 ) {
844 let prev = abs_cb_get();
848 let pb = self.dimensions.padding_box();
849 abs_cb_set(pb.x, pb.y, pb.width, pb.height);
850 Some(prev)
851 } else {
852 None
853 }
854 }
855
856 fn restore_abs_cb(saved: Option<(i32, i32, i32, i32)>) {
858 if let Some(prev) = saved {
859 abs_cb_set(prev.0, prev.1, prev.2, prev.3);
860 }
861 }
862
863 fn child_is_out_of_flow(child: &LayoutBox) -> bool {
865 matches!(box_position(child), "absolute" | "fixed")
866 }
867
868 fn apply_positioned_offsets(&mut self) {
876 let cw = self.dimensions.content.width;
877 let ch = self.dimensions.content.height;
878 for child in &mut self.children {
879 match box_position(child) {
880 "relative" => {
881 let dx = box_inset_len(child, "left", cw)
882 .or_else(|| box_inset_len(child, "right", cw).map(|r| -r))
883 .unwrap_or(0);
884 let dy = box_inset_len(child, "top", ch)
885 .or_else(|| box_inset_len(child, "bottom", ch).map(|b| -b))
886 .unwrap_or(0);
887 if dx != 0 || dy != 0 {
888 shift_layout_box(child, dx, dy);
889 }
890 }
891 pos @ ("absolute" | "fixed") => {
892 let (bx, by, bw, bh) = if pos == "fixed" {
894 let (vx, vy) = vp_origin_get();
895 (
896 vx,
897 vy,
898 VIEWPORT_HINT_W.load(AtomicOrdering::Relaxed),
899 VIEWPORT_HINT_H.load(AtomicOrdering::Relaxed),
900 )
901 } else {
902 abs_cb_get()
903 };
904 let is_pct = |name: &str| {
911 matches!(
912 parse_length_value(box_style_value(child, name)),
913 Some(LengthValue::Percent(_))
914 )
915 };
916 let need_pct = is_pct("width") || is_pct("height");
920 let block_like = matches!(
921 child.box_type,
922 BoxType::BlockNode(_) | BoxType::FlexNode(_) | BoxType::GridNode(_)
923 );
924 let h_non = child.h_non_content();
925 let target_w: Option<i32> = if block_like && !box_has_explicit(child, "width") {
926 let l = box_inset_len(child, "left", bw);
927 let r = box_inset_len(child, "right", bw);
928 Some(match (l, r) {
929 (Some(l), Some(r)) => (bw - l - r - h_non).max(0),
930 _ => {
931 let (minc, maxc) = child.block_intrinsic_widths();
932 let avail = (bw - l.or(r).unwrap_or(0) - h_non).max(0);
933 maxc.min(avail).max(minc).max(0)
934 }
935 })
936 } else {
937 None
938 };
939 let target_h: Option<i32> = if block_like && !box_has_explicit(child, "height") {
943 match (box_inset_len(child, "top", bh), box_inset_len(child, "bottom", bh)) {
944 (Some(t), Some(b)) => {
945 let mb0 = child.dimensions.margin_box();
946 let v_non = mb0.height - child.dimensions.content.height;
947 Some((bh - t - b - v_non).max(0))
948 }
949 _ => None,
950 }
951 } else {
952 None
953 };
954 let is_block = matches!(child.box_type, BoxType::BlockNode(_));
955 let need_w = target_w.is_some_and(|w| w != child.dimensions.content.width);
956 let need_h = is_block && target_h.is_some_and(|h| h != child.dimensions.content.height);
958 if need_pct || need_w || need_h {
959 let mb0 = child.dimensions.margin_box();
960 let _pct_h = if need_pct { Some(PctBaseHGuard::set(bh)) } else { None };
961 let width = match target_w {
962 Some(w) => w + h_non,
963 None if need_pct => bw,
964 None => mb0.width,
965 };
966 if need_h {
967 if let Some(h) = target_h {
968 FORCED_BLOCK_H.store(h, AtomicOrdering::Relaxed);
969 }
970 }
971 let cb = Dimensions {
972 content: Rect { x: mb0.x, y: mb0.y, width, height: 0 },
973 ..Default::default()
974 };
975 child.layout(cb);
976 FORCED_BLOCK_H.store(-1, AtomicOrdering::Relaxed);
977 }
978 if let Some(h) = target_h {
979 if h != child.dimensions.content.height {
980 child.dimensions.content.height = h;
981 }
982 }
983 let mb = child.dimensions.margin_box();
984 let tx = if let Some(l) = box_inset_len(child, "left", bw) {
985 bx + l
986 } else if let Some(r) = box_inset_len(child, "right", bw) {
987 bx + bw - r - mb.width
988 } else {
989 bx
990 };
991 let ty = if let Some(t) = box_inset_len(child, "top", bh) {
992 by + t
993 } else if let Some(b) = box_inset_len(child, "bottom", bh) {
994 by + bh - b - mb.height
995 } else {
996 by
997 };
998 shift_layout_box(child, tx - mb.x, ty - mb.y);
999 }
1000 _ => {}
1001 }
1002
1003 let (txx, tyy) = box_translate(child);
1006 if txx != 0 || tyy != 0 {
1007 shift_layout_box(child, txx, tyy);
1008 }
1009 }
1010 }
1011
1012 fn prepare_child_subgrid_ctx(
1014 child: &LayoutBox,
1015 col_widths: &[i32],
1016 col_gap_eff: i32,
1017 c: usize,
1018 cs: usize,
1019 cell_w: i32,
1020 ) -> Option<SubgridContext> {
1021 let child_node = match &child.box_type {
1022 BoxType::GridNode(n) => Some(*n),
1023 BoxType::InlineBlockNode(n) if child.is_inline_grid() => Some(*n),
1024 _ => None,
1025 }?;
1026 let cols_val = child_node.value("grid-template-columns").unwrap_or_default();
1027 if !is_subgrid(&cols_val) {
1028 return None;
1029 }
1030 let end_c = (c + cs).min(col_widths.len());
1031 let sub_cols = if c < end_c {
1032 col_widths[c..end_c].to_vec()
1033 } else {
1034 alloc::vec![cell_w]
1035 };
1036 Some(SubgridContext {
1037 col_widths: sub_cols,
1038 col_gap: col_gap_eff,
1039 })
1040 }
1041
1042 fn layout_grid(&mut self, containing_block: Dimensions) {
1043 self.apply_box_model_styles_with_base(containing_block.content.width);
1044
1045 let node = match &self.box_type {
1046 BoxType::GridNode(n) => *n,
1047 _ => return,
1048 };
1049 let avail_w = containing_block.content.width.max(1);
1050
1051 let inner_non_content = self.dimensions.border.left
1053 + self.dimensions.border.right
1054 + self.dimensions.padding.left
1055 + self.dimensions.padding.right;
1056 let h_non_content =
1057 self.dimensions.margin.left + self.dimensions.margin.right + inner_non_content;
1058 let width_val = len_value(node, "width");
1059 let box_sizing = node.value("box-sizing").unwrap_or_default();
1060 let h_auto = margin_is_h_auto(node);
1061 let explicit_w =
1062 !matches!(parse_length_value(width_val.clone()), Some(LengthValue::Auto) | None);
1063 let mut content_w = match parse_length_value(width_val) {
1064 Some(LengthValue::Auto) | None => (avail_w - h_non_content).max(1),
1065 Some(LengthValue::Px(px)) => px.max(1),
1066 Some(LengthValue::Percent(pct)) => round_f32_to_i32(avail_w as f32 * pct).max(1),
1067 Some(LengthValue::MinContent) => {
1068 let (minc, _maxc) = self.block_intrinsic_widths();
1069 minc.max(1)
1070 }
1071 Some(LengthValue::MaxContent) => {
1072 let (_minc, maxc) = self.block_intrinsic_widths();
1073 maxc.max(1)
1074 }
1075 Some(LengthValue::FitContent) => {
1076 let (minc, maxc) = self.block_intrinsic_widths();
1077 let avail = (avail_w - h_non_content).max(0);
1078 avail.min(maxc).max(minc).max(1)
1079 }
1080 Some(LengthValue::FitContentWith(lim_box)) => {
1081 let avail = (avail_w - h_non_content).max(0);
1082 let limit = resolve_length_value_with_font(*lim_box, avail, self.font_size())
1083 .unwrap_or(avail);
1084 let (minc, maxc) = self.block_intrinsic_widths();
1085 maxc.min(limit.max(minc)).max(1)
1086 }
1087 Some(LengthValue::MathExpr(expr)) => {
1088 crate::os_lib::css::eval_css_math_with_font(&expr, avail_w, self.font_size())
1089 .unwrap_or((avail_w - h_non_content).max(1))
1090 .max(1)
1091 }
1092 };
1093 if explicit_w && box_sizing.trim() == "border-box" {
1097 content_w = (content_w - inner_non_content).max(1);
1098 }
1099 let bb_inner = if box_sizing.trim() == "border-box" { inner_non_content } else { 0 };
1101 let min_val = len_value(node, "min-width");
1102 if let Some(min_w) = self.resolve_sizing_value(min_val.clone(), avail_w) {
1103 let min_w = if is_length_sizing(&min_val) { (min_w - bb_inner).max(0) } else { min_w };
1104 content_w = content_w.max(min_w);
1105 }
1106 let max_val = len_value(node, "max-width");
1107 if let Some(max_w) = self.resolve_sizing_value(max_val.clone(), avail_w) {
1108 let max_w = if is_length_sizing(&max_val) { (max_w - bb_inner).max(0) } else { max_w };
1109 content_w = content_w.min(max_w);
1110 }
1111 let content_w = content_w.max(1);
1112 self.dimensions.content.width = content_w;
1113 if h_auto {
1114 let free = (avail_w - content_w - inner_non_content).max(0);
1115 self.dimensions.margin.left = free / 2;
1116 self.dimensions.margin.right = free / 2;
1117 }
1118
1119 let flow_y = containing_block.content.y
1120 + containing_block.content.height
1121 + self.dimensions.margin.top;
1122 self.dimensions.content.x = containing_block.content.x
1123 + self.dimensions.margin.left
1124 + self.dimensions.border.left
1125 + self.dimensions.padding.left;
1126 self.dimensions.content.y =
1127 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
1128
1129 let (row_gap, col_gap) = {
1130 let gpx = |s: &str| -> i32 {
1132 match parse_length_value(Some(String::from(s.trim()))) {
1133 Some(LengthValue::Px(p)) => p,
1134 Some(LengthValue::Percent(pct)) => {
1135 round_f32_to_i32(content_w as f32 * pct).max(0)
1136 }
1137 _ => 0,
1138 }
1139 };
1140
1141 let gap_all = len_value(node, "gap").unwrap_or_default();
1142 if !gap_all.is_empty() {
1143 let toks: Vec<&str> = gap_all.split_whitespace().collect();
1144 let rg = gpx(toks.first().copied().unwrap_or("0"));
1145 let cg = gpx(toks
1146 .get(1)
1147 .copied()
1148 .unwrap_or(toks.first().copied().unwrap_or("0")));
1149 (rg, cg)
1150 } else {
1151 let rg = node
1152 .value("row-gap")
1153 .or_else(|| node.value("grid-row-gap"))
1154 .map(|v| gpx(&v))
1155 .unwrap_or(0);
1156 let cg = node
1157 .value("column-gap")
1158 .or_else(|| node.value("grid-column-gap"))
1159 .map(|v| gpx(&v))
1160 .unwrap_or(0);
1161 (rg, cg)
1162 }
1163 };
1164
1165 let t_cols = node.value("grid-template-columns").unwrap_or_default();
1168 let (mut col_widths, col_gap) = if is_subgrid(&t_cols) {
1169 if let Some(sub_ctx) = get_subgrid_ctx() {
1170 let has_own_gap = node.value("gap").is_some()
1171 || node.value("column-gap").is_some()
1172 || node.value("grid-column-gap").is_some();
1173 let eff_gap = if has_own_gap { col_gap } else { sub_ctx.col_gap };
1174 (sub_ctx.col_widths, eff_gap)
1175 } else {
1176 (alloc::vec![content_w], col_gap)
1177 }
1178 } else {
1179 (
1180 parse_grid_template_columns_gap(&t_cols, content_w, col_gap),
1181 col_gap,
1182 )
1183 };
1184 let n_cols = col_widths.len().max(1);
1185
1186 let auto_col_tracks = parse_grid_tracks(
1192 node.value("grid-auto-columns").as_deref().unwrap_or(""),
1193 content_w,
1194 );
1195 let auto_col_fallback_px = col_widths.last().copied().unwrap_or(100).max(1);
1196 let resolve_auto_col_px = |idx: usize| -> i32 {
1197 if auto_col_tracks.is_empty() {
1198 return auto_col_fallback_px;
1199 }
1200 match auto_col_tracks[idx % auto_col_tracks.len()] {
1201 GridTrack::Px(px) => px.max(1),
1202 GridTrack::MinMax { min_px, .. } => min_px.max(1),
1203 _ => auto_col_fallback_px,
1204 }
1205 };
1206
1207 let template_areas =
1209 parse_grid_template_areas(node.value("grid-template-areas").as_deref().unwrap_or(""));
1210
1211 let row_base = match parse_length_value(len_value(node, "height")) {
1218 Some(LengthValue::Px(px)) => px.max(0),
1219 Some(LengthValue::Percent(pct)) => {
1220 round_f32_to_i32(containing_block.content.height as f32 * pct).max(0)
1221 }
1222 _ => 0,
1223 };
1224
1225 let row_tracks = parse_grid_tracks(
1227 node.value("grid-template-rows").as_deref().unwrap_or(""),
1228 row_base,
1229 );
1230
1231 let auto_row_tracks = parse_grid_tracks(
1233 node.value("grid-auto-rows").as_deref().unwrap_or(""),
1234 row_base,
1235 );
1236
1237 let eff_row_track = |r: usize| -> Option<GridTrack> {
1239 if let Some(t) = row_tracks.get(r) {
1240 return Some(*t);
1241 }
1242 if auto_row_tracks.is_empty() {
1243 return None;
1244 }
1245 let implicit_idx = r - row_tracks.len();
1246 auto_row_tracks
1247 .get(implicit_idx % auto_row_tracks.len())
1248 .copied()
1249 };
1250
1251 let justify_items = node
1254 .value("justify-items")
1255 .unwrap_or_else(|| String::from("stretch"))
1256 .trim()
1257 .to_lowercase();
1258 let align_items = node
1259 .value("align-items")
1260 .unwrap_or_else(|| String::from("stretch"))
1261 .trim()
1262 .to_lowercase();
1263
1264 let auto_flow_col = node
1268 .value("grid-auto-flow")
1269 .map(|v| v.to_lowercase().contains("column"))
1270 .unwrap_or(false);
1271 let flow_rows = row_tracks.len().max(1);
1273 let mut occ: Vec<Vec<bool>> = Vec::new();
1274 let mut n_cols_dyn = n_cols;
1280 let n_children = self.children.len();
1282 let mut placement: Vec<Option<(usize, usize, usize, usize)>> =
1283 alloc::vec![None; n_children];
1284 let mut cursor_r = 0usize;
1285 let mut cursor_c = 0usize;
1286 for (idx, child) in self.children.iter().enumerate() {
1287 if Self::child_is_out_of_flow(child) {
1288 continue;
1289 }
1290 let (mut col_start, mut col_span_raw) =
1291 grid_axis_placement(child, "grid-column", "grid-column-start", "grid-column-end");
1292 let (mut row_start, mut row_span_raw) =
1293 grid_axis_placement(child, "grid-row", "grid-row-start", "grid-row-end");
1294 if col_start.is_none() && row_start.is_none() {
1297 if let Some(area_name) = box_style_value(child, "grid-area") {
1298 let area_name = area_name.trim();
1299 if !area_name.is_empty() {
1300 if let Some((ar, ac, arspan, acspan)) =
1301 find_grid_area(&template_areas, area_name)
1302 {
1303 row_start = Some(ar);
1304 col_start = Some(ac);
1305 row_span_raw = arspan;
1306 col_span_raw = acspan;
1307 }
1308 }
1309 }
1310 }
1311 let col_span = col_span_raw.min(n_cols).max(1);
1312 let row_span = row_span_raw.max(1);
1313
1314 let (r, c) = match (row_start, col_start) {
1316 (Some(r), Some(c)) => (r, c.min(n_cols.saturating_sub(1))),
1317 (Some(r), None) => {
1318 grid_ensure_row(&mut occ, r + row_span - 1, n_cols);
1320 let mut cc = 0usize;
1321 while cc < n_cols && !grid_cells_free(&occ, r, cc, row_span, col_span, n_cols) {
1322 cc += 1;
1323 }
1324 (r, cc.min(n_cols.saturating_sub(1)))
1325 }
1326 (None, Some(c)) => {
1327 let c = c.min(n_cols.saturating_sub(1));
1329 let mut rr = cursor_r;
1330 loop {
1331 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols);
1332 if grid_cells_free(&occ, rr, c, row_span, col_span, n_cols) {
1333 break;
1334 }
1335 rr += 1;
1336 }
1337 (rr, c)
1338 }
1339 (None, None) => {
1340 if auto_flow_col {
1341 let mut rr = cursor_r;
1348 let mut cc = cursor_c;
1349 loop {
1350 if rr + row_span > flow_rows {
1351 cc += 1;
1352 rr = 0;
1353 }
1354 if cc + col_span > n_cols_dyn {
1355 n_cols_dyn = cc + col_span;
1356 grid_ensure_col(&mut occ, n_cols_dyn);
1357 }
1358 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols_dyn);
1359 if grid_cells_free(&occ, rr, cc, row_span, col_span, n_cols_dyn) {
1360 break;
1361 }
1362 rr += 1;
1363 }
1364 cursor_r = rr + row_span;
1365 cursor_c = cc;
1366 (rr, cc)
1367 } else {
1368 let mut rr = cursor_r;
1370 let mut cc = cursor_c;
1371 loop {
1372 if cc + col_span > n_cols {
1373 rr += 1;
1374 cc = 0;
1375 }
1376 grid_ensure_row(&mut occ, rr + row_span - 1, n_cols);
1377 if grid_cells_free(&occ, rr, cc, row_span, col_span, n_cols) {
1378 break;
1379 }
1380 cc += 1;
1381 }
1382 cursor_r = rr;
1383 cursor_c = cc + col_span;
1384 (rr, cc)
1385 }
1386 }
1387 };
1388 grid_ensure_row(&mut occ, r + row_span - 1, n_cols_dyn);
1389 grid_mark(&mut occ, r, c, row_span, col_span, n_cols_dyn);
1390 placement[idx] = Some((r, c, row_span, col_span));
1391 }
1392
1393 while col_widths.len() < n_cols_dyn {
1396 let idx = col_widths.len() - n_cols;
1397 col_widths.push(resolve_auto_col_px(idx));
1398 }
1399
1400 {
1406 let template = node.value("grid-template-columns").unwrap_or_default();
1407 let tracks = parse_grid_tracks(&template, content_w);
1408 let has_intrinsic = tracks.iter().any(|t| {
1409 matches!(
1410 t,
1411 GridTrack::MinContent | GridTrack::MaxContent | GridTrack::FitContent(_)
1412 )
1413 });
1414 let flex = flex_factors(&template, col_widths.len());
1415 let has_flex = flex.iter().any(|f| f.is_some());
1416 if has_intrinsic || has_flex {
1417 let mut mins = alloc::vec![0i32; col_widths.len()];
1418 let mut maxs = alloc::vec![0i32; col_widths.len()];
1419 for (idx, child) in self.children.iter().enumerate() {
1420 let Some((_, c, _, cs)) = placement.get(idx).copied().flatten() else {
1421 continue;
1422 };
1423 if cs != 1 || c >= mins.len() {
1424 continue;
1425 }
1426 let item_node = match &child.box_type {
1427 BoxType::BlockNode(n)
1428 | BoxType::InlineNode(n)
1429 | BoxType::InlineBlockNode(n)
1430 | BoxType::FlexNode(n)
1431 | BoxType::GridNode(n)
1432 | BoxType::TableNode(n)
1433 | BoxType::TableRowGroupNode(n)
1434 | BoxType::TableRowNode(n)
1435 | BoxType::TableCellNode(n) => Some(*n),
1436 BoxType::AnonymousBlock => None,
1437 };
1438 let col_w = col_widths.get(c).copied().unwrap_or(content_w);
1439 let mut item_min = 0;
1440 let mut item_max = 0;
1441 if let Some(n) = item_node {
1442 let (m, p, b) = style_box_edges(n, col_w, &Dimensions::default());
1443 let edges = m.left + m.right + p.left + p.right + b.left + b.right;
1444 let is_border_box = n
1445 .value("box-sizing")
1446 .is_some_and(|s| s.trim().eq_ignore_ascii_case("border-box"));
1447 let bb_sub = if is_border_box {
1448 (p.left + p.right + b.left + b.right).max(0)
1449 } else {
1450 0
1451 };
1452 let definite = |name: &str| {
1453 parse_length_value(len_value(n, name))
1454 .and_then(|v| resolve_length_value(v, col_w))
1455 .map(|px| (px - bb_sub).max(0))
1456 };
1457 let scroll_container = ["overflow", "overflow-x"].iter().any(|k| {
1458 n.value(k).is_some_and(|v| {
1459 matches!(v.trim(), "hidden" | "scroll" | "auto")
1460 })
1461 });
1462 item_min = if let Some(mw) = definite("min-width") {
1468 mw + edges
1469 } else if scroll_container {
1470 0
1471 } else if let Some(w) = definite("width") {
1472 w + edges
1473 } else {
1474 child.block_intrinsic_widths().0 + edges
1475 };
1476 item_max = if let Some(w) = definite("width") {
1477 w + edges
1478 } else {
1479 child.block_intrinsic_widths().1 + edges
1480 };
1481 if let Some(mx) = definite("max-width") {
1482 item_min = item_min.min(mx + edges);
1483 item_max = item_max.min(mx + edges);
1484 }
1485 }
1486 mins[c] = mins[c].max(item_min);
1487 maxs[c] = maxs[c].max(item_max);
1488 }
1489
1490 for (c, track) in tracks.iter().enumerate() {
1492 if c >= col_widths.len() {
1493 break;
1494 }
1495 match track {
1496 GridTrack::MinContent => {
1497 col_widths[c] = mins[c].max(1);
1498 }
1499 GridTrack::MaxContent => {
1500 col_widths[c] = maxs[c].max(1);
1501 }
1502 GridTrack::FitContent(limit) => {
1503 col_widths[c] = maxs[c].min(*limit).max(mins[c]).max(1);
1504 }
1505 _ => {}
1506 }
1507 }
1508
1509 if has_flex {
1511 let n_cols_total = col_widths.len();
1512 let gaps_total = col_gap * (n_cols_total as i32 - 1).max(0);
1513 let non_flex_sum: i32 = (0..n_cols_total)
1514 .filter(|&i| flex.get(i).copied().flatten().is_none())
1515 .map(|i| col_widths[i])
1516 .sum();
1517 let flex_avail = (content_w - non_flex_sum - gaps_total).max(0);
1518 let flex_sum: f32 = flex.iter().filter_map(|&f| f).sum();
1519 if flex_sum > 0.0 {
1520 for (i, f) in flex.iter().enumerate() {
1521 if let Some(factor) = f {
1522 col_widths[i] = round_f32_to_i32(flex_avail as f32 * factor / flex_sum).max(1);
1523 }
1524 }
1525 }
1526 col_widths = expand_flexible_tracks(&col_widths, &flex, &mins);
1527 }
1528 }
1529 }
1530
1531 let n_cols_eff = col_widths.len();
1536 let cols_total: i32 =
1537 col_widths.iter().sum::<i32>() + (n_cols_eff.saturating_sub(1) as i32) * col_gap;
1538 let cols_free = (content_w - cols_total).max(0);
1539 let justify_content_grid = node
1540 .value("justify-content")
1541 .unwrap_or_default()
1542 .trim()
1543 .to_lowercase();
1544 let (col_start_offset, col_extra_gap) = if cols_free > 0 && n_cols_eff > 0 {
1545 match justify_content_grid.as_str() {
1546 "end" | "flex-end" => (cols_free, 0),
1547 "center" => (cols_free / 2, 0),
1548 "space-between" if n_cols_eff > 1 => (0, cols_free / (n_cols_eff as i32 - 1)),
1549 "space-around" if n_cols_eff > 0 => {
1550 let g = cols_free / n_cols_eff as i32;
1551 (g / 2, g)
1552 }
1553 "space-evenly" if n_cols_eff > 0 => {
1554 let g = cols_free / (n_cols_eff as i32 + 1);
1555 (g, g)
1556 }
1557 _ => (0, 0),
1558 }
1559 } else {
1560 (0, 0)
1561 };
1562 let col_gap_eff = col_gap + col_extra_gap;
1563
1564 let n_rows = occ.len().max(row_tracks.len());
1565
1566 let mut row_heights: Vec<i32> = alloc::vec![0; n_rows];
1569 for (r, h) in row_heights.iter_mut().enumerate() {
1571 match eff_row_track(r) {
1572 Some(GridTrack::Px(px)) => *h = px.max(0),
1573 Some(GridTrack::MinMax { min_px, .. }) => *h = min_px.max(0),
1574 _ => {}
1575 }
1576 }
1577 let base_x = self.dimensions.content.x;
1580 let base_y = self.dimensions.content.y;
1581 for (idx, child) in self.children.iter_mut().enumerate() {
1582 if Self::child_is_out_of_flow(child) {
1583 continue;
1584 }
1585 let Some((_r, c, _rs, cs)) = placement[idx] else {
1586 continue;
1587 };
1588 let cell_w = grid_span_width(&col_widths, col_gap_eff, c, cs);
1589 let cell_cb = Dimensions {
1590 content: Rect {
1591 x: base_x + col_start_offset + grid_col_x(&col_widths, col_gap_eff, c),
1592 y: base_y,
1593 width: cell_w,
1594 height: 0,
1595 },
1596 ..Dimensions::default()
1597 };
1598 let sub_ctx = Self::prepare_child_subgrid_ctx(child, &col_widths, col_gap_eff, c, cs, cell_w);
1599 let is_sub = sub_ctx.is_some();
1600 let prev_ctx = if is_sub {
1601 set_subgrid_ctx(sub_ctx)
1602 } else {
1603 None
1604 };
1605 child.layout(cell_cb);
1606 if is_sub {
1607 set_subgrid_ctx(prev_ctx);
1608 }
1609 if let Some((num, den)) = box_aspect_ratio(child) {
1610 if num > 0.0 && den > 0.0 && !box_has_explicit(child, "height") {
1611 let w = child.dimensions.content.width as f32;
1612 let h = round_f32_to_i32(w * den / num).max(1);
1613 child.dimensions.content.height = h;
1614 }
1615 }
1616 }
1617 for (idx, child) in self.children.iter().enumerate() {
1619 let Some((r, _c, rs, _cs)) = placement[idx] else {
1620 continue;
1621 };
1622 if rs != 1 {
1623 continue;
1624 }
1625 let explicit_px = matches!(eff_row_track(r), Some(GridTrack::Px(_)));
1626 if explicit_px {
1627 continue;
1628 }
1629 let ch = child.dimensions.margin_box().height;
1630 if let Some(h) = row_heights.get_mut(r) {
1631 *h = (*h).max(ch);
1632 }
1633 if let Some(GridTrack::MinMax {
1635 max_px,
1636 max_is_fr,
1637 max_is_auto,
1638 ..
1639 }) = eff_row_track(r)
1640 {
1641 if !max_is_fr && !max_is_auto {
1642 if let Some(h) = row_heights.get_mut(r) {
1643 *h = (*h).min(max_px);
1644 }
1645 }
1646 }
1647 }
1648 for (idx, child) in self.children.iter().enumerate() {
1650 let Some((r, _c, rs, _cs)) = placement[idx] else {
1651 continue;
1652 };
1653 if rs <= 1 {
1654 continue;
1655 }
1656 let ch = child.dimensions.margin_box().height;
1657 let mut covered = 0i32;
1658 for rr in r..(r + rs).min(row_heights.len()) {
1659 covered += row_heights.get(rr).copied().unwrap_or(0);
1660 }
1661 covered += (rs.saturating_sub(1) as i32) * row_gap;
1662 if ch > covered {
1663 let last = (r + rs - 1).min(row_heights.len().saturating_sub(1));
1664 if let Some(h) = row_heights.get_mut(last) {
1665 *h += ch - covered;
1666 }
1667 }
1668 }
1669
1670 let mut container_h_explicit: Option<i32> = None;
1672 let height_val = len_value(node, "height");
1673 if let Some(h) = parse_length_value(height_val.clone())
1674 .and_then(|v| resolve_length_value(v, containing_block.content.height.max(0)))
1675 {
1676 let vert_non = self.dimensions.border.top
1677 + self.dimensions.border.bottom
1678 + self.dimensions.padding.top
1679 + self.dimensions.padding.bottom;
1680 container_h_explicit = Some(if box_sizing.trim() == "border-box" {
1681 (h - vert_non).max(0)
1682 } else {
1683 h.max(0)
1684 });
1685 }
1686 if let Some(ch) = container_h_explicit {
1687 let track_fr = |t: &GridTrack| -> f32 {
1689 match t {
1690 GridTrack::Fr(f) => *f,
1691 GridTrack::MinMax {
1692 max_fr, max_is_fr, ..
1693 } if *max_is_fr => *max_fr,
1694 _ => 0.0,
1695 }
1696 };
1697 let mut fr_total = 0.0f32;
1699 for r in 0..n_rows {
1700 if let Some(t) = eff_row_track(r) {
1701 fr_total += track_fr(&t);
1702 }
1703 }
1704 if fr_total > 0.0 {
1705 let mut used = 0i32;
1706 for (r, h) in row_heights.iter().enumerate() {
1707 let is_fr = eff_row_track(r).map(|t| track_fr(&t)).unwrap_or(0.0) > 0.0;
1708 if !is_fr {
1709 used += *h;
1710 }
1711 }
1712 used += (n_rows.saturating_sub(1) as i32) * row_gap;
1713 let free = (ch - used).max(0);
1714 for r in 0..n_rows {
1715 let Some(t) = eff_row_track(r) else { continue };
1716 let f = track_fr(&t);
1717 if f > 0.0 {
1718 let share = round_f32_to_i32(free as f32 * f / fr_total);
1719 let floor = match t {
1721 GridTrack::MinMax { min_px, .. } => min_px,
1722 _ => 0,
1723 };
1724 if let Some(rh) = row_heights.get_mut(r) {
1725 *rh = share.max(floor);
1726 }
1727 }
1728 }
1729 }
1730 }
1731
1732 let rows_total_h: i32 =
1737 row_heights.iter().sum::<i32>() + (n_rows.saturating_sub(1) as i32) * row_gap;
1738 let rows_free = container_h_explicit
1739 .map(|ch| (ch - rows_total_h).max(0))
1740 .unwrap_or(0);
1741 let align_content_grid = node
1742 .value("align-content")
1743 .unwrap_or_default()
1744 .trim()
1745 .to_lowercase();
1746 let (row_start_offset, row_extra_gap) = if rows_free > 0 && n_rows > 0 {
1747 match align_content_grid.as_str() {
1748 "end" | "flex-end" => (rows_free, 0),
1749 "center" => (rows_free / 2, 0),
1750 "space-between" if n_rows > 1 => (0, rows_free / (n_rows as i32 - 1)),
1751 "space-around" if n_rows > 0 => {
1752 let g = rows_free / n_rows as i32;
1753 (g / 2, g)
1754 }
1755 "space-evenly" if n_rows > 0 => {
1756 let g = rows_free / (n_rows as i32 + 1);
1757 (g, g)
1758 }
1759 _ => (0, 0),
1760 }
1761 } else {
1762 (0, 0)
1763 };
1764 let row_gap_eff = row_gap + row_extra_gap;
1765
1766 let mut row_tops: Vec<i32> = alloc::vec![0; n_rows + 1];
1768 for r in 0..n_rows {
1769 let h = row_heights.get(r).copied().unwrap_or(0);
1770 row_tops[r + 1] = row_tops[r] + h + if r + 1 < n_rows { row_gap_eff } else { 0 };
1771 }
1772
1773 for (idx, child) in self.children.iter_mut().enumerate() {
1775 if Self::child_is_out_of_flow(child) {
1776 let oof_cb = Dimensions {
1779 content: Rect {
1780 x: base_x,
1781 y: base_y,
1782 width: content_w,
1783 height: 0,
1784 },
1785 ..Dimensions::default()
1786 };
1787 child.layout(oof_cb);
1788 continue;
1789 }
1790 let Some((r, c, rs, cs)) = placement[idx] else {
1791 continue;
1792 };
1793 let cell_w = grid_span_width(&col_widths, col_gap_eff, c, cs);
1794 let cell_x = base_x + col_start_offset + grid_col_x(&col_widths, col_gap_eff, c);
1795 let cell_y = base_y + row_start_offset + row_tops.get(r).copied().unwrap_or(0);
1796 let mut cell_h = 0i32;
1798 for rr in r..(r + rs).min(row_heights.len()) {
1799 cell_h += row_heights.get(rr).copied().unwrap_or(0);
1800 }
1801 cell_h += (rs.saturating_sub(1) as i32) * row_gap;
1802
1803 let j = grid_item_align(child, "justify-self", &justify_items);
1805 let a = grid_item_align(child, "align-self", &align_items);
1806
1807 let cell_cb = Dimensions {
1809 content: Rect {
1810 x: cell_x,
1811 y: cell_y,
1812 width: cell_w,
1813 height: 0,
1814 },
1815 ..Dimensions::default()
1816 };
1817 let sub_ctx = Self::prepare_child_subgrid_ctx(child, &col_widths, col_gap_eff, c, cs, cell_w);
1818 let is_sub = sub_ctx.is_some();
1819 let prev_ctx = if is_sub {
1820 set_subgrid_ctx(sub_ctx)
1821 } else {
1822 None
1823 };
1824 child.layout(cell_cb);
1825 if is_sub {
1826 set_subgrid_ctx(prev_ctx);
1827 }
1828 if let Some((num, den)) = box_aspect_ratio(child) {
1829 if num > 0.0 && den > 0.0 && !box_has_explicit(child, "height") {
1830 let w = child.dimensions.content.width as f32;
1831 let h = round_f32_to_i32(w * den / num).max(1);
1832 child.dimensions.content.height = h;
1833 }
1834 }
1835
1836 if a == "stretch" && !box_has_explicit(child, "height") && box_aspect_ratio(child).is_none() && cell_h > 0 {
1838 let mb = child.dimensions.margin_box();
1839 let non_content = mb.height - child.dimensions.content.height;
1840 child.dimensions.content.height = (cell_h - non_content).max(0);
1841 }
1842
1843 let mb = child.dimensions.margin_box();
1847 let dx = if j == "stretch" || !box_has_explicit(child, "width") {
1848 0
1849 } else {
1850 let free = (cell_w - mb.width).max(0);
1851 match j {
1852 "center" => free / 2,
1853 "end" => free,
1854 _ => 0, }
1856 };
1857 let dy = if a == "stretch" {
1863 0
1864 } else {
1865 let free = (cell_h - mb.height).max(0);
1866 match a {
1867 "center" => free / 2,
1868 "end" => free,
1869 _ => 0, }
1871 };
1872 if dx != 0 || dy != 0 {
1873 shift_layout_box(child, dx, dy);
1874 }
1875 }
1876
1877 let natural_h = row_tops.get(n_rows).copied().unwrap_or(0).max(0);
1879 self.dimensions.content.height = container_h_explicit.unwrap_or(natural_h);
1880
1881 let saved_abs_cb = self.establish_abs_cb();
1883 self.apply_positioned_offsets();
1884 Self::restore_abs_cb(saved_abs_cb);
1885 }
1886
1887 fn layout_table(&mut self, containing_block: Dimensions) {
1888 self.apply_box_model_styles_with_base(containing_block.content.width);
1889
1890 let h_non_content = self.dimensions.margin.left
1891 + self.dimensions.margin.right
1892 + self.dimensions.border.left
1893 + self.dimensions.border.right
1894 + self.dimensions.padding.left
1895 + self.dimensions.padding.right;
1896 let avail = (containing_block.content.width - h_non_content).max(1);
1897
1898 let table_width = match &self.box_type {
1900 BoxType::TableNode(node) => match parse_length_value(len_value(node, "width")) {
1901 Some(LengthValue::Px(px)) => px.max(1).min(avail),
1902 Some(LengthValue::Percent(pct)) => round_f32_to_i32(avail as f32 * pct).max(1),
1903 _ => avail,
1904 },
1905 _ => avail,
1906 };
1907
1908 let flow_y = containing_block.content.y
1910 + containing_block.content.height
1911 + self.dimensions.margin.top;
1912 self.dimensions.content.x = containing_block.content.x
1913 + self.dimensions.margin.left
1914 + self.dimensions.border.left
1915 + self.dimensions.padding.left;
1916 self.dimensions.content.y =
1917 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
1918 self.dimensions.content.width = table_width;
1919
1920 let base_x = self.dimensions.content.x;
1921
1922 let collapse = match &self.box_type {
1924 BoxType::TableNode(node) => node
1925 .value("border-collapse")
1926 .map(|v| v.trim().eq_ignore_ascii_case("collapse"))
1927 .unwrap_or(false),
1928 _ => false,
1929 };
1930 let collapse_px: i32 = if collapse { 1 } else { 0 };
1931
1932 let table_layout_fixed = match &self.box_type {
1936 BoxType::TableNode(node) => node
1937 .value("table-layout")
1938 .map(|v| v.trim().eq_ignore_ascii_case("fixed"))
1939 .unwrap_or(false),
1940 _ => false,
1941 };
1942
1943 let (h_spacing, v_spacing): (i32, i32) = if collapse {
1946 (0, 0)
1947 } else {
1948 match &self.box_type {
1949 BoxType::TableNode(node) => match node.value("border-spacing") {
1950 Some(v) => {
1951 let toks: Vec<&str> = v.split_whitespace().collect();
1952 let px = |s: &str| -> i32 {
1953 match parse_length_value(Some(String::from(s))) {
1954 Some(LengthValue::Px(px)) => px.max(0),
1955 _ => 0,
1956 }
1957 };
1958 match toks.as_slice() {
1959 [h] => (px(h), px(h)),
1960 [h, w] => (px(h), px(w)),
1961 _ => (0, 0),
1962 }
1963 }
1964 None => (0, 0),
1965 },
1966 _ => (0, 0),
1967 }
1968 };
1969
1970 let caption_indices: Vec<usize> = (0..self.children.len())
1974 .filter(|&i| box_tag_name(&self.children[i]) == Some("caption"))
1975 .collect();
1976 let caption_side_bottom = caption_indices
1977 .first()
1978 .and_then(|&ci| box_style_value(&self.children[ci], "caption-side"))
1979 .map(|v| v.trim().eq_ignore_ascii_case("bottom"))
1980 .unwrap_or(false);
1981 let mut top_offset = 0i32;
1982 if !caption_side_bottom {
1983 let y = self.dimensions.content.y;
1984 top_offset = self.layout_captions_at(&caption_indices, base_x, y, table_width);
1985 }
1986 let rows_top = self.dimensions.content.y + top_offset;
1987
1988 let row_indices: Vec<usize> = (0..self.children.len())
1992 .filter(|&i| {
1993 matches!(self.children[i].box_type, BoxType::TableRowNode(_))
1994 && box_style_value(&self.children[i], "visibility")
1995 .map(|v| !v.trim().eq_ignore_ascii_case("collapse"))
1996 .unwrap_or(true)
1997 })
1998 .collect();
1999 let nrows = row_indices.len();
2000
2001 struct CellPlace {
2003 row_ord: usize,
2004 cell_ord: usize,
2005 col: usize,
2006 colspan: usize,
2007 rowspan: usize,
2008 }
2009 let mut occ: Vec<Vec<bool>> = Vec::new();
2010 let mut places: Vec<CellPlace> = Vec::new();
2011 let mut ncols = 0usize;
2012 for (row_ord, &ri) in row_indices.iter().enumerate() {
2013 let ncells = self.children[ri].children.len();
2014 let mut c = 0usize;
2015 for cell_ord in 0..ncells {
2016 while occ_is(&occ, row_ord, c) {
2018 c += 1;
2019 }
2020 let (cs, rs) = cell_spans(&self.children[ri].children[cell_ord]);
2021 let rs = rs.min(nrows - row_ord); for rr in row_ord..(row_ord + rs) {
2023 for cc in c..(c + cs) {
2024 occ_mark(&mut occ, rr, cc);
2025 }
2026 }
2027 places.push(CellPlace {
2028 row_ord,
2029 cell_ord,
2030 col: c,
2031 colspan: cs,
2032 rowspan: rs.max(1),
2033 });
2034 c += cs;
2035 ncols = ncols.max(c);
2036 }
2037 }
2038
2039 if ncols == 0 {
2040 if caption_side_bottom {
2041 let y = self.dimensions.content.y + top_offset;
2042 top_offset += self.layout_captions_at(&caption_indices, base_x, y, table_width);
2043 }
2044 self.dimensions.content.height = top_offset;
2045 return;
2046 }
2047
2048 let mut col_w = alloc::vec![0i32; ncols];
2050 let mut is_fixed_col = alloc::vec![false; ncols];
2051 let pad = 24; let usable_width = (table_width - h_spacing * (ncols as i32 - 1).max(0)).max(1);
2053
2054 if table_layout_fixed {
2055 for p in &places {
2058 if p.row_ord != 0 || p.colspan != 1 {
2059 continue;
2060 }
2061 let cell = &self.children[row_indices[p.row_ord]].children[p.cell_ord];
2062 if let Some(w) = box_style_value(cell, "width") {
2063 match parse_length_value(Some(w)) {
2064 Some(LengthValue::Px(px)) => {
2065 col_w[p.col] = px.max(1);
2066 is_fixed_col[p.col] = true;
2067 }
2068 Some(LengthValue::Percent(pct)) => {
2069 col_w[p.col] = round_f32_to_i32(usable_width as f32 * pct).max(1);
2070 is_fixed_col[p.col] = true;
2071 }
2072 _ => {}
2073 }
2074 }
2075 }
2076 let fixed_sum: i32 = (0..ncols).filter(|&c| is_fixed_col[c]).map(|c| col_w[c]).sum();
2078 let auto_cols: alloc::vec::Vec<usize> =
2079 (0..ncols).filter(|&c| !is_fixed_col[c]).collect();
2080 if !auto_cols.is_empty() {
2081 let remaining = (usable_width - fixed_sum).max(0);
2082 let auto_w = (remaining / auto_cols.len() as i32).max(1);
2083 let mut assigned = 0i32;
2084 for (i, &c) in auto_cols.iter().enumerate() {
2085 if i == auto_cols.len() - 1 {
2086 col_w[c] = (remaining - assigned).max(1);
2087 } else {
2088 col_w[c] = auto_w;
2089 assigned += auto_w;
2090 }
2091 }
2092 }
2093 } else {
2094 for p in &places {
2095 if p.colspan != 1 {
2096 continue;
2097 }
2098 let (cw, _) = self.children[row_indices[p.row_ord]].children[p.cell_ord]
2099 .intrinsic_inline_size(table_width);
2100 col_w[p.col] = col_w[p.col].max(cw + pad);
2101 }
2102 for p in &places {
2103 if p.colspan <= 1 {
2104 continue;
2105 }
2106 let (cw, _) = self.children[row_indices[p.row_ord]].children[p.cell_ord]
2107 .intrinsic_inline_size(table_width);
2108 let need = cw + pad;
2109 let cur: i32 = col_w[p.col..p.col + p.colspan].iter().sum();
2110 if need > cur {
2111 let deficit = need - cur;
2112 let add = deficit / p.colspan as i32;
2113 for cc in p.col..p.col + p.colspan {
2114 col_w[cc] += add;
2115 }
2116 col_w[p.col] += deficit - add * p.colspan as i32; }
2118 }
2119
2120 let total: i32 = col_w.iter().sum::<i32>().max(1);
2122 if total > usable_width {
2123 let mut assigned = 0i32;
2124 for (c, w) in col_w.iter_mut().enumerate() {
2125 if c == ncols - 1 {
2126 *w = (usable_width - assigned).max(1);
2127 } else {
2128 let scaled = ((*w as i64 * usable_width as i64) / total as i64).max(1) as i32;
2129 *w = scaled;
2130 assigned += scaled;
2131 }
2132 }
2133 } else if total < usable_width {
2134 let diff = usable_width - total;
2135 let add = diff / ncols as i32;
2136 let mut distributed = 0i32;
2137 for c in 0..ncols {
2138 if c == ncols - 1 {
2139 col_w[c] += diff - distributed;
2140 } else {
2141 col_w[c] += add;
2142 distributed += add;
2143 }
2144 }
2145 }
2146 }
2147 let mut col_x = alloc::vec![0i32; ncols];
2149 let mut acc = 0i32;
2150 for c in 0..ncols {
2151 col_x[c] = acc;
2152 acc += col_w[c];
2153 if c + 1 < ncols {
2154 acc += h_spacing;
2155 }
2156 }
2157 let span_w = |col: usize, colspan: usize| -> i32 {
2158 col_w[col..(col + colspan).min(ncols)].iter().sum::<i32>()
2159 + h_spacing * colspan.saturating_sub(1) as i32
2160 };
2161
2162 let mut place_h = alloc::vec![0i32; places.len()];
2164 for (pi, p) in places.iter().enumerate() {
2165 let cw_total = span_w(p.col, p.colspan);
2166 let cell = &mut self.children[row_indices[p.row_ord]].children[p.cell_ord];
2167 let mut cb = Dimensions::default();
2168 cb.content.x = base_x + col_x[p.col];
2169 cb.content.y = rows_top;
2170 cb.content.height = 0;
2171 cb.content.width = cw_total;
2172 cell.layout(cb);
2173 place_h[pi] = cell.dimensions.margin_box().height;
2174 }
2175 let mut row_h = alloc::vec![0i32; nrows];
2177 for (pi, p) in places.iter().enumerate() {
2178 if p.rowspan == 1 {
2179 row_h[p.row_ord] = row_h[p.row_ord].max(place_h[pi]);
2180 }
2181 }
2182 for (pi, p) in places.iter().enumerate() {
2183 if p.rowspan <= 1 {
2184 continue;
2185 }
2186 let end = (p.row_ord + p.rowspan).min(nrows);
2187 let cur: i32 = row_h[p.row_ord..end].iter().sum();
2188 if place_h[pi] > cur {
2189 row_h[end - 1] += place_h[pi] - cur;
2190 }
2191 }
2192 let mut row_y = alloc::vec![0i32; nrows];
2194 let mut accy = 0i32;
2195 for r in 0..nrows {
2196 row_y[r] = accy;
2197 accy += row_h[r];
2198 if r + 1 < nrows {
2199 accy += v_spacing;
2200 }
2201 }
2202 let rows_total = accy;
2203
2204 for p in places.iter() {
2206 let cw_total = span_w(p.col, p.colspan);
2207 let end_r = (p.row_ord + p.rowspan).min(nrows);
2208 let span_h: i32 = row_h[p.row_ord..end_r].iter().sum();
2209 let x0 = base_x + col_x[p.col] - collapse_px * p.col as i32;
2210 let y0 = rows_top + row_y[p.row_ord] - collapse_px * p.row_ord as i32;
2211 let cell = &mut self.children[row_indices[p.row_ord]].children[p.cell_ord];
2212 let mut cb = Dimensions::default();
2213 cb.content.x = x0;
2214 cb.content.y = y0;
2215 cb.content.height = 0;
2216 cb.content.width = cw_total;
2217 cell.layout(cb); let w_extra = cell.dimensions.padding.left
2219 + cell.dimensions.padding.right
2220 + cell.dimensions.border.left
2221 + cell.dimensions.border.right;
2222 let h_extra = cell.dimensions.padding.top
2223 + cell.dimensions.padding.bottom
2224 + cell.dimensions.border.top
2225 + cell.dimensions.border.bottom;
2226 let natural_h = cell.dimensions.content.height;
2229 let target_h = (span_h - h_extra).max(natural_h);
2230 let extra_space = (target_h - natural_h).max(0);
2231 if extra_space > 0 {
2232 let va = match &cell.box_type {
2233 BoxType::TableCellNode(node) => node.value("vertical-align").unwrap_or_default(),
2234 _ => String::new(),
2235 };
2236 let dy = match va.trim() {
2237 "middle" | "center" => extra_space / 2,
2238 "bottom" => extra_space,
2239 _ => 0, };
2241 for child in &mut cell.children {
2243 child.translate_y(dy);
2244 }
2245 }
2246 cell.dimensions.content.width = (cw_total - w_extra).max(1);
2247 cell.dimensions.content.height = target_h;
2248 }
2249
2250 for (row_ord, &ri) in row_indices.iter().enumerate() {
2252 let row = &mut self.children[ri];
2253 row.dimensions.content.x = base_x;
2254 row.dimensions.content.y = rows_top + row_y[row_ord] - collapse_px * row_ord as i32;
2255 row.dimensions.content.width = table_width;
2256 row.dimensions.content.height = row_h[row_ord];
2257 let saved_row_cb = row.establish_abs_cb();
2260 row.apply_positioned_offsets();
2261 Self::restore_abs_cb(saved_row_cb);
2262 }
2263
2264 let collapse_shrink = collapse_px * (nrows.saturating_sub(1)) as i32;
2266 let rows_height = (rows_total - collapse_shrink).max(0);
2267 if caption_side_bottom {
2268 let y = self.dimensions.content.y + top_offset + rows_height;
2269 top_offset += self.layout_captions_at(&caption_indices, base_x, y, table_width);
2270 }
2271 self.dimensions.content.height = top_offset + rows_height;
2272
2273 let saved_abs_cb = self.establish_abs_cb();
2277 self.apply_positioned_offsets();
2278 Self::restore_abs_cb(saved_abs_cb);
2279 }
2280
2281 fn layout_flex(&mut self, containing_block: Dimensions) {
2282 self.apply_box_model_styles_with_base(containing_block.content.width);
2283
2284 let (width_val, min_width_val, max_width_val, height_val, box_sizing, h_auto) =
2285 match &self.box_type {
2286 BoxType::FlexNode(node) => (
2287 len_value(node, "width"),
2288 len_value(node, "min-width"),
2289 len_value(node, "max-width"),
2290 len_value(node, "height"),
2291 node.value("box-sizing").unwrap_or_default(),
2292 margin_is_h_auto(node),
2293 ),
2294 _ => return,
2295 };
2296 let inner_non_content = self.dimensions.border.left
2298 + self.dimensions.border.right
2299 + self.dimensions.padding.left
2300 + self.dimensions.padding.right;
2301
2302 let horizontal_non_content = self.dimensions.margin.left
2303 + self.dimensions.margin.right
2304 + self.dimensions.border.left
2305 + self.dimensions.border.right
2306 + self.dimensions.padding.left
2307 + self.dimensions.padding.right;
2308 let available_width = containing_block.content.width.max(1);
2309 let explicit_width =
2310 !matches!(parse_length_value(width_val.clone()), Some(LengthValue::Auto) | None);
2311
2312 let mut content_width = match parse_length_value(width_val) {
2313 Some(LengthValue::Auto) | None => (available_width - horizontal_non_content).max(1),
2314 Some(LengthValue::Px(px)) => px.max(1),
2315 Some(LengthValue::Percent(pct)) => {
2316 round_f32_to_i32(available_width as f32 * pct).max(1)
2317 }
2318 Some(LengthValue::MinContent) => {
2319 let (minc, _maxc) = self.block_intrinsic_widths();
2320 minc.max(1)
2321 }
2322 Some(LengthValue::MaxContent) => {
2323 let (_minc, maxc) = self.block_intrinsic_widths();
2324 maxc.max(1)
2325 }
2326 Some(LengthValue::FitContent) => {
2327 let (minc, maxc) = self.block_intrinsic_widths();
2328 let avail = (available_width - horizontal_non_content).max(0);
2329 avail.min(maxc).max(minc).max(1)
2330 }
2331 Some(LengthValue::FitContentWith(lim_box)) => {
2332 let avail = (available_width - horizontal_non_content).max(0);
2333 let limit = resolve_length_value_with_font(*lim_box, avail, self.font_size())
2334 .unwrap_or(avail);
2335 let (minc, maxc) = self.block_intrinsic_widths();
2336 maxc.min(limit.max(minc)).max(1)
2337 }
2338 Some(LengthValue::MathExpr(expr)) => {
2339 crate::os_lib::css::eval_css_math_with_font(&expr, available_width, self.font_size())
2340 .unwrap_or((available_width - horizontal_non_content).max(1))
2341 .max(1)
2342 }
2343 };
2344 if explicit_width && box_sizing.trim() == "border-box" {
2346 content_width = (content_width - inner_non_content).max(1);
2347 }
2348
2349 if let Some(min_width) = parse_length_value(min_width_val)
2350 .and_then(|v| resolve_length_value_with_font(v, available_width, self.font_size()))
2351 {
2352 content_width = content_width.max(min_width);
2353 }
2354 if let Some(max_width) = parse_length_value(max_width_val)
2355 .and_then(|v| resolve_length_value_with_font(v, available_width, self.font_size()))
2356 {
2357 content_width = content_width.min(max_width);
2358 }
2359 self.dimensions.content.width = content_width.max(1);
2360
2361 if h_auto {
2364 let free = (available_width - self.dimensions.content.width - inner_non_content).max(0);
2365 self.dimensions.margin.left = free / 2;
2366 self.dimensions.margin.right = free / 2;
2367 }
2368
2369 let flow_y = containing_block.content.y
2370 + containing_block.content.height
2371 + self.dimensions.margin.top;
2372 self.dimensions.content.x = containing_block.content.x
2373 + self.dimensions.margin.left
2374 + self.dimensions.border.left
2375 + self.dimensions.padding.left;
2376 self.dimensions.content.y =
2377 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
2378
2379 let definite_h = self.explicit_content_height_px(height_val.clone()).unwrap_or(-1);
2381 let _pct_h_guard = PctBaseHGuard::set(definite_h);
2382
2383 let (flex_dir, justify_content, align_items, align_content, row_gap, col_gap, flex_wrap) =
2384 match &self.box_type {
2385 BoxType::FlexNode(node) => {
2386 let (rg, cg) = parse_flex_gap(node, self.dimensions.content.width);
2387 (
2388 node.value("flex-direction")
2389 .unwrap_or_else(|| String::from("row"))
2390 .trim()
2391 .to_lowercase(),
2392 node.value("justify-content")
2393 .unwrap_or_else(|| String::from("flex-start"))
2394 .trim()
2395 .to_lowercase(),
2396 node.value("align-items")
2397 .unwrap_or_else(|| String::from("stretch"))
2398 .trim()
2399 .to_lowercase(),
2400 node.value("align-content")
2401 .unwrap_or_else(|| String::from("stretch"))
2402 .trim()
2403 .to_lowercase(),
2404 rg,
2405 cg,
2406 node.value("flex-wrap")
2407 .unwrap_or_else(|| String::from("nowrap"))
2408 .trim()
2409 .to_lowercase(),
2410 )
2411 }
2412 _ => return,
2413 };
2414
2415 let is_row = flex_dir == "row" || flex_dir == "row-reverse";
2417 let is_reverse = flex_dir == "row-reverse" || flex_dir == "column-reverse";
2418 let do_wrap = flex_wrap == "wrap" || flex_wrap == "wrap-reverse";
2420 let gap_main = if is_row { col_gap } else { row_gap };
2422 let gap_cross = if is_row { row_gap } else { col_gap };
2424
2425 if do_wrap {
2426 self.layout_flex_wrapped(
2427 is_row,
2428 is_reverse,
2429 flex_wrap == "wrap-reverse",
2430 &justify_content,
2431 &align_items,
2432 &align_content,
2433 gap_main,
2434 gap_cross,
2435 height_val.clone(),
2436 );
2437 return;
2438 }
2439
2440 let mut total_main = 0;
2442 let mut max_cross = 0;
2443 let oof: Vec<bool> = self
2446 .children
2447 .iter()
2448 .map(|c| Self::child_is_out_of_flow(c))
2449 .collect();
2450 let n = oof.iter().filter(|&&b| !b).count();
2451
2452 let basis_main_ref = if is_row {
2454 self.dimensions.content.width
2455 } else {
2456 match parse_length_value(height_val.clone()) {
2457 Some(LengthValue::Px(px)) => px,
2458 _ => 0,
2459 }
2460 };
2461
2462 for (ci, child) in self.children.iter_mut().enumerate() {
2463 let mut cb = self.dimensions.clone();
2464 cb.content.x = 0;
2465 cb.content.y = 0;
2466 cb.content.height = 0; child.layout(cb);
2468 if oof.get(ci).copied().unwrap_or(false) {
2469 continue;
2470 }
2471 {
2476 let (lead, trail) = box_main_auto_margins(child, is_row);
2477 if lead || trail {
2478 if is_row {
2479 child.dimensions.margin.left = 0;
2480 child.dimensions.margin.right = 0;
2481 } else {
2482 child.dimensions.margin.top = 0;
2483 child.dimensions.margin.bottom = 0;
2484 }
2485 }
2486 }
2487 if let Some(basis) = box_flex_basis(child, basis_main_ref) {
2489 if is_row {
2490 child.dimensions.content.width = basis;
2491 } else {
2492 child.dimensions.content.height = basis;
2493 }
2494 }
2495 if is_row
2507 && box_flex_basis(child, basis_main_ref).is_none()
2508 && !box_has_explicit(child, "width")
2509 {
2510 let mut max_content = child.block_intrinsic_widths().1;
2511 let container_w = self.dimensions.content.width;
2515 let inner = border_box_inner_w(child);
2516 if let Some(mw) = box_len(child, "max-width", container_w) {
2517 max_content = max_content.min((mw - inner).max(0));
2518 }
2519 if let Some(mw) = box_len(child, "min-width", container_w) {
2520 max_content = max_content.max((mw - inner).max(0));
2521 }
2522 let no_in_flow = child.children.iter().all(|c| Self::child_is_out_of_flow(c));
2525 if (max_content > 0 || no_in_flow) && max_content != child.dimensions.content.width {
2526 let mut cb2 = self.dimensions.clone();
2531 cb2.content.x = 0;
2532 cb2.content.y = 0;
2533 cb2.content.width = max_content + child.h_non_content();
2537 cb2.content.height = 0; child.layout(cb2);
2539 child.dimensions.content.width = max_content;
2540 }
2541 }
2542 let child_w = child.dimensions.margin_box().width;
2543 let child_h = child.dimensions.margin_box().height;
2544 if is_row {
2545 total_main += child_w;
2546 max_cross = max_cross.max(child_h);
2547 } else {
2548 total_main += child_h;
2549 max_cross = max_cross.max(child_w);
2550 }
2551 }
2552 if n > 1 {
2554 total_main += gap_main * (n as i32 - 1);
2555 }
2556
2557 let container_main = if is_row {
2558 self.dimensions.content.width
2559 } else {
2560 self.explicit_content_height_px(height_val.clone()).unwrap_or(total_main)
2561 };
2562
2563 let mut container_cross = if is_row {
2564 self.explicit_content_height_px(height_val.clone()).unwrap_or(max_cross)
2565 } else {
2566 self.dimensions.content.width
2567 };
2568
2569 if !is_row {
2570 self.dimensions.content.height = container_main;
2571 } else {
2572 self.dimensions.content.height = container_cross;
2573 }
2574
2575 let grows: Vec<i32> = self
2578 .children
2579 .iter()
2580 .enumerate()
2581 .map(|(i, c)| {
2582 if oof.get(i).copied().unwrap_or(false) {
2583 0
2584 } else {
2585 box_flex_grow(c)
2586 }
2587 })
2588 .collect();
2589 let total_grow: i32 = grows.iter().sum();
2590 if total_grow > 0 {
2591 let mut base_main = total_main;
2593 for (i, child) in self.children.iter().enumerate() {
2594 if grows.get(i).copied().unwrap_or(0) <= 0 {
2595 continue;
2596 }
2597 base_main -= if is_row {
2598 child.dimensions.content.width
2599 } else {
2600 child.dimensions.content.height
2601 };
2602 }
2603 let free = (container_main - base_main).max(0);
2604 if free > 0 {
2605 let last_grow_idx = grows.iter().rposition(|g| *g > 0);
2606 let mut remaining = free;
2607 for (i, child) in self.children.iter_mut().enumerate() {
2608 let g = grows.get(i).copied().unwrap_or(0);
2609 if g <= 0 {
2610 continue;
2611 }
2612 let extra = if Some(i) == last_grow_idx {
2614 remaining
2615 } else {
2616 free * g / total_grow
2617 };
2618 remaining -= extra;
2619 if is_row {
2620 child.dimensions.content.width = extra.max(0);
2621 } else {
2622 child.dimensions.content.height = extra.max(0);
2623 }
2624 }
2625 total_main = base_main + free; }
2627 }
2628
2629 let overflow = total_main - container_main;
2633 if total_grow == 0 && overflow > 0 {
2634 let shrinks: Vec<i32> = self
2635 .children
2636 .iter()
2637 .enumerate()
2638 .map(|(i, c)| {
2639 if oof.get(i).copied().unwrap_or(false) {
2640 0
2641 } else {
2642 box_flex_shrink(c)
2643 }
2644 })
2645 .collect();
2646 let total_shrink: i32 = shrinks.iter().sum();
2647 if total_shrink > 0 {
2648 let items: Vec<crate::os_lib::layout::flex_min::ShrinkItem> = self
2654 .children
2655 .iter()
2656 .enumerate()
2657 .map(|(i, c)| {
2658 let base = if is_row {
2659 c.dimensions.content.width
2660 } else {
2661 c.dimensions.content.height
2662 };
2663 let min = if is_row {
2665 c.block_intrinsic_widths().0
2666 } else {
2667 0
2668 };
2669 crate::os_lib::layout::flex_min::ShrinkItem {
2670 base: base.max(0),
2671 min: min.clamp(0, base.max(0)),
2672 shrink: shrinks.get(i).copied().unwrap_or(0).max(0),
2673 }
2674 })
2675 .collect();
2676
2677 match crate::os_lib::layout::flex_min::distribute_shrink(&items, overflow.max(0)) {
2678 Ok(sizes) => {
2679 for (i, child) in self.children.iter_mut().enumerate() {
2680 if let Some(&s) = sizes.get(i) {
2681 if is_row {
2682 child.dimensions.content.width = s;
2683 } else {
2684 child.dimensions.content.height = s;
2685 }
2686 }
2687 }
2688 if is_row {
2699 for (i, child) in self.children.iter_mut().enumerate() {
2700 if oof.get(i).copied().unwrap_or(false) {
2701 continue;
2702 }
2703 let Some(&s) = sizes.get(i) else {
2704 continue;
2705 };
2706 let base = items.get(i).map(|it| it.base).unwrap_or(s);
2707 if s >= base {
2709 continue;
2710 }
2711 let mut cb = self.dimensions.clone();
2712 cb.content.x = 0;
2713 cb.content.y = 0;
2714 cb.content.width = s + child.h_non_content();
2717 cb.content.height = 0; child.layout(cb);
2719 child.dimensions.content.width = s;
2721 }
2722 }
2723 total_main = sizes.iter().sum::<i32>().max(container_main.min(total_main));
2726 }
2727 Err(_) => {
2728 }
2731 }
2732 }
2733 }
2734
2735 for (ci, child) in self.children.iter_mut().enumerate() {
2739 if oof.get(ci).copied().unwrap_or(false) {
2740 continue;
2741 }
2742 if let Some((num, den)) = box_aspect_ratio(child) {
2743 if num > 0.0 && den > 0.0 {
2744 if is_row && !box_has_explicit(child, "height") {
2745 let w = child.dimensions.content.width as f32;
2746 let h = round_f32_to_i32(w * den / num).max(1);
2747 child.dimensions.content.height = h;
2748 } else if !is_row && !box_has_explicit(child, "width") {
2749 let h = child.dimensions.content.height as f32;
2750 let w = round_f32_to_i32(h * num / den).max(1);
2751 child.dimensions.content.width = w;
2752 }
2753 }
2754 }
2755 }
2756
2757 if is_row && !matches!(parse_length_value(height_val.clone()), Some(LengthValue::Px(_))) {
2767 let mut recomputed = 0;
2768 for (ci, child) in self.children.iter().enumerate() {
2769 if oof.get(ci).copied().unwrap_or(false) {
2770 continue;
2771 }
2772 recomputed = recomputed.max(child.dimensions.margin_box().height);
2773 }
2774 if recomputed > container_cross {
2775 container_cross = recomputed;
2776 self.dimensions.content.height = container_cross;
2777 }
2778 }
2779
2780 if align_items == "stretch" {
2784 let cross_prop = if is_row { "height" } else { "width" };
2785 for (ci, child) in self.children.iter_mut().enumerate() {
2786 if oof.get(ci).copied().unwrap_or(false) {
2787 continue;
2788 }
2789 if box_has_explicit(child, cross_prop) || box_aspect_ratio(child).is_some() {
2790 continue;
2791 }
2792 if let Some(s) = box_align_self(child) {
2794 if s != "stretch" {
2795 continue;
2796 }
2797 }
2798 let mb = child.dimensions.margin_box();
2799 let (cur_cross, cur_content) = if is_row {
2800 (mb.height, child.dimensions.content.height)
2801 } else {
2802 (mb.width, child.dimensions.content.width)
2803 };
2804 let non_content = cur_cross - cur_content; let new_content = (container_cross - non_content).max(0);
2806 if is_row {
2807 child.dimensions.content.height = new_content;
2808 } else {
2809 child.dimensions.content.width = new_content;
2810 }
2811 }
2812 }
2813
2814 let main_free_space = container_main - total_main;
2816
2817 let mut total_auto_margins = 0i32;
2820 for (ci, child) in self.children.iter().enumerate() {
2821 if oof.get(ci).copied().unwrap_or(false) {
2822 continue;
2823 }
2824 let (a, b) = box_main_auto_margins(child, is_row);
2825 total_auto_margins += a as i32 + b as i32;
2826 }
2827 let auto_margin_unit = if total_auto_margins > 0 && main_free_space > 0 {
2828 main_free_space / total_auto_margins
2829 } else {
2830 0
2831 };
2832 let use_auto_margins = total_auto_margins > 0 && main_free_space > 0;
2833
2834 let main_offset = if use_auto_margins {
2835 0
2836 } else {
2837 match justify_content.as_str() {
2838 "flex-end" | "end" => main_free_space,
2843 "center" => main_free_space / 2,
2844 "space-around" if n > 0 => main_free_space / (n as i32 * 2),
2845 "space-evenly" if n > 0 => main_free_space / (n as i32 + 1),
2849 _ => 0,
2851 }
2852 };
2853
2854 let spacing = if use_auto_margins {
2855 0
2856 } else {
2857 match justify_content.as_str() {
2858 "space-between" if n > 1 => main_free_space / (n as i32 - 1),
2859 "space-around" if n > 0 => main_free_space / (n as i32),
2860 "space-evenly" if n > 0 => main_free_space / (n as i32 + 1),
2861 _ => 0,
2862 }
2863 };
2864
2865 let mut order_idx: Vec<usize> = (0..self.children.len()).collect();
2869 order_idx.sort_by_key(|&i| (box_order(&self.children[i]), i));
2870 let mut cursor = main_offset;
2871 for &ci in &order_idx {
2872 if oof.get(ci).copied().unwrap_or(false) {
2874 continue;
2875 }
2876 let (lead_auto, trail_auto) = if use_auto_margins {
2878 box_main_auto_margins(&self.children[ci], is_row)
2879 } else {
2880 (false, false)
2881 };
2882 if lead_auto {
2883 cursor += auto_margin_unit;
2884 }
2885 let child_w = self.children[ci].dimensions.margin_box().width;
2886 let child_h = self.children[ci].dimensions.margin_box().height;
2887 let child_main = if is_row { child_w } else { child_h };
2888
2889 let cross_free_space = container_cross - if is_row { child_h } else { child_w };
2890 let effective_align =
2892 box_align_self(&self.children[ci]).unwrap_or_else(|| align_items.clone());
2893 let cross_offset = match effective_align.as_str() {
2894 "flex-end" | "end" | "self-end" => cross_free_space,
2898 "center" => cross_free_space / 2,
2899 _ => 0, };
2901
2902 let main_start = if is_reverse {
2904 container_main - cursor - child_main
2905 } else {
2906 cursor
2907 };
2908
2909 let start_x = self.dimensions.content.x;
2911 let start_y = self.dimensions.content.y;
2912
2913 let target_x = if is_row {
2914 start_x + main_start
2915 } else {
2916 start_x + cross_offset
2917 };
2918 let target_y = if is_row {
2919 start_y + cross_offset
2920 } else {
2921 start_y + main_start
2922 };
2923
2924 let shift_x = target_x - self.children[ci].dimensions.margin_box().x;
2925 let shift_y = target_y - self.children[ci].dimensions.margin_box().y;
2926
2927 shift_layout_box(&mut self.children[ci], shift_x, shift_y);
2928
2929 cursor += child_main + spacing + gap_main;
2930 if trail_auto {
2931 cursor += auto_margin_unit;
2932 }
2933 }
2934
2935 let saved_abs_cb = self.establish_abs_cb();
2938 self.apply_positioned_offsets();
2939 Self::restore_abs_cb(saved_abs_cb);
2940 }
2941
2942 #[allow(clippy::too_many_arguments)]
2946 fn layout_flex_wrapped(
2947 &mut self,
2948 is_row: bool,
2949 is_reverse: bool,
2950 wrap_reverse: bool,
2951 justify_content: &str,
2952 align_items: &str,
2953 align_content: &str,
2954 gap_main: i32,
2955 gap_cross: i32,
2956 height_val: Option<String>,
2957 ) {
2958 let n = self.children.len();
2959 if n == 0 {
2960 return;
2961 }
2962
2963 let oof: Vec<bool> = self
2967 .children
2968 .iter()
2969 .map(|c| Self::child_is_out_of_flow(c))
2970 .collect();
2971 let mut sizes: Vec<(i32, i32)> = Vec::with_capacity(n); for child in &mut self.children {
2973 let mut cb = self.dimensions.clone();
2974 cb.content.x = 0;
2975 cb.content.y = 0;
2976 cb.content.height = 0;
2977 child.layout(cb);
2978 let w = child.dimensions.margin_box().width;
2979 let h = child.dimensions.margin_box().height;
2980 if is_row {
2981 sizes.push((w, h));
2982 } else {
2983 sizes.push((h, w));
2984 }
2985 }
2986 let flow_idx: Vec<usize> = (0..n).filter(|&i| !oof[i]).collect();
2988
2989 let container_main = if is_row {
2990 self.dimensions.content.width
2991 } else {
2992 self.explicit_content_height_px(height_val.clone()).unwrap_or(i32::MAX / 4)
2994 };
2995
2996 let fln = flow_idx.len();
2998 let mut lines: Vec<(usize, usize, i32, i32)> = Vec::new(); let mut line_start = 0usize;
3000 let mut line_main = 0i32;
3001 let mut line_cross = 0i32;
3002 for pos in 0..fln {
3003 let (m, c) = sizes[flow_idx[pos]];
3004 let add = if pos == line_start { m } else { m + gap_main };
3005 if pos > line_start && line_main + add > container_main {
3006 lines.push((line_start, pos, line_main, line_cross));
3007 line_start = pos;
3008 line_main = m;
3009 line_cross = c;
3010 } else {
3011 line_main += add;
3012 line_cross = line_cross.max(c);
3013 }
3014 }
3015 lines.push((line_start, fln, line_main, line_cross));
3016
3017 if wrap_reverse {
3019 lines.reverse();
3020 }
3021
3022 let total_cross: i32 =
3024 lines.iter().map(|l| l.3).sum::<i32>() + gap_cross * (lines.len() as i32 - 1).max(0);
3025 let container_cross = if is_row {
3026 self.explicit_content_height_px(height_val.clone()).unwrap_or(total_cross)
3027 } else {
3028 self.dimensions.content.width
3029 };
3030
3031 if is_row {
3032 self.dimensions.content.height = container_cross.max(total_cross);
3033 } else {
3034 self.dimensions.content.height = container_main.min(total_cross).max(total_cross);
3035 }
3036
3037 let base_x = self.dimensions.content.x;
3038 let base_y = self.dimensions.content.y;
3039
3040 let nlines = lines.len() as i32;
3045 let cross_free = (container_cross - total_cross).max(0);
3046 let mut line_cross_size: Vec<i32> = lines.iter().map(|l| l.3).collect();
3047 if (align_content == "stretch" || align_content.is_empty()) && cross_free > 0 && nlines > 0
3049 {
3050 let per = cross_free / nlines;
3051 let mut rem = cross_free - per * nlines;
3052 for sz in line_cross_size.iter_mut() {
3053 *sz += per;
3054 if rem > 0 {
3055 *sz += 1;
3056 rem -= 1;
3057 }
3058 }
3059 }
3060 let (ac_offset, ac_spacing) = match align_content {
3062 "flex-end" | "end" => (cross_free, 0),
3063 "center" => (cross_free / 2, 0),
3064 "space-between" if nlines > 1 => (0, cross_free / (nlines - 1)),
3065 "space-around" if nlines > 0 => (cross_free / (nlines * 2), cross_free / nlines),
3066 "space-evenly" if nlines > 0 => (cross_free / (nlines + 1), cross_free / (nlines + 1)),
3067 _ => (0, 0),
3069 };
3070 let mut line_cross_start: Vec<i32> = Vec::with_capacity(lines.len());
3071 let mut cc = ac_offset;
3072 for sz in &line_cross_size {
3073 line_cross_start.push(cc);
3074 cc += sz + gap_cross + ac_spacing;
3075 }
3076
3077 for (li, &(start, end_excl, line_main_used, _line_cross_nat)) in lines.iter().enumerate() {
3078 let count = end_excl - start;
3079 let main_free = container_main - line_main_used;
3080 let line_cross = line_cross_size.get(li).copied().unwrap_or(0);
3081 let cross_cursor = line_cross_start.get(li).copied().unwrap_or(0);
3082
3083 let mut main_offset = match justify_content {
3085 "flex-end" | "end" => main_free,
3086 "center" => main_free / 2,
3087 "space-around" if count > 0 => main_free / (count as i32 * 2),
3088 "space-evenly" if count > 0 => main_free / (count as i32 + 1),
3089 _ => 0,
3090 };
3091 let spacing = match justify_content {
3092 "space-between" if count > 1 => main_free / (count as i32 - 1),
3093 "space-around" if count > 0 => main_free / (count as i32),
3094 "space-evenly" if count > 0 => main_free / (count as i32 + 1),
3095 _ => 0,
3096 };
3097
3098 let pos_list: Vec<usize> = if is_reverse {
3100 (start..end_excl).rev().collect()
3101 } else {
3102 (start..end_excl).collect()
3103 };
3104
3105 for &pos in &pos_list {
3106 let ci = flow_idx[pos];
3107 let (m, c) = sizes[ci];
3108 let cross_free_item = line_cross - c;
3109 let effective_align =
3110 box_align_self(&self.children[ci]).unwrap_or_else(|| String::from(align_items));
3111 let cross_in_line = match effective_align.as_str() {
3112 "flex-end" | "end" | "self-end" => cross_free_item,
3113 "center" => cross_free_item / 2,
3114 _ => 0, };
3116 let cross_pos = cross_cursor + cross_in_line;
3117
3118 let (target_x, target_y) = if is_row {
3119 (base_x + main_offset, base_y + cross_pos)
3120 } else {
3121 (base_x + cross_pos, base_y + main_offset)
3122 };
3123
3124 let shift_x = target_x - self.children[ci].dimensions.margin_box().x;
3125 let shift_y = target_y - self.children[ci].dimensions.margin_box().y;
3126 shift_layout_box(&mut self.children[ci], shift_x, shift_y);
3127
3128 main_offset += m + spacing + gap_main;
3129 }
3130 }
3131
3132 let saved_abs_cb = self.establish_abs_cb();
3134 self.apply_positioned_offsets();
3135 Self::restore_abs_cb(saved_abs_cb);
3136 }
3137
3138 fn layout_block(&mut self, containing_block: Dimensions) {
3139 let forced_h = FORCED_BLOCK_H.swap(-1, AtomicOrdering::Relaxed);
3141 self.apply_box_model_styles_with_base(containing_block.content.width);
3142 let pct_base_h = PCT_BASE_H.load(AtomicOrdering::Relaxed);
3145
3146 let (
3149 width_val,
3150 min_width_val,
3151 max_width_val,
3152 height_val,
3153 min_height_val,
3154 max_height_val,
3155 box_sizing,
3156 h_auto,
3157 aspect_ratio_val,
3158 ) = match &self.box_type {
3159 BoxType::BlockNode(node)
3160 | BoxType::TableRowGroupNode(node)
3161 | BoxType::TableRowNode(node)
3162 | BoxType::TableCellNode(node) => (
3163 len_value(node, "width"),
3164 len_value(node, "min-width"),
3165 len_value(node, "max-width"),
3166 len_value(node, "height"),
3167 len_value(node, "min-height"),
3168 len_value(node, "max-height"),
3169 node.value("box-sizing").unwrap_or_default(),
3170 margin_is_h_auto(node),
3171 node.value("aspect-ratio"),
3172 ),
3173 _ => return,
3174 };
3175
3176 let horizontal_non_content = self.dimensions.margin.left
3177 + self.dimensions.margin.right
3178 + self.dimensions.border.left
3179 + self.dimensions.border.right
3180 + self.dimensions.padding.left
3181 + self.dimensions.padding.right;
3182 let available_width = containing_block.content.width.max(1);
3183 let inner_non_content = self.dimensions.border.left
3185 + self.dimensions.border.right
3186 + self.dimensions.padding.left
3187 + self.dimensions.padding.right;
3188
3189 let parsed_width = parse_length_value(width_val.clone());
3191 let explicit_width = !matches!(parsed_width, Some(LengthValue::Auto) | None);
3192 let mut content_width = match parsed_width {
3193 Some(LengthValue::Auto) | None => (available_width - horizontal_non_content).max(1),
3194 Some(LengthValue::Px(px)) => px.max(1),
3195 Some(LengthValue::Percent(pct)) => {
3196 round_f32_to_i32(available_width as f32 * pct).max(1)
3197 }
3198 Some(LengthValue::MinContent) => {
3200 let (minc, _maxc) = self.block_intrinsic_widths();
3201 minc.max(1)
3202 }
3203 Some(LengthValue::MaxContent) => {
3204 let (_minc, maxc) = self.block_intrinsic_widths();
3205 maxc.max(1)
3206 }
3207 Some(LengthValue::FitContent) => {
3208 let avail = (available_width - horizontal_non_content).max(1);
3210 let (minc, maxc) = self.block_intrinsic_widths();
3211 maxc.min(avail).max(minc).max(1)
3212 }
3213 Some(LengthValue::FitContentWith(lim_box)) => {
3214 let avail = (available_width - horizontal_non_content).max(1);
3216 let limit = resolve_length_value_with_font(*lim_box, avail, self.font_size())
3217 .unwrap_or(avail);
3218 let (minc, maxc) = self.block_intrinsic_widths();
3219 maxc.min(limit.max(minc)).max(1)
3220 }
3221 Some(LengthValue::MathExpr(expr)) => {
3222 crate::os_lib::css::eval_css_math_with_font(&expr, available_width, self.font_size())
3223 .unwrap_or((available_width - horizontal_non_content).max(1))
3224 .max(1)
3225 }
3226 };
3227 if explicit_width && box_sizing.trim() == "border-box" {
3231 content_width = (content_width - inner_non_content).max(1);
3232 }
3233
3234 let bb_inner = if box_sizing.trim() == "border-box" { inner_non_content } else { 0 };
3237 if let Some(min_width) = self.resolve_sizing_value(min_width_val.clone(), available_width) {
3238 let min_width = if is_length_sizing(&min_width_val) { (min_width - bb_inner).max(0) } else { min_width };
3239 content_width = content_width.max(min_width);
3240 }
3241 if let Some(max_width) = self.resolve_sizing_value(max_width_val.clone(), available_width) {
3242 let max_width = if is_length_sizing(&max_width_val) { (max_width - bb_inner).max(0) } else { max_width };
3243 content_width = content_width.min(max_width);
3244 }
3245 self.dimensions.content.width = content_width.max(1);
3246
3247 if h_auto {
3249 let free = (available_width - content_width - inner_non_content).max(0);
3250 self.dimensions.margin.left = free / 2;
3251 self.dimensions.margin.right = free / 2;
3252 }
3253
3254 let flow_y = containing_block.content.y
3256 + containing_block.content.height
3257 + self.dimensions.margin.top;
3258 self.dimensions.content.x = containing_block.content.x
3259 + self.dimensions.margin.left
3260 + self.dimensions.border.left
3261 + self.dimensions.padding.left;
3262 self.dimensions.content.y =
3263 flow_y + self.dimensions.border.top + self.dimensions.padding.top;
3264
3265 let px_h = match parse_length_value(height_val.clone()) {
3271 Some(LengthValue::Px(px)) => Some(px),
3272 Some(LengthValue::Percent(pct)) if pct_base_h >= 0 => Some(round_f32_to_i32(pct_base_h as f32 * pct)),
3273 _ => None,
3274 };
3275 let definite_h = match (px_h, parse_length_value(height_val.clone())) {
3276 (Some(px), _) => {
3277 if box_sizing.trim() == "border-box" {
3278 let d = &self.dimensions;
3279 (px - d.padding.top - d.padding.bottom - d.border.top - d.border.bottom).max(0)
3280 } else {
3281 px.max(0)
3282 }
3283 }
3284 (None, None | Some(LengthValue::Auto)) => aspect_ratio_val
3285 .as_deref()
3286 .and_then(parse_aspect_ratio)
3287 .map(|(num, den)| round_f32_to_i32(self.dimensions.content.width as f32 * den / num).max(0))
3288 .unwrap_or(-1),
3289 _ => -1,
3290 };
3291 let definite_h = if forced_h >= 0 { forced_h } else { definite_h };
3292 let _pct_h_guard = PctBaseHGuard::set(definite_h);
3293
3294 let self_pos = box_position(self);
3297 let saved_abs_cb = if matches!(self_pos, "relative" | "absolute" | "fixed") {
3298 let prev = abs_cb_get();
3299 let exp_h = if forced_h >= 0 {
3300 forced_h
3301 } else if definite_h >= 0 {
3302 definite_h
3303 } else {
3304 parse_length_value(height_val.clone())
3305 .and_then(|v| resolve_length_value(v, containing_block.content.height.max(0)))
3306 .unwrap_or(0)
3307 };
3308 let d = &self.dimensions;
3310 abs_cb_set(
3311 d.content.x - d.padding.left,
3312 d.content.y - d.padding.top,
3313 d.content.width + d.padding.left + d.padding.right,
3314 exp_h + d.padding.top + d.padding.bottom,
3315 );
3316 Some(prev)
3317 } else {
3318 None
3319 };
3320
3321 let line_height_default = 20i32;
3323 let mut y_cursor = 0i32;
3324 let mut line_x = 0i32;
3325 let mut line_h = 0i32;
3326 let max_w = self.dimensions.content.width.max(1);
3327
3328 let mut left_float: Option<(i32, i32)> = None;
3332 let mut right_float: Option<(i32, i32)> = None;
3333
3334 fn float_avail(
3336 max_w: i32,
3337 left_float: Option<(i32, i32)>,
3338 right_float: Option<(i32, i32)>,
3339 y: i32,
3340 ) -> (i32, i32) {
3341 let l = left_float
3342 .filter(|&(_, yb)| yb > y)
3343 .map(|(w, _)| w)
3344 .unwrap_or(0);
3345 let r = right_float
3346 .filter(|&(_, yb)| yb > y)
3347 .map(|(w, _)| w)
3348 .unwrap_or(0);
3349 (l, (max_w - l - r).max(1))
3350 }
3351
3352 let mut lines: Vec<(usize, usize)> = Vec::new();
3354 let mut line_start = 0usize;
3355 let mut last_was_space = true;
3358 let mut trailing_space: Option<(usize, i32)> = None;
3359 let mut hanging: Vec<(usize, i32)> = Vec::new();
3360
3361 for (ci, child) in self.children.iter_mut().enumerate() {
3362 let float_side = box_float(child);
3363 if float_side != "none" {
3364 let float_y = y_cursor;
3366 let is_inline_kind =
3367 matches!(child.box_type, BoxType::InlineNode(_) | BoxType::InlineBlockNode(_));
3368 let (fw, fh) = if is_inline_kind {
3369 child.intrinsic_inline_size(max_w)
3370 } else {
3371 let mut cb = self.dimensions.clone();
3372 cb.content.height = float_y;
3373 child.layout(cb);
3374 (
3375 child.dimensions.margin_box().width,
3376 child.dimensions.margin_box().height,
3377 )
3378 };
3379
3380 let x = if float_side == "left" {
3381 let x_off = left_float
3382 .filter(|&(_, yb)| yb > float_y)
3383 .map(|(w, _)| w)
3384 .unwrap_or(0);
3385 left_float = Some((x_off + fw, float_y + fh));
3386 self.dimensions.content.x + x_off
3387 } else {
3388 let x_off = right_float
3389 .filter(|&(_, yb)| yb > float_y)
3390 .map(|(w, _)| w)
3391 .unwrap_or(0);
3392 right_float = Some((x_off + fw, float_y + fh));
3393 self.dimensions.content.x + max_w - x_off - fw
3394 };
3395
3396 if is_inline_kind {
3397 child.dimensions.content.x = x;
3398 child.dimensions.content.y = self.dimensions.content.y + float_y;
3399 child.dimensions.content.width = fw;
3400 child.dimensions.content.height = fh;
3401 let mut cb = child.dimensions.clone();
3402 cb.content.height = 0;
3403 child.layout(cb);
3404 child.dimensions.content.x = x;
3405 child.dimensions.content.y = self.dimensions.content.y + float_y;
3406 child.dimensions.content.width = fw;
3407 child.dimensions.content.height = fh;
3408 } else {
3409 let dx = x - child.dimensions.content.x;
3411 child.dimensions.content.x += dx;
3412 }
3413 continue;
3415 }
3416
3417 match child.box_type {
3418 BoxType::BlockNode(_)
3419 | BoxType::FlexNode(_)
3420 | BoxType::GridNode(_)
3421 | BoxType::AnonymousBlock
3422 | BoxType::TableNode(_)
3423 | BoxType::TableRowGroupNode(_)
3424 | BoxType::TableRowNode(_)
3425 | BoxType::TableCellNode(_) => {
3426 let out_of_flow = matches!(box_position(child), "absolute" | "fixed");
3427 if out_of_flow {
3428 let mut cb = self.dimensions.clone();
3430 cb.content.height = y_cursor;
3431 child.layout(cb);
3432 } else {
3433 if line_x > 0 {
3434 lines.push((line_start, ci));
3435 hanging.extend(trailing_space.take());
3436 y_cursor += if line_h > 0 { line_h } else { line_height_default };
3437 line_x = 0;
3438 line_h = 0;
3439 }
3440 last_was_space = true;
3441 let clear = box_clear(child);
3443 if clear == "left" || clear == "both" {
3444 if let Some((_, yb)) = left_float {
3445 y_cursor = y_cursor.max(yb);
3446 }
3447 }
3448 if clear == "right" || clear == "both" {
3449 if let Some((_, yb)) = right_float {
3450 y_cursor = y_cursor.max(yb);
3451 }
3452 }
3453 let (x_off, avail) = float_avail(max_w, left_float, right_float, y_cursor);
3454 let mut cb = self.dimensions.clone();
3455 cb.content.height = y_cursor;
3456 cb.content.x = self.dimensions.content.x + x_off;
3457 cb.content.width = avail;
3458 child.layout(cb);
3459 y_cursor += child.dimensions.margin_box().height;
3460 }
3461 }
3462 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
3463 if child.is_inline_line_break() {
3464 if line_x > 0 {
3465 lines.push((line_start, ci));
3466 }
3467 hanging.extend(trailing_space.take());
3468 last_was_space = true;
3469 y_cursor += if line_h > 0 { line_h } else { line_height_default };
3470 line_x = 0;
3471 line_h = 0;
3472 child.dimensions.content.x = self.dimensions.content.x;
3473 child.dimensions.content.y = self.dimensions.content.y + y_cursor;
3474 child.dimensions.content.width = 0;
3475 child.dimensions.content.height = 0;
3476 continue;
3477 }
3478 let (mut x_off, mut eff_w) =
3479 float_avail(max_w, left_float, right_float, y_cursor);
3480 child.trim_leading_space = false;
3482 let (mut inline_w, mut inline_h) = child.intrinsic_inline_size(eff_w);
3483 let is_space = child.is_collapsible_space();
3488 let mut text_trailing_w = 0i32;
3489 if is_space {
3490 inline_w = if line_x == 0 || last_was_space { 0 } else { child.collapsed_space_width() };
3491 if inline_w == 0 {
3492 inline_h = 0;
3494 }
3495 } else {
3496 if child.text_starts_with_space() && (line_x == 0 || last_was_space) {
3497 inline_w = inline_w.saturating_sub(child.collapsed_space_width());
3498 child.trim_leading_space = true;
3499 }
3500 if child.text_ends_with_space() {
3501 let sp_w = child.collapsed_space_width();
3502 inline_w += sp_w;
3503 text_trailing_w = sp_w;
3504 }
3505 }
3506 let edges_w = if matches!(child.box_type, BoxType::InlineBlockNode(_)) || child.is_replaced_inline() {
3508 intrinsic_h_edges(child, false)
3509 } else {
3510 0
3511 };
3512 if !is_space && line_x > 0 && line_x + inline_w + edges_w > eff_w {
3513 lines.push((line_start, ci));
3514 hanging.extend(trailing_space.take());
3515 y_cursor += if line_h > 0 { line_h } else { line_height_default };
3516 line_x = 0;
3517 line_h = 0;
3518 let recomputed = float_avail(max_w, left_float, right_float, y_cursor);
3519 x_off = recomputed.0;
3520 eff_w = recomputed.1;
3521 }
3522 if line_x == 0 {
3523 line_start = ci;
3524 if !is_space && !child.trim_leading_space && child.text_starts_with_space() {
3526 inline_w = inline_w.saturating_sub(child.collapsed_space_width());
3527 child.trim_leading_space = true;
3528 }
3529 }
3530 let _ = eff_w;
3531 child.dimensions.content.x = self.dimensions.content.x + x_off + line_x;
3532 child.dimensions.content.y = self.dimensions.content.y + y_cursor;
3533 child.dimensions.content.width = inline_w;
3534 child.dimensions.content.height = inline_h;
3535
3536 let mut cb = child.dimensions.clone();
3542 cb.content.height = 0;
3543 child.layout(cb);
3544 let (el, er, et, eb) = child.inline_block_edges();
3554 let slot_x = self.dimensions.content.x + x_off + line_x + el;
3555 let slot_y = self.dimensions.content.y + y_cursor + et;
3556 let (dx, dy) = (slot_x - child.dimensions.content.x, slot_y - child.dimensions.content.y);
3557 child.translate(dx, dy);
3558 child.dimensions.content.width = inline_w;
3559 child.dimensions.content.height = inline_h;
3560
3561 line_x += el + inline_w + er;
3562 let outer_h = et + inline_h + eb;
3563 if outer_h > line_h {
3564 line_h = outer_h;
3565 }
3566 if is_space {
3567 if inline_w > 0 {
3568 trailing_space = Some((ci, inline_w));
3569 }
3570 last_was_space = true;
3571 } else if child.text_ends_with_space() {
3572 if text_trailing_w > 0 {
3573 trailing_space = Some((ci, text_trailing_w));
3574 }
3575 last_was_space = true;
3576 } else if !child.is_blank_inline() {
3577 trailing_space = None;
3578 last_was_space = false;
3579 }
3580 }
3581 }
3582 }
3583
3584 hanging.extend(trailing_space.take());
3585 for (i, trim_w) in hanging {
3587 if let Some(c) = self.children.get_mut(i) {
3588 c.dimensions.content.width = c.dimensions.content.width.saturating_sub(trim_w);
3589 }
3590 }
3591 if line_x > 0 {
3592 lines.push((line_start, self.children.len()));
3593 y_cursor += if line_h > 0 { line_h } else { line_height_default };
3594 }
3595 if let Some((_, yb)) = left_float {
3598 y_cursor = y_cursor.max(yb);
3599 }
3600 if let Some((_, yb)) = right_float {
3601 y_cursor = y_cursor.max(yb);
3602 }
3603
3604 if let BoxType::BlockNode(node) = &self.box_type {
3616 let cc = node.value("column-count");
3617 let cw = node.value("column-width");
3618 let shorthand = node.value("columns");
3619 let (sh_w, sh_c) = shorthand
3620 .as_deref()
3621 .map(crate::os_lib::layout::multicol::parse_columns_shorthand)
3622 .unwrap_or((None, None));
3623 let parse_px = |v: &Option<String>| -> Option<i32> {
3624 v.as_deref()
3625 .and_then(|s| s.trim().strip_suffix("px"))
3626 .and_then(|n| n.trim().parse::<f32>().ok())
3627 .filter(|f| f.is_finite() && *f > 0.0)
3628 .map(|f| f as i32)
3629 };
3630 let count = cc
3631 .as_deref()
3632 .and_then(|s| s.trim().parse::<i32>().ok())
3633 .filter(|v| *v > 0)
3634 .or(sh_c);
3635 let width = parse_px(&cw).or(sh_w);
3636 if count.is_some() || width.is_some() {
3637 let gap_str = node.value("column-gap").or_else(|| node.value("gap"));
3639 let gap = crate::os_lib::layout::multicol::resolve_column_gap(
3640 gap_str.as_deref(),
3641 self.dimensions.content.width.max(0),
3642 self.font_size(),
3643 );
3644 let spec = crate::os_lib::layout::multicol::MultiColSpec { count, width, gap };
3645 let cols = crate::os_lib::layout::multicol::layout_columns(
3646 &spec,
3647 self.dimensions.content.width.max(0),
3648 );
3649 if cols.len() > 1 {
3650 let base_x = self.dimensions.content.x;
3651 let base_y = self.dimensions.content.y;
3652 let flow_idx: alloc::vec::Vec<usize> = self
3654 .children
3655 .iter()
3656 .enumerate()
3657 .filter(|(_, c)| box_float(c) == "none")
3658 .map(|(i, _)| i)
3659 .collect();
3660
3661 let items: alloc::vec::Vec<crate::os_lib::layout::multicol::ChildKind> = flow_idx
3663 .iter()
3664 .map(|&i| {
3665 let child = &self.children[i];
3666 let h = child.dimensions.margin_box().height;
3667 let is_spanner = match &child.box_type {
3668 BoxType::BlockNode(n) => {
3669 n.value("column-span").as_deref().map(|s| s.trim()) == Some("all")
3670 }
3671 _ => false,
3672 };
3673 if is_spanner {
3674 crate::os_lib::layout::multicol::ChildKind::Spanner { height: h }
3675 } else {
3676 crate::os_lib::layout::multicol::ChildKind::ColumnItem { height: h }
3677 }
3678 })
3679 .collect();
3680
3681 let (places, total_h) =
3682 crate::os_lib::layout::multicol::assign_children_with_spans(&items, cols.len());
3683
3684 for (slot, &ci) in flow_idx.iter().enumerate() {
3685 let place = places[slot];
3686 let child = &mut self.children[ci];
3687 let target_w = if place.is_span {
3688 self.dimensions.content.width.max(0)
3689 } else {
3690 cols[place.col].width
3691 };
3692 let target_x = if place.is_span {
3693 0
3694 } else {
3695 cols[place.col].x
3696 };
3697
3698 let dx = base_x + target_x - child.dimensions.content.x
3699 + child.dimensions.margin.left
3700 + child.dimensions.border.left
3701 + child.dimensions.padding.left;
3702 let dy = base_y + place.y - child.dimensions.content.y
3703 + child.dimensions.margin.top
3704 + child.dimensions.border.top
3705 + child.dimensions.padding.top;
3706 child.translate(dx, dy);
3707 child.dimensions.content.width = (target_w
3708 - child.dimensions.margin.left
3709 - child.dimensions.margin.right
3710 - child.dimensions.border.left
3711 - child.dimensions.border.right
3712 - child.dimensions.padding.left
3713 - child.dimensions.padding.right)
3714 .max(0);
3715 }
3716 y_cursor = total_h;
3717 }
3718 }
3719 }
3720
3721
3722 if let BoxType::BlockNode(n) = &self.box_type {
3725 if let Some((_, h)) = pseudo_content_size(n) {
3726 y_cursor = y_cursor.max(h);
3727 }
3728 }
3729 self.dimensions.content.height = y_cursor;
3730 let cb_height = containing_block.content.height.max(0);
3731 let height_len = match parse_length_value(height_val.clone()) {
3735 Some(LengthValue::Percent(pct)) if pct_base_h >= 0 => {
3736 Some(round_f32_to_i32(pct_base_h as f32 * pct))
3737 }
3738 Some(LengthValue::Percent(_)) => None,
3739 v => v.and_then(|v| resolve_length_value(v, cb_height)),
3740 };
3741 if let Some(h) = height_len
3742 {
3743 let vertical_non_content = self.dimensions.border.top
3745 + self.dimensions.border.bottom
3746 + self.dimensions.padding.top
3747 + self.dimensions.padding.bottom;
3748 let content_h = if box_sizing.trim() == "border-box" {
3749 (h - vertical_non_content).max(0)
3750 } else {
3751 h.max(0)
3752 };
3753 self.dimensions.content.height = content_h;
3754 }
3755 if let Some(mx) =
3760 parse_length_value(max_height_val).and_then(|v| resolve_length_value(v, cb_height))
3761 {
3762 let vertical_non_content = self.dimensions.border.top
3763 + self.dimensions.border.bottom
3764 + self.dimensions.padding.top
3765 + self.dimensions.padding.bottom;
3766 let max_content = if box_sizing.trim() == "border-box" {
3767 (mx - vertical_non_content).max(0)
3768 } else {
3769 mx.max(0)
3770 };
3771 self.dimensions.content.height = self.dimensions.content.height.min(max_content);
3772 }
3773 if let Some(mh) =
3774 parse_length_value(min_height_val).and_then(|v| resolve_length_value(v, cb_height))
3775 {
3776 self.dimensions.content.height = self.dimensions.content.height.max(mh);
3777 }
3778
3779 if let Some((num, den)) = aspect_ratio_val.as_deref().and_then(parse_aspect_ratio) {
3782 let width_is_auto = matches!(
3783 parse_length_value(width_val.clone()),
3784 Some(LengthValue::Auto) | None
3785 );
3786 let height_is_auto = parse_length_value(height_val.clone()).is_none();
3787 if height_is_auto {
3788 self.dimensions.content.height =
3790 round_f32_to_i32(self.dimensions.content.width as f32 * den / num).max(1);
3791 } else if width_is_auto {
3792 let h = self.dimensions.content.height as f32;
3795 let mut w = round_f32_to_i32(h * num / den).max(1);
3796 if let Some(min_w) = parse_length_value(min_width_val.clone())
3797 .and_then(|v| resolve_length_value(v, available_width))
3798 {
3799 w = w.max(min_w);
3800 }
3801 if let Some(max_w) = parse_length_value(max_width_val.clone())
3802 .and_then(|v| resolve_length_value(v, available_width))
3803 {
3804 w = w.min(max_w);
3805 }
3806 self.dimensions.content.width = w.max(1);
3807 }
3808 }
3809
3810 let (text_align, direction, text_align_last) = match &self.box_type {
3814 BoxType::BlockNode(node) | BoxType::TableCellNode(node) => (
3815 node.value("text-align").unwrap_or_default(),
3816 node.value("direction").unwrap_or_default(),
3817 node.value("text-align-last").unwrap_or_default(),
3818 ),
3819 _ => (String::new(), String::new(), String::new()),
3820 };
3821 let is_rtl = direction.trim().eq_ignore_ascii_case("rtl");
3822 let mut ta = text_align.trim().to_lowercase();
3823 if ta.is_empty() && is_rtl {
3824 ta = String::from("right");
3825 }
3826 ta = match ta.as_str() {
3829 "start" => String::from(if is_rtl { "right" } else { "left" }),
3830 "end" => String::from(if is_rtl { "left" } else { "right" }),
3831 _ => ta,
3832 };
3833 let ta_last_resolved = match text_align_last.trim().to_lowercase().as_str() {
3837 "start" => String::from(if is_rtl { "right" } else { "left" }),
3838 "end" => String::from(if is_rtl { "left" } else { "right" }),
3839 other => String::from(other),
3840 };
3841 let ta_last_effective = if matches!(ta_last_resolved.as_str(), "left" | "center" | "right")
3842 {
3843 Some(ta_last_resolved)
3844 } else {
3845 None
3846 };
3847 if ta == "center" || ta == "right" || ta == "justify" || ta_last_effective.is_some() {
3848 let content_w = self.dimensions.content.width.max(1);
3849 let last_line_idx = lines.len().saturating_sub(1);
3850
3851 for (line_idx, &(s, e)) in lines.iter().enumerate() {
3852 let mut line_indices = Vec::new();
3854 let mut total_w = 0i32;
3855 for ci in s..e {
3856 match self.children[ci].box_type {
3857 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
3858 line_indices.push(ci);
3859 let (el, er, _, _) = self.children[ci].inline_block_edges();
3860 total_w += el + self.children[ci].dimensions.content.width + er;
3861 }
3862 _ => {}
3863 }
3864 }
3865 if line_indices.is_empty() {
3866 continue;
3867 }
3868
3869 let is_last_line = line_idx == last_line_idx;
3870 let effective_ta = if is_last_line {
3871 ta_last_effective.as_deref().unwrap_or(if ta == "justify" {
3872 if is_rtl { "right" } else { "left" }
3873 } else {
3874 ta.as_str()
3875 })
3876 } else {
3877 ta.as_str()
3878 };
3879
3880 if effective_ta == "justify" {
3881 let k = line_indices.len();
3882 if k > 1 {
3883 let remaining = (content_w - total_w).max(0);
3884 for (i, &ci) in line_indices.iter().enumerate() {
3885 let shift = (remaining * i as i32) / (k as i32 - 1);
3886 self.children[ci].translate(shift, 0);
3887 }
3888 }
3889 } else {
3890 let offset = match effective_ta {
3891 "center" => ((content_w - total_w) / 2).max(0),
3892 "right" => (content_w - total_w).max(0),
3893 _ => 0,
3894 };
3895 if offset > 0 {
3896 for &ci in &line_indices {
3897 self.children[ci].translate(offset, 0);
3898 }
3899 }
3900 }
3901 }
3902 }
3903
3904 let mut total_delta_h = 0i32;
3908 for &(s, e) in &lines {
3909 let orig_h = self.children[s..e]
3910 .iter()
3911 .filter(|c| {
3912 !c.is_whitespace_text()
3913 && matches!(c.box_type, BoxType::InlineNode(_) | BoxType::InlineBlockNode(_))
3914 && box_float(c) == "none"
3915 && !matches!(box_position(c), "absolute" | "fixed")
3916 })
3917 .map(|c| {
3918 let (_, _, et, eb) = c.inline_block_edges();
3919 et + c.dimensions.content.height + eb
3920 })
3921 .max()
3922 .unwrap_or(0);
3923
3924 let new_h = Self::align_line_baselines(&mut self.children, s, e);
3925 if new_h > orig_h {
3926 let delta = new_h - orig_h;
3927 total_delta_h += delta;
3928 for ci in e..self.children.len() {
3929 self.children[ci].translate_y(delta);
3930 }
3931 }
3932 }
3933 if total_delta_h > 0 {
3934 self.dimensions.content.height += total_delta_h;
3935 }
3936 if forced_h >= 0 {
3937 self.dimensions.content.height = forced_h;
3938 }
3939
3940 if saved_abs_cb.is_some() {
3943 let pb = self.dimensions.padding_box();
3944 abs_cb_set(pb.x, pb.y, pb.width, pb.height);
3945 }
3946 self.apply_positioned_offsets();
3947
3948 if let Some(prev) = saved_abs_cb {
3950 abs_cb_set(prev.0, prev.1, prev.2, prev.3);
3951 }
3952 }
3953
3954 fn intrinsic_inline_size_ext(&self, avail_hint: i32, pure: bool) -> (i32, i32) {
3970 if self.is_inline_flex() || self.is_inline_grid() {
3972 if pure {
3973 let (minc, maxc) = self.block_intrinsic_widths();
3974 return (if avail_hint <= 1 { minc } else { maxc }, 0);
3975 }
3976 let w = self.inline_flex_content_width(avail_hint);
3977 let mut probe = self.clone();
3978 probe.layout_as_flex_at(0, 0, w);
3979 return (w, probe.dimensions.content.height.max(0));
3980 }
3981 match &self.box_type {
3982 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
3983 let base = if !pure {
3991 Some(avail_hint)
3992 } else if avail_hint <= 1 {
3993 Some(0)
3994 } else {
3995 None
3996 };
3997 if let Some(size) = get_replaced_element_size_in(node, base) {
3998 return size;
3999 }
4000 let font_size = node
4001 .value("font-size")
4002 .and_then(|v| parse_font_size_u32(&v))
4003 .unwrap_or(16);
4004 let line_height_val = node.value("line-height");
4005 let line_height = parse_line_height(line_height_val, font_size);
4006
4007 let is_text_node = matches!(node.node.node_type, NodeType::Text(_));
4016 let css_w = if is_text_node { None } else {
4017 len_value(node, "width").and_then(|v| match parse_length_value(Some(v)) {
4018 Some(LengthValue::Px(px)) => Some(px),
4019 Some(LengthValue::Percent(pct)) => {
4020 Some(round_f32_to_i32(avail_hint as f32 * pct))
4021 }
4022 _ => None,
4023 })
4024 };
4025 let css_h = if is_text_node { None } else {
4026 node.value("height").and_then(|v| match parse_length_value(Some(v)) {
4027 Some(LengthValue::Px(px)) => Some(px),
4028 _ => None,
4029 })
4030 };
4031
4032 match &node.node.node_type {
4033 NodeType::Text(text) => {
4034 let nowrap = matches!(
4038 node.value("white-space").as_deref(),
4039 Some("nowrap") | Some("pre")
4040 );
4041 let pre_wrap = matches!(
4042 node.value("white-space").as_deref(),
4043 Some("pre-wrap") | Some("pre-line")
4044 );
4045 let break_all = matches!(
4046 node.value("word-break").as_deref(),
4047 Some("break-all")
4048 );
4049 let overflow_wrap = matches!(
4050 node.value("overflow-wrap").as_deref(),
4051 Some("break-word") | Some("anywhere")
4052 ) || matches!(
4053 node.value("word-wrap").as_deref(),
4054 Some("break-word") | Some("anywhere")
4055 ) || matches!(
4056 node.value("word-break").as_deref(),
4057 Some("break-word")
4058 );
4059 let available = if nowrap {
4069 i32::MAX / 2
4070 } else {
4071 css_w.unwrap_or({
4072 if pure {
4073 if avail_hint > 0 {
4075 avail_hint
4076 } else {
4077 800
4078 }
4079 } else if self.dimensions.content.width > 0 {
4080 if avail_hint > 0 {
4087 self.dimensions.content.width.min(avail_hint)
4088 } else {
4089 self.dimensions.content.width
4090 }
4091 } else if avail_hint > 0 {
4092 avail_hint
4093 } else {
4094 800
4095 }
4096 })
4097 };
4098 let ls = parse_letter_spacing_with_font(node.value("letter-spacing").as_deref(), available, font_size as f32);
4099 let ws = parse_word_spacing_with_font(node.value("word-spacing").as_deref(), available, font_size as f32);
4100 let text = &crate::os_lib::layout::line_break::collapse_white_space(
4103 text,
4104 &node.value("white-space").unwrap_or_default(),
4105 );
4106 let text = if self.trim_leading_space {
4107 text.strip_prefix(' ').unwrap_or(text)
4108 } else {
4109 text.as_str()
4110 };
4111 let (fam, weight) = text_font_of(node);
4113 let (w, h) = crate::kernel::vector_font::get_vector_string_wrapped_size_family(
4114 text,
4115 font_size,
4116 available as u32,
4117 ls,
4118 ws,
4119 break_all,
4120 overflow_wrap,
4121 pre_wrap,
4122 &fam,
4123 weight,
4124 );
4125 let lines = h as i32 / (font_size as i32 + 4).max(1);
4126 (
4127 css_w.unwrap_or(w as i32),
4128 css_h.unwrap_or(lines * line_height),
4129 )
4130 }
4131 NodeType::Element { tag_name, .. } => {
4132 if tag_name == "br" {
4133 (0, line_height)
4134 } else {
4135 let (content_w, content_h) = if self.children.is_empty() {
4142 (0, line_height)
4143 } else {
4144 let mut total_w = 0i32;
4145 let mut max_h = line_height;
4146 let avail_hint = if matches!(self.box_type, BoxType::InlineBlockNode(_))
4150 && avail_hint > 0
4151 && avail_hint < 1_000_000
4152 {
4153 (avail_hint - intrinsic_h_edges(self, css_w.is_some())).max(1)
4154 } else {
4155 avail_hint
4156 };
4157 for child in &self.children {
4158 let child_is_text = matches!(
4172 &child.box_type,
4173 BoxType::InlineNode(n) if matches!(n.node.node_type, NodeType::Text(_))
4174 );
4175 let child_is_block_level = matches!(
4176 child.box_type,
4177 BoxType::BlockNode(_) | BoxType::FlexNode(_) | BoxType::GridNode(_)
4178 );
4179 let (cw, ch) = if child_is_text {
4180 child.intrinsic_inline_size_ext(avail_hint, pure)
4181 } else if child_is_block_level {
4182 let explicit = matches!(parse_length_value(box_style_value(child, "width")), Some(LengthValue::Px(_)));
4186 let (mn, mx) = match parse_length_value(box_style_value(child, "width")) {
4187 Some(LengthValue::Px(px)) => (px.max(0), px.max(0)),
4188 _ => child.block_intrinsic_widths(),
4189 };
4190 let w = if avail_hint <= 1 { mn } else { mx.min(avail_hint.max(0)) };
4191 (w + intrinsic_h_edges(child, explicit), child.dimensions.margin_box().height.max(0))
4192 } else {
4193 child.intrinsic_inline_size(avail_hint)
4194 };
4195 total_w += cw;
4196 if ch > max_h {
4197 max_h = ch;
4198 }
4199 }
4200 (total_w, max_h)
4201 };
4202 (css_w.unwrap_or(content_w), css_h.unwrap_or(content_h))
4203 }
4204 }
4205 }
4206 }
4207 _ => (0, 0),
4208 }
4209 }
4210
4211 fn intrinsic_inline_size(&self, avail_hint: i32) -> (i32, i32) {
4213 self.intrinsic_inline_size_ext(avail_hint, false)
4214 }
4215
4216 fn layout_inline(&mut self, containing_block: Dimensions, _block_like: bool) {
4217 self.apply_box_model_styles_with_base(containing_block.content.width);
4218
4219 let (width_val, min_width_val, max_width_val, height_val, min_height_val, max_height_val) =
4220 match &self.box_type {
4221 BoxType::InlineNode(node) if matches!(node.node.node_type, NodeType::Text(_)) => {
4224 (None, None, None, None, None, None)
4225 }
4226 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => (
4227 len_value(node, "width"),
4228 len_value(node, "min-width"),
4229 len_value(node, "max-width"),
4230 len_value(node, "height"),
4231 len_value(node, "min-height"),
4232 len_value(node, "max-height"),
4233 ),
4234 _ => return,
4235 };
4236
4237 let available_width = containing_block.content.width.max(1);
4238 let available_height = containing_block.content.height.max(1);
4239 let (intrinsic_w, intrinsic_h) = self.inline_content_intrinsic_size(available_width);
4240
4241 let mut content_width = match parse_length_value(width_val) {
4242 Some(LengthValue::Auto) | None => intrinsic_w.max(1),
4243 Some(LengthValue::Px(px)) => px.max(1),
4244 Some(LengthValue::Percent(pct)) => {
4245 round_f32_to_i32(available_width as f32 * pct).max(1)
4246 }
4247 Some(LengthValue::MinContent) => {
4248 let (minc, _maxc) = self.block_intrinsic_widths();
4249 minc.max(1)
4250 }
4251 Some(LengthValue::MaxContent) => {
4252 let (_minc, maxc) = self.block_intrinsic_widths();
4253 maxc.max(1)
4254 }
4255 Some(LengthValue::FitContent) => {
4256 let (minc, maxc) = self.block_intrinsic_widths();
4257 available_width.min(maxc).max(minc).max(1)
4258 }
4259 Some(LengthValue::FitContentWith(lim_box)) => {
4260 let limit = resolve_length_value_with_font(*lim_box, available_width, self.font_size())
4261 .unwrap_or(available_width);
4262 let (minc, maxc) = self.block_intrinsic_widths();
4263 maxc.min(limit.max(minc)).max(1)
4264 }
4265 Some(LengthValue::MathExpr(expr)) => {
4266 crate::os_lib::css::eval_css_math_with_font(&expr, available_width, self.font_size())
4267 .unwrap_or(intrinsic_w.max(1))
4268 .max(1)
4269 }
4270 };
4271 if let Some(min_width) = parse_length_value(min_width_val)
4272 .and_then(|v| resolve_length_value_with_font(v, available_width, self.font_size()))
4273 {
4274 content_width = content_width.max(min_width);
4275 }
4276 if let Some(max_width) = parse_length_value(max_width_val)
4277 .and_then(|v| resolve_length_value_with_font(v, available_width, self.font_size()))
4278 {
4279 content_width = content_width.min(max_width);
4280 }
4281
4282 let mut content_height = match parse_length_value(height_val) {
4283 Some(LengthValue::Auto)
4284 | Some(LengthValue::MinContent)
4285 | Some(LengthValue::MaxContent)
4286 | Some(LengthValue::FitContent)
4287 | Some(LengthValue::FitContentWith(_))
4288 | None => intrinsic_h.max(1),
4289 Some(LengthValue::Px(px)) => px.max(1),
4290 Some(LengthValue::Percent(pct)) => match PCT_BASE_H.load(AtomicOrdering::Relaxed) {
4296 b if b >= 0 => round_f32_to_i32(b as f32 * pct).max(1),
4297 _ => intrinsic_h.max(1),
4298 },
4299 Some(LengthValue::MathExpr(expr)) => {
4300 crate::os_lib::css::eval_css_math_with_font(&expr, available_height, self.font_size())
4301 .unwrap_or(intrinsic_h.max(1))
4302 .max(1)
4303 }
4304 };
4305 if let Some(min_height) = parse_length_value(min_height_val)
4306 .filter(|v| !matches!(v, LengthValue::Percent(_)))
4307 .and_then(|v| resolve_length_value_with_font(v, available_height, self.font_size()))
4308 {
4309 content_height = content_height.max(min_height);
4310 }
4311 if let Some(max_height) = parse_length_value(max_height_val)
4312 .filter(|v| !matches!(v, LengthValue::Percent(_)))
4313 .and_then(|v| resolve_length_value_with_font(v, available_height, self.font_size()))
4314 {
4315 content_height = content_height.min(max_height);
4316 }
4317
4318 self.dimensions.content.width = content_width.max(1);
4319 self.dimensions.content.height = content_height.max(1);
4320 self.dimensions.content.x = containing_block.content.x
4321 + self.dimensions.margin.left
4322 + self.dimensions.border.left
4323 + self.dimensions.padding.left;
4324 self.dimensions.content.y = containing_block.content.y
4325 + containing_block.content.height
4326 + self.dimensions.margin.top
4327 + self.dimensions.border.top
4328 + self.dimensions.padding.top;
4329
4330 let mut child_flow_x = 0i32;
4333 let mut child_flow_y = 0i32;
4334 let mut line_h = 0i32;
4335 let max_w = self.dimensions.content.width.max(1);
4336 let mut line_start_idx = 0usize;
4340
4341 let mut i = 0usize;
4342 while i < self.children.len() {
4343 match self.children[i].box_type {
4344 BoxType::BlockNode(_)
4345 | BoxType::FlexNode(_)
4346 | BoxType::GridNode(_)
4347 | BoxType::TableNode(_)
4348 | BoxType::TableRowGroupNode(_)
4349 | BoxType::TableRowNode(_)
4350 | BoxType::TableCellNode(_) => {
4351 if child_flow_x > 0 {
4352 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
4353 child_flow_y += line_h;
4354 child_flow_x = 0;
4355 line_h = 0;
4356 }
4357 line_start_idx = i + 1;
4358 let child = &mut self.children[i];
4359 let mut cb = self.dimensions.clone();
4360 cb.content.height = child_flow_y;
4361 child.layout(cb);
4362 child_flow_y += child.dimensions.margin_box().height;
4363 i += 1;
4367 }
4368 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
4369 if self.children[i].is_inline_line_break() {
4370 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
4371 child_flow_y += line_h.max(20);
4372 child_flow_x = 0;
4373 line_h = 0;
4374 line_start_idx = i + 1;
4375 i += 1;
4376 continue;
4377 }
4378 let (child_w, child_h) = self.children[i].intrinsic_inline_size(max_w);
4379 if child_flow_x > 0 && child_flow_x + child_w > max_w {
4380 self.finalize_inline_line_vertical_align(line_start_idx, i, line_h, max_w);
4381 child_flow_y += line_h.max(20);
4382 child_flow_x = 0;
4383 line_h = 0;
4384 line_start_idx = i;
4385 }
4386 let child = &mut self.children[i];
4387 child.dimensions.content.x = self.dimensions.content.x + child_flow_x;
4388 child.dimensions.content.y = self.dimensions.content.y + child_flow_y;
4389 child.dimensions.content.width = child_w;
4390 child.dimensions.content.height = child_h;
4391
4392 let cb = child.dimensions.clone();
4399 child.layout(cb);
4400 let (el, er, et, eb) = child.inline_block_edges();
4402 let slot_x = self.dimensions.content.x + child_flow_x + el;
4403 let slot_y = self.dimensions.content.y + child_flow_y + et;
4404 let (dx, dy) = (slot_x - child.dimensions.content.x, slot_y - child.dimensions.content.y);
4405 child.translate(dx, dy);
4406 child.dimensions.content.width = child_w;
4407 child.dimensions.content.height = child_h;
4408
4409 child_flow_x += el + child_w + er;
4410 let outer_h = et + child_h + eb;
4411 if outer_h > line_h {
4412 line_h = outer_h;
4413 }
4414 i += 1;
4415 }
4416 BoxType::AnonymousBlock => {
4417 i += 1;
4418 }
4419 }
4420 }
4421 self.finalize_inline_line_vertical_align(line_start_idx, self.children.len(), line_h, max_w);
4423 }
4424
4425 fn finalize_inline_line_vertical_align(&mut self, start: usize, end: usize, line_h: i32, max_w: i32) {
4431 if line_h <= 0 || end <= start {
4432 return;
4433 }
4434 for idx in start..end {
4435 let Some(child) = self.children.get_mut(idx) else {
4436 continue;
4437 };
4438 if !matches!(
4439 child.box_type,
4440 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_)
4441 ) {
4442 continue;
4443 }
4444 let va = box_style_value(child, "vertical-align").unwrap_or_default();
4445 let offset = match va.trim() {
4446 "middle" => {
4447 let (_, child_h) = child.intrinsic_inline_size(max_w);
4448 (line_h - child_h) / 2
4449 }
4450 "bottom" | "text-bottom" => {
4451 let (_, child_h) = child.intrinsic_inline_size(max_w);
4452 line_h - child_h
4453 }
4454 _ => 0,
4455 };
4456 if offset != 0 {
4457 child.translate_y(offset);
4458 }
4459 }
4460 }
4461
4462 fn resolve_sizing_value(&self, value: Option<String>, base: i32) -> Option<i32> {
4465 match parse_length_value(value)? {
4466 LengthValue::Auto => None,
4467 LengthValue::Px(px) => Some(px),
4468 LengthValue::Percent(pct) => Some(round_f32_to_i32(base as f32 * pct)),
4469 LengthValue::MinContent => Some(self.block_intrinsic_widths().0),
4470 LengthValue::MaxContent => Some(self.block_intrinsic_widths().1),
4471 LengthValue::FitContent => {
4472 let (minc, maxc) = self.block_intrinsic_widths();
4473 Some(maxc.min(base.max(0)).max(minc))
4474 }
4475 LengthValue::FitContentWith(lim_box) => {
4476 let limit = resolve_length_value_with_font(*lim_box, base, self.font_size())
4477 .unwrap_or(base);
4478 let (minc, maxc) = self.block_intrinsic_widths();
4479 Some(maxc.min(limit.max(minc)))
4480 }
4481 LengthValue::MathExpr(expr) => {
4482 crate::os_lib::css::eval_css_math_with_font(&expr, base, self.font_size())
4483 }
4484 }
4485 }
4486
4487 pub(crate) fn block_intrinsic_widths(&self) -> (i32, i32) {
4491 if let BoxType::BlockNode(n) | BoxType::InlineBlockNode(n) | BoxType::InlineNode(n) = &self.box_type {
4492 if let Some((w, _)) = pseudo_content_size(n) {
4493 return (w, w);
4494 }
4495 }
4496 if matches!(
4499 self.box_type,
4500 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_)
4501 ) && !self.is_inline_flex()
4502 && !self.is_inline_grid()
4503 {
4504 let max_c = self.intrinsic_inline_size_ext(1_000_000, true).0;
4505 let min_c = self.intrinsic_inline_size_ext(1, true).0;
4506 return (min_c.max(0), max_c.max(min_c).max(0));
4507 }
4508 if let BoxType::InlineBlockNode(node) = &self.box_type {
4521 if self.is_inline_grid() {
4522 let cols = parse_grid_tracks(
4523 node.value("grid-template-columns").as_deref().unwrap_or(""),
4524 0,
4525 );
4526 let all_px: Option<i32> = if cols.is_empty() {
4527 None
4528 } else {
4529 cols.iter().try_fold(0i32, |acc, t| match t {
4530 GridTrack::Px(px) => Some(acc + (*px).max(0)),
4531 _ => None,
4532 })
4533 };
4534 if let Some(sum) = all_px {
4535 let (_, cg) = parse_flex_gap(node, 0);
4536 let w = sum + cg.max(0) * (cols.len() as i32 - 1).max(0);
4537 return (w, w);
4538 }
4539 }
4540 }
4541 let flex_node = match &self.box_type {
4542 BoxType::FlexNode(n) => Some(*n),
4543 BoxType::InlineBlockNode(n) if self.is_inline_flex() => Some(*n),
4544 _ => None,
4545 };
4546 if let Some(node) = flex_node {
4547 let dir = node
4548 .value("flex-direction")
4549 .unwrap_or_else(|| alloc::string::String::from("row"))
4550 .trim()
4551 .to_lowercase();
4552 let is_row = !dir.starts_with("column");
4553 let wraps = node
4554 .value("flex-wrap")
4555 .map(|w| w.trim().to_lowercase().starts_with("wrap"))
4556 .unwrap_or(false);
4557 if is_row {
4558 let mut sum_min = 0i32;
4559 let mut sum_max = 0i32;
4560 let mut max_min = 0i32;
4561 for child in &self.children {
4562 let explicit = match parse_length_value(box_style_value(child, "width")) {
4568 Some(LengthValue::Px(px)) => Some(px.max(0)),
4569 _ => None,
4570 };
4571 let (cmin, cmax) = explicit.map(|w| (w, w)).unwrap_or_else(|| child.block_intrinsic_widths());
4572 let (cmin, cmax) = clamp_contribution(child, cmin, cmax, explicit.is_none());
4573 let edges = intrinsic_h_edges(child, explicit.is_some());
4574 let (cmin, cmax) = (cmin + edges, cmax + edges);
4575 sum_min += cmin;
4576 sum_max += cmax;
4577 max_min = max_min.max(cmin);
4578 }
4579 let (rg, cg) = parse_flex_gap(node, self.dimensions.content.width);
4580 let _ = rg;
4581 let n = self.children.len() as i32;
4582 let gaps = if n > 1 { cg * (n - 1) } else { 0 };
4583 let min_c = if wraps { max_min } else { sum_min + gaps };
4585 return (min_c.max(0), (sum_max + gaps).max(min_c).max(0));
4586 }
4587 }
4588
4589 let grid_node = match &self.box_type {
4596 BoxType::GridNode(n) => Some(*n),
4597 BoxType::InlineBlockNode(n) if self.is_inline_grid() => Some(*n),
4598 _ => None,
4599 };
4600 if let Some(node) = grid_node {
4601 let template = node.value("grid-template-columns").unwrap_or_default();
4602 let n_cols = grid_column_count_for_intrinsic(&template);
4603 let tracks = parse_grid_tracks(&template, 0);
4604 let fixed: Vec<Option<i32>> = (0..n_cols)
4605 .map(|i| match tracks.get(i) {
4606 Some(GridTrack::Px(px)) if tracks.len() == n_cols => Some(*px),
4607 _ => None,
4608 })
4609 .collect();
4610 let mut items: Vec<(usize, i32, i32)> = Vec::new();
4611 for child in self.children.iter().filter(|c| !Self::child_is_out_of_flow(c)) {
4612 let (cmin, cmax, explicit) = match parse_length_value(box_style_value(child, "width")) {
4613 Some(LengthValue::Px(px)) => (px.max(0), px.max(0), true),
4614 _ => {
4615 let (a, b) = child.block_intrinsic_widths();
4616 (a, b, false)
4617 }
4618 };
4619 let (cmin, cmax) = clamp_contribution(child, cmin, cmax, !explicit);
4620 let edges = intrinsic_h_edges(child, explicit);
4621 items.push((items.len() % n_cols, cmin + edges, cmax + edges));
4622 }
4623 let (_, cg) = parse_flex_gap(node, 0);
4624 return grid_intrinsic_widths(n_cols, &fixed, &items, cg);
4625 }
4626
4627 self.children_intrinsic_widths()
4628 }
4629
4630 pub(crate) fn children_intrinsic_widths(&self) -> (i32, i32) {
4633 let mut inline_max = 0i32; let mut overall_max = 0i32;
4636 let mut overall_min = 0i32;
4637 for child in &self.children {
4638 if child.is_inline_line_break() {
4641 overall_max = overall_max.max(inline_max);
4642 inline_max = 0;
4643 continue;
4644 }
4645 match child.box_type {
4646 BoxType::InlineNode(_) | BoxType::InlineBlockNode(_) => {
4647 let mut cmax = child.intrinsic_inline_size_ext(1_000_000, true).0;
4652 let mut cmin = child.intrinsic_inline_size_ext(1, true).0;
4653 if matches!(child.box_type, BoxType::InlineBlockNode(_)) && !child.is_replaced_inline() {
4654 let explicit = matches!(parse_length_value(box_style_value(child, "width")), Some(LengthValue::Px(_)));
4655 if !explicit && !child.is_inline_flex() && !child.is_inline_grid() {
4657 let (amin, amax) = child.children_intrinsic_widths();
4658 cmin = cmin.max(amin);
4659 cmax = cmax.max(amax);
4660 }
4661 let edges = intrinsic_h_edges(child, explicit);
4662 cmin += edges;
4663 cmax += edges;
4664 } else if child.is_replaced_inline() {
4665 let m = inline_h_margins(child);
4666 cmin += m;
4667 cmax += m;
4668 }
4669 inline_max += cmax;
4670 overall_min = overall_min.max(cmin);
4671 }
4672 _ => {
4673 overall_max = overall_max.max(inline_max);
4674 inline_max = 0;
4675 let (cmin, cmax, explicit) = match parse_length_value(box_style_value(child, "width")) {
4677 Some(LengthValue::Px(px)) => (px.max(0), px.max(0), true),
4678 _ => {
4679 let (a, b) = child.block_intrinsic_widths();
4680 (a, b, false)
4681 }
4682 };
4683 let (cmin, cmax) = clamp_contribution(child, cmin, cmax, !explicit);
4684 let edges = intrinsic_h_edges(child, explicit);
4685 let (cmin, cmax) = (cmin + edges, cmax + edges);
4686 overall_min = overall_min.max(cmin);
4687 overall_max = overall_max.max(cmax);
4688 }
4689 }
4690 }
4691 overall_max = overall_max.max(inline_max);
4692 (overall_min.max(0), overall_max.max(overall_min).max(0))
4693 }
4694
4695 fn inline_content_intrinsic_size(&self, available_width: i32) -> (i32, i32) {
4696 match &self.box_type {
4697 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
4698 if let Some(size) = get_replaced_element_size_in(node, Some(available_width)) {
4699 return size;
4700 }
4701 match &node.node.node_type {
4702 NodeType::Text(text) => {
4703 let font_size = node
4704 .value("font-size")
4705 .and_then(|v| parse_font_size_u32(&v))
4706 .unwrap_or(16);
4707 let line_height_val = node.value("line-height");
4708 let line_height = parse_line_height(line_height_val, font_size);
4709 let pre_wrap = matches!(
4710 node.value("white-space").as_deref(),
4711 Some("pre-wrap") | Some("pre-line")
4712 );
4713 let break_all = matches!(
4714 node.value("word-break").as_deref(),
4715 Some("break-all")
4716 );
4717 let is_anywhere = matches!(
4720 node.value("overflow-wrap").as_deref(),
4721 Some("anywhere")
4722 ) || matches!(
4723 node.value("word-wrap").as_deref(),
4724 Some("anywhere")
4725 );
4726 let is_break_word = matches!(
4727 node.value("overflow-wrap").as_deref(),
4728 Some("break-word")
4729 ) || matches!(
4730 node.value("word-wrap").as_deref(),
4731 Some("break-word")
4732 ) || matches!(
4733 node.value("word-break").as_deref(),
4734 Some("break-word")
4735 );
4736 let overflow_wrap = if available_width <= 100 {
4737 is_anywhere
4738 } else {
4739 is_anywhere || is_break_word
4740 };
4741 let text = &crate::os_lib::layout::line_break::collapse_white_space(
4744 text,
4745 &node.value("white-space").unwrap_or_default(),
4746 );
4747 let text = if self.trim_leading_space {
4748 text.strip_prefix(' ').unwrap_or(text)
4749 } else {
4750 text.as_str()
4751 };
4752 let (fam, weight) = text_font_of(node);
4753 let (w, h) = crate::kernel::vector_font::get_vector_string_wrapped_size_family(
4754 text,
4755 font_size,
4756 available_width.max(1) as u32,
4757 0, 0, break_all,
4760 overflow_wrap,
4761 pre_wrap,
4762 &fam,
4763 weight,
4764 );
4765 let lines = h as i32 / (font_size as i32 + 4).max(1);
4766 (w as i32, lines * line_height)
4767 }
4768 NodeType::Element { .. } if pseudo_content_size(node).is_some() => {
4769 pseudo_content_size(node).unwrap_or((0, 0))
4770 }
4771 NodeType::Element { .. } => {
4772 let mut max_line_w = 0i32;
4773 let mut total_h = 0i32;
4774 let mut current_line_w = 0i32;
4775 let mut current_line_h = 0i32;
4776 for child in &self.children {
4777 if child.is_inline_line_break() {
4778 if current_line_w > max_line_w {
4779 max_line_w = current_line_w;
4780 }
4781 total_h += current_line_h.max(20);
4782 current_line_w = 0;
4783 current_line_h = 0;
4784 continue;
4785 }
4786 let (cw, ch) = child.intrinsic_inline_size(available_width.max(1));
4787 if current_line_w > 0 && current_line_w + cw > available_width.max(1) {
4788 if current_line_w > max_line_w {
4789 max_line_w = current_line_w;
4790 }
4791 total_h += current_line_h.max(20);
4792 current_line_w = 0;
4793 current_line_h = 0;
4794 }
4795 current_line_w += cw;
4796 if ch > current_line_h {
4797 current_line_h = ch;
4798 }
4799 }
4800 if current_line_w > max_line_w {
4801 max_line_w = current_line_w;
4802 }
4803 total_h += current_line_h.max(20);
4804 if total_h == 0 {
4805 let font_size = node
4806 .value("font-size")
4807 .and_then(|v| parse_font_size_u32(&v))
4808 .unwrap_or(16);
4809 return (8, (font_size as i32 + 4).max(12));
4810 }
4811 (max_line_w.max(1), total_h.max(1))
4812 }
4813 }
4814 }
4815 _ => (0, 0),
4816 }
4817 }
4818
4819 fn is_inline_line_break(&self) -> bool {
4820 match &self.box_type {
4821 BoxType::InlineNode(node) | BoxType::InlineBlockNode(node) => {
4822 matches!(&node.node.node_type, NodeType::Element { tag_name, .. } if tag_name == "br")
4823 }
4824 _ => false,
4825 }
4826 }
4827
4828 fn apply_box_model_styles_with_base(&mut self, containing_width: i32) {
4829 let node = match &self.box_type {
4834 BoxType::BlockNode(n)
4835 | BoxType::InlineBlockNode(n)
4836 | BoxType::FlexNode(n)
4837 | BoxType::GridNode(n)
4838 | BoxType::TableNode(n) => n,
4839 BoxType::InlineNode(n) if self.is_replaced_inline() => n,
4840 _ => return,
4841 };
4842 let (m, p, b) = style_box_edges(node, containing_width, &self.dimensions);
4843 self.dimensions.margin = m;
4844 self.dimensions.padding = p;
4845 self.dimensions.border = b;
4846 }
4847
4848 fn apply_box_model_styles(&mut self) {
4849 self.apply_box_model_styles_with_base(0);
4850 }
4851}
4852