1use crate::box_model::BoxModel;
7use crate::fragment::{Fragment, FragmentKind};
8use crate::geometry::{Point, Size};
9use crate::layout::{self, LayoutContext};
10use crate::style::*;
11use crate::tree::NodeId;
12
13pub fn layout_table(
15 ctx: &LayoutContext,
16 node: NodeId,
17 containing_block_width: f32,
18 containing_block_height: f32,
19) -> Fragment {
20 let style = ctx.tree.style(node);
21 let mut fragment = Fragment::new(node, FragmentKind::Box);
22
23 let border = BoxModel::resolve_border(style);
25 let padding = BoxModel::resolve_padding(style, containing_block_width);
26 let margin = BoxModel::resolve_margin(style, containing_block_width);
27
28 fragment.border = border;
29 fragment.padding = padding;
30 fragment.margin = margin;
31
32 let content_width = match style.width.resolve(containing_block_width) {
33 Some(mut w) => {
34 if style.box_sizing == BoxSizing::BorderBox {
35 w = (w - border.horizontal() - padding.horizontal()).max(0.0);
36 }
37 w
38 }
39 None => (containing_block_width
40 - border.horizontal()
41 - padding.horizontal()
42 - margin.horizontal())
43 .max(0.0),
44 };
45
46 let mut table = TableStructure::new();
48 collect_table_structure(ctx, node, &mut table);
49
50 let is_fixed = style.table_layout == TableLayout::Fixed;
51 let is_collapse = style.border_collapse == BorderCollapse::Collapse;
52 let border_spacing = if is_collapse {
53 0.0
54 } else {
55 style.border_spacing
56 };
57
58 let col_widths = if is_fixed {
60 fixed_table_layout(&table, content_width, border_spacing, ctx)
61 } else {
62 auto_table_layout(&table, content_width, border_spacing, ctx)
63 };
64
65 let num_cols = col_widths.len();
66 let total_spacing = border_spacing * (num_cols + 1) as f32;
67 let table_content_width = col_widths.iter().sum::<f32>() + total_spacing;
68
69 let mut cursor_y = 0.0f32;
71 if style.caption_side == CaptionSide::Top {
72 for &caption_node in &table.captions {
73 let mut cap_frag =
74 layout::layout_node(ctx, caption_node, content_width, containing_block_height);
75 cap_frag.position = Point::new(cap_frag.margin.left, cursor_y);
76 cursor_y += cap_frag.border_box().height + cap_frag.margin.vertical();
77 fragment.children.push(cap_frag);
78 }
79 }
80
81 for row in table.rows.iter() {
83 let mut row_height: f32 = 0.0;
84 let mut cell_fragments: Vec<(usize, Fragment)> = Vec::new();
85
86 for cell in &row.cells {
87 let col_idx = cell.col_start;
88 let col_span = cell.col_span;
89
90 let mut cell_width = 0.0f32;
92 for c in col_idx..(col_idx + col_span).min(num_cols) {
93 cell_width += col_widths[c];
94 if c > col_idx {
95 cell_width += border_spacing;
96 }
97 }
98
99 let cell_frag =
101 layout::layout_node(ctx, cell.node, cell_width, containing_block_height);
102
103 let cell_total_height = cell_frag.border_box().height;
104 row_height = row_height.max(cell_total_height);
105
106 cell_fragments.push((col_idx, cell_frag));
107 }
108
109 if let Some(row_node) = row.node {
111 let row_style = ctx.tree.style(row_node);
112 if let Some(h) = row_style.height.resolve(containing_block_height) {
113 row_height = row_height.max(h);
114 }
115 }
116
117 for (col_idx, mut cell_frag) in cell_fragments {
119 let x = col_position(&col_widths, col_idx, border_spacing);
120 cell_frag.position = Point::new(x, cursor_y);
121
122 fragment.children.push(cell_frag);
125 }
126
127 cursor_y += row_height + border_spacing;
128 }
129
130 if style.caption_side == CaptionSide::Bottom {
132 for &caption_node in &table.captions {
133 let mut cap_frag =
134 layout::layout_node(ctx, caption_node, content_width, containing_block_height);
135 cap_frag.position = Point::new(cap_frag.margin.left, cursor_y);
136 cursor_y += cap_frag.border_box().height + cap_frag.margin.vertical();
137 fragment.children.push(cap_frag);
138 }
139 }
140
141 let final_height = style
142 .height
143 .resolve(containing_block_height)
144 .unwrap_or(cursor_y);
145 let min_h = style.min_height.resolve(containing_block_height);
146 let max_h = style
147 .max_height
148 .resolve(containing_block_height)
149 .unwrap_or(f32::INFINITY);
150
151 fragment.size = Size::new(
152 table_content_width.max(content_width),
153 final_height.max(min_h).min(max_h),
154 );
155
156 fragment
157}
158
159struct TableStructure {
162 rows: Vec<TableRow>,
163 captions: Vec<NodeId>,
164 column_specs: Vec<ColumnSpec>,
165}
166
167struct TableRow {
168 node: Option<NodeId>,
169 cells: Vec<TableCell>,
170}
171
172struct TableCell {
173 node: NodeId,
174 col_start: usize,
175 col_span: usize,
176}
177
178struct ColumnSpec {
179 width: Option<f32>,
180}
181
182impl TableStructure {
183 fn new() -> Self {
184 Self {
185 rows: Vec::new(),
186 captions: Vec::new(),
187 column_specs: Vec::new(),
188 }
189 }
190
191 fn num_columns(&self) -> usize {
192 let from_cells = self
193 .rows
194 .iter()
195 .flat_map(|r| &r.cells)
196 .map(|c| c.col_start + c.col_span)
197 .max()
198 .unwrap_or(0);
199 from_cells.max(self.column_specs.len())
200 }
201}
202
203fn collect_table_structure(ctx: &LayoutContext, table_node: NodeId, table: &mut TableStructure) {
204 let children = ctx.tree.children(table_node);
205
206 for &child_id in children {
207 let child_style = ctx.tree.style(child_id);
208
209 match child_style.display.inner {
210 DisplayInner::TableCaption => {
211 table.captions.push(child_id);
212 }
213 DisplayInner::TableRow => {
214 collect_row(ctx, child_id, table);
215 }
216 DisplayInner::TableRowGroup
217 | DisplayInner::TableHeaderGroup
218 | DisplayInner::TableFooterGroup => {
219 let group_children = ctx.tree.children(child_id);
221 for &gc in group_children {
222 let gc_style = ctx.tree.style(gc);
223 if gc_style.display.inner == DisplayInner::TableRow {
224 collect_row(ctx, gc, table);
225 }
226 }
227 }
228 DisplayInner::TableColumn | DisplayInner::TableColumnGroup => {
229 let col_style = ctx.tree.style(child_id);
231 if let Some(w) = col_style.width.resolve(0.0) {
232 table.column_specs.push(ColumnSpec { width: Some(w) });
233 }
234 }
235 DisplayInner::TableCell => {
236 let row = TableRow {
238 node: None,
239 cells: vec![TableCell {
240 node: child_id,
241 col_start: 0,
242 col_span: 1,
243 }],
244 };
245 table.rows.push(row);
246 }
247 _ => {
248 }
250 }
251 }
252}
253
254fn collect_row(ctx: &LayoutContext, row_node: NodeId, table: &mut TableStructure) {
255 let children = ctx.tree.children(row_node);
256 let mut cells = Vec::new();
257 let mut col = 0;
258
259 for &child_id in children {
260 let child_style = ctx.tree.style(child_id);
261 if child_style.display.is_none() {
262 continue;
263 }
264
265 cells.push(TableCell {
266 node: child_id,
267 col_start: col,
268 col_span: 1, });
270 col += 1;
271 }
272
273 table.rows.push(TableRow {
274 node: Some(row_node),
275 cells,
276 });
277}
278
279fn fixed_table_layout(
282 table: &TableStructure,
283 table_width: f32,
284 border_spacing: f32,
285 ctx: &LayoutContext,
286) -> Vec<f32> {
287 let num_cols = table.num_columns().max(1);
288 let total_spacing = border_spacing * (num_cols + 1) as f32;
289 let available = (table_width - total_spacing).max(0.0);
290
291 let mut widths = vec![0.0f32; num_cols];
292 let mut assigned = vec![false; num_cols];
293
294 for (i, spec) in table.column_specs.iter().enumerate() {
296 if i < num_cols {
297 if let Some(w) = spec.width {
298 widths[i] = w;
299 assigned[i] = true;
300 }
301 }
302 }
303
304 if let Some(first_row) = table.rows.first() {
306 for cell in &first_row.cells {
307 if cell.col_start < num_cols && !assigned[cell.col_start] {
308 let cell_style = ctx.tree.style(cell.node);
309 if let Some(w) = cell_style.width.resolve(available) {
310 widths[cell.col_start] = w;
311 assigned[cell.col_start] = true;
312 }
313 }
314 }
315 }
316
317 let assigned_total: f32 = widths.iter().sum();
319 let remaining = (available - assigned_total).max(0.0);
320 let unassigned_count = assigned.iter().filter(|&&a| !a).count();
321
322 if unassigned_count > 0 {
323 let per_col = remaining / unassigned_count as f32;
324 for i in 0..num_cols {
325 if !assigned[i] {
326 widths[i] = per_col;
327 }
328 }
329 }
330
331 widths
332}
333
334fn auto_table_layout(
337 table: &TableStructure,
338 table_width: f32,
339 border_spacing: f32,
340 ctx: &LayoutContext,
341) -> Vec<f32> {
342 let num_cols = table.num_columns().max(1);
343 let total_spacing = border_spacing * (num_cols + 1) as f32;
344 let available = (table_width - total_spacing).max(0.0);
345
346 let mut min_widths = vec![0.0f32; num_cols];
348 let mut pref_widths = vec![0.0f32; num_cols];
349
350 for row in &table.rows {
351 for cell in &row.cells {
352 if cell.col_span == 1 && cell.col_start < num_cols {
353 let cell_style = ctx.tree.style(cell.node);
354
355 if let Some(w) = cell_style.width.resolve(available) {
357 pref_widths[cell.col_start] = pref_widths[cell.col_start].max(w);
358 min_widths[cell.col_start] = min_widths[cell.col_start].max(w);
359 } else {
360 let min_w = 1.0; let pref_w = available / num_cols as f32;
363 min_widths[cell.col_start] = min_widths[cell.col_start].max(min_w);
364 pref_widths[cell.col_start] = pref_widths[cell.col_start].max(pref_w);
365 }
366 }
367 }
368 }
369
370 let total_pref: f32 = pref_widths.iter().sum();
372
373 if total_pref <= available {
374 let extra = available - total_pref;
376 let per_col = extra / num_cols as f32;
377 let mut result = pref_widths.clone();
378 for w in &mut result {
379 *w += per_col;
380 }
381 result
382 } else {
383 let total_min: f32 = min_widths.iter().sum();
385 if total_min >= available {
386 min_widths
388 } else {
389 let flex_range = total_pref - total_min;
391 let available_range = available - total_min;
392 let factor = if flex_range > 0.0 {
393 available_range / flex_range
394 } else {
395 0.0
396 };
397
398 let mut result = Vec::with_capacity(num_cols);
399 for i in 0..num_cols {
400 let w = min_widths[i] + (pref_widths[i] - min_widths[i]) * factor;
401 result.push(w);
402 }
403 result
404 }
405 }
406}
407
408fn col_position(col_widths: &[f32], col_idx: usize, border_spacing: f32) -> f32 {
409 let mut x = border_spacing;
410 for i in 0..col_idx {
411 x += col_widths[i] + border_spacing;
412 }
413 x
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419 use crate::layout::{compute_layout, FixedWidthTextMeasure};
420 use crate::style::ComputedStyle;
421 use crate::tree::BoxTreeBuilder;
422 use crate::values::LengthPercentageAuto;
423
424 fn make_table_style() -> ComputedStyle {
425 ComputedStyle {
426 display: Display::TABLE,
427 ..ComputedStyle::block()
428 }
429 }
430
431 fn make_row_style() -> ComputedStyle {
432 ComputedStyle {
433 display: Display::TABLE_ROW,
434 ..ComputedStyle::block()
435 }
436 }
437
438 fn make_cell_style() -> ComputedStyle {
439 let mut s = ComputedStyle {
440 display: Display::TABLE_CELL,
441 ..ComputedStyle::block()
442 };
443 s.height = LengthPercentageAuto::px(30.0);
444 s
445 }
446
447 #[test]
448 fn test_simple_table_2x2() {
449 let mut builder = BoxTreeBuilder::new();
450 let root = builder.root(make_table_style());
451
452 let row1 = builder.element(root, make_row_style());
453 builder.element(row1, make_cell_style());
454 builder.element(row1, make_cell_style());
455
456 let row2 = builder.element(root, make_row_style());
457 builder.element(row2, make_cell_style());
458 builder.element(row2, make_cell_style());
459
460 let tree = builder.build();
461 let result = compute_layout(&tree, &FixedWidthTextMeasure, Size::new(800.0, 600.0));
462
463 let root_layout = result.bounding_rect(tree.root()).unwrap();
464 assert!(root_layout.width >= 800.0);
465 assert!(root_layout.height > 0.0);
466 }
467
468 #[test]
469 fn test_col_position() {
470 let widths = vec![100.0, 200.0, 150.0];
471 assert_eq!(col_position(&widths, 0, 5.0), 5.0);
472 assert_eq!(col_position(&widths, 1, 5.0), 110.0); assert_eq!(col_position(&widths, 2, 5.0), 315.0); }
475}