lightweight_pdf_layout/layoutable/
row.rs1use super::shared::{coerce_to_fit_and_warn, finish_fit, measure_at_width, resolve_auto_size, resolve_bound};
8use super::{LayoutCtx, LayoutResult, Layoutable};
9use crate::geometry::{Constraints, Rect, Size};
10use crate::render_node::align_offset;
11use crate::warnings::LayoutWarning;
12use lightweight_pdf_core::{Element, Row};
13
14impl Layoutable for Row {
15 fn measure(&self, ctx: &LayoutCtx, constraints: Constraints) -> Size {
16 let (width, inner_width) = resolve_bound(self.common.width, constraints.max_width, self.common.padding);
17 let (natural_heights, used_width) = row_natural_layout(self, ctx, inner_width);
18 let height = resolve_auto_size(
19 self.common.height,
20 natural_heights.iter().cloned().fold(0.0f32, f32::max),
21 self.common.padding,
22 );
23 Size {
24 width: self.common.width.unwrap_or((used_width + 2.0 * self.common.padding).min(width)),
25 height,
26 }
27 }
28
29 fn layout(&self, ctx: &LayoutCtx, area: Rect, warnings: &mut Vec<LayoutWarning>, page: usize) -> LayoutResult {
30 let inner = area.shrink(self.common.padding);
31 let resolved_widths = resolve_row_widths(self, ctx, inner.width);
32
33 let mut children_nodes = Vec::with_capacity(self.children.len());
34 let mut cursor_x = inner.x;
35 let mut max_child_height = 0.0f32;
36 let mut child_sizes = Vec::with_capacity(self.children.len());
37
38 for (child, w) in self.children.iter().zip(resolved_widths.iter()) {
39 if let Element::Spacer(s) = child {
40 cursor_x += s.size + self.gap;
41 continue;
42 }
43 let size = measure_at_width(ctx, child, *w);
44 max_child_height = max_child_height.max(size.height);
45 child_sizes.push((cursor_x, *w, size.height));
46 cursor_x += w + self.gap;
47 }
48
49 let row_height = self.common.height.unwrap_or(max_child_height).max(0.0);
50 let bounded_row_height = row_height.min(inner.height.max(row_height));
51
52 let mut idx = 0;
53 for child in self.children.iter() {
54 if matches!(child, Element::Spacer(_)) {
55 continue;
56 }
57 let (x, w, h) = child_sizes[idx];
58 idx += 1;
59 let y_offset = align_offset(self.align, bounded_row_height, h);
60 let child_area = Rect {
61 x,
62 y: inner.y + y_offset,
63 width: w,
64 height: h,
65 };
66 let result = child.layout(ctx, child_area, warnings, page);
69 children_nodes.push(coerce_to_fit_and_warn(
70 result,
71 warnings,
72 page,
73 "Row child taller than available space",
74 ));
75 }
76
77 finish_fit(&self.common, area, bounded_row_height, children_nodes)
78 }
79}
80
81fn row_natural_layout(row: &Row, ctx: &LayoutCtx, bound_width: f32) -> (Vec<f32>, f32) {
84 let mut heights = Vec::new();
85 let mut used = 0.0f32;
86 let n = row.children.len();
87 for (i, child) in row.children.iter().enumerate() {
88 if let Element::Spacer(s) = child {
89 used += s.size;
90 } else {
91 let size = measure_at_width(ctx, child, bound_width);
92 heights.push(size.height);
93 used += size.width;
94 }
95 if i + 1 < n {
96 used += row.gap;
97 }
98 }
99 (heights, used)
100}
101
102fn resolve_row_widths(row: &Row, ctx: &LayoutCtx, bound_width: f32) -> Vec<f32> {
105 let n = row.children.len();
106 let gaps = if n > 0 { (n - 1) as f32 * row.gap } else { 0.0 };
107 let mut natural = vec![0.0f32; n];
108 let mut flex_sum = 0.0f32;
109 let mut fixed_total = 0.0f32;
110
111 for (i, child) in row.children.iter().enumerate() {
112 if let Element::Spacer(s) = child {
113 natural[i] = s.size;
114 fixed_total += s.size;
115 continue;
116 }
117 let common = child.common();
118 if let Some(f) = common.and_then(|c| c.flex) {
119 flex_sum += f;
120 continue;
121 }
122 let size = measure_at_width(ctx, child, bound_width);
123 natural[i] = size.width;
124 fixed_total += size.width;
125 }
126
127 let leftover = (bound_width - fixed_total - gaps).max(0.0);
128 if flex_sum > 0.0 {
129 for (i, child) in row.children.iter().enumerate() {
130 if let Some(f) = child.common().and_then(|c| c.flex) {
131 natural[i] = leftover * (f / flex_sum);
132 }
133 }
134 }
135 natural
136}