Skip to main content

text_typeset/layout/
table.rs

1use crate::font::registry::FontRegistry;
2use crate::layout::block::{BlockLayout, BlockLayoutParams, layout_block};
3use crate::types::{DecorationKind, DecorationRect};
4
5/// Parameters for a table, extracted from text-document's TableSnapshot.
6pub struct TableLayoutParams {
7    pub table_id: usize,
8    pub rows: usize,
9    pub columns: usize,
10    /// Relative column widths (0.0-1.0). If empty, columns are distributed evenly.
11    pub column_widths: Vec<f32>,
12    pub border_width: f32,
13    pub cell_spacing: f32,
14    pub cell_padding: f32,
15    /// One entry per cell, in row-major order (row 0 col 0, row 0 col 1, ...).
16    pub cells: Vec<CellLayoutParams>,
17}
18
19/// Parameters for a single table cell.
20pub struct CellLayoutParams {
21    pub row: usize,
22    pub column: usize,
23    pub blocks: Vec<BlockLayoutParams>,
24    pub background_color: Option<[f32; 4]>,
25}
26
27/// Computed layout for an entire table.
28pub struct TableLayout {
29    pub table_id: usize,
30    /// Y position relative to document start (set by flow).
31    pub y: f32,
32    /// Total height of the table including borders.
33    pub total_height: f32,
34    /// Total width of the table.
35    pub total_width: f32,
36    /// Computed column x positions (left edge of each column's content area).
37    pub column_xs: Vec<f32>,
38    /// Computed column widths (content area, excluding padding).
39    pub column_content_widths: Vec<f32>,
40    /// Computed row y positions (top edge of each row's content area, relative to table top).
41    pub row_ys: Vec<f32>,
42    /// Computed row heights (content area).
43    pub row_heights: Vec<f32>,
44    /// Laid out cells. Each cell contains its block layouts.
45    pub cell_layouts: Vec<CellLayout>,
46    pub border_width: f32,
47    pub cell_padding: f32,
48}
49
50pub struct CellLayout {
51    pub row: usize,
52    pub column: usize,
53    pub blocks: Vec<BlockLayout>,
54    pub background_color: Option<[f32; 4]>,
55}
56
57/// Lay out a table: distribute column widths, lay out cell contents, compute row heights.
58pub fn layout_table(
59    registry: &FontRegistry,
60    params: &TableLayoutParams,
61    available_width: f32,
62) -> TableLayout {
63    let cols = params.columns.max(1);
64    let rows = params.rows.max(1);
65    let border = params.border_width;
66    let padding = params.cell_padding;
67    let spacing = params.cell_spacing;
68
69    // Total overhead: borders + spacing + padding for all columns
70    let total_overhead =
71        border * 2.0 + spacing * (cols as f32 - 1.0).max(0.0) + padding * 2.0 * cols as f32;
72    let content_area = (available_width - total_overhead).max(0.0);
73
74    // Compute column widths
75    let column_content_widths =
76        compute_column_widths(&params.column_widths, cols, content_area, padding);
77
78    // Compute column x positions
79    let mut column_xs = Vec::with_capacity(cols);
80    let mut x = border;
81    for (c, &col_w) in column_content_widths.iter().enumerate() {
82        column_xs.push(x + padding);
83        x += padding * 2.0 + col_w;
84        if c < cols - 1 {
85            x += spacing;
86        }
87    }
88    let total_width = x + border;
89
90    // Lay out each cell's blocks
91    let mut cell_layouts = Vec::new();
92    // Track row heights (max cell content height per row)
93    let mut row_heights = vec![0.0f32; rows];
94
95    for cell_params in &params.cells {
96        let r = cell_params.row;
97        let c = cell_params.column;
98        if c >= cols || r >= rows {
99            continue;
100        }
101
102        let cell_width = column_content_widths[c];
103        let mut cell_blocks = Vec::new();
104        let mut cell_height = 0.0f32;
105
106        for block_params in &cell_params.blocks {
107            let block = layout_block(registry, block_params, cell_width);
108            cell_height += block.height;
109            cell_blocks.push(block);
110        }
111
112        // Position blocks within the cell vertically
113        let mut block_y = 0.0f32;
114        for block in &mut cell_blocks {
115            block.y = block_y;
116            block_y += block.height;
117        }
118
119        row_heights[r] = row_heights[r].max(cell_height);
120
121        cell_layouts.push(CellLayout {
122            row: r,
123            column: c,
124            blocks: cell_blocks,
125            background_color: cell_params.background_color,
126        });
127    }
128
129    // Compute row y positions
130    let mut row_ys = Vec::with_capacity(rows);
131    let mut y = border;
132    for (r, &row_h) in row_heights.iter().enumerate() {
133        row_ys.push(y + padding);
134        y += padding * 2.0 + row_h;
135        if r < rows - 1 {
136            y += spacing;
137        }
138    }
139    let total_height = y + border;
140
141    TableLayout {
142        table_id: params.table_id,
143        y: 0.0, // set by flow
144        total_height,
145        total_width,
146        column_xs,
147        column_content_widths,
148        row_ys,
149        row_heights,
150        cell_layouts,
151        border_width: border,
152        cell_padding: padding,
153    }
154}
155
156fn compute_column_widths(
157    specified: &[f32],
158    cols: usize,
159    content_area: f32,
160    _padding: f32,
161) -> Vec<f32> {
162    if specified.is_empty() || specified.len() != cols {
163        // Distribute evenly
164        let w = content_area / cols as f32;
165        return vec![w; cols];
166    }
167
168    // Use specified proportions
169    let total: f32 = specified.iter().sum();
170    if total <= 0.0 {
171        let w = content_area / cols as f32;
172        return vec![w; cols];
173    }
174
175    specified
176        .iter()
177        .map(|&s| (s / total) * content_area)
178        .collect()
179}
180
181/// Generate border and background decoration rects for a table.
182pub fn generate_table_decorations(table: &TableLayout, scroll_offset: f32) -> Vec<DecorationRect> {
183    let mut decorations = Vec::new();
184    let table_y = table.y - scroll_offset;
185
186    // Outer table border
187    if table.border_width > 0.0 {
188        let bw = table.border_width;
189        let color = [0.6, 0.6, 0.6, 1.0]; // gray border
190        // Top
191        decorations.push(DecorationRect {
192            rect: [0.0, table_y, table.total_width, bw],
193            color,
194            kind: DecorationKind::TableBorder,
195        });
196        // Bottom
197        decorations.push(DecorationRect {
198            rect: [
199                0.0,
200                table_y + table.total_height - bw,
201                table.total_width,
202                bw,
203            ],
204            color,
205            kind: DecorationKind::TableBorder,
206        });
207        // Left
208        decorations.push(DecorationRect {
209            rect: [0.0, table_y, bw, table.total_height],
210            color,
211            kind: DecorationKind::TableBorder,
212        });
213        // Right
214        decorations.push(DecorationRect {
215            rect: [table.total_width - bw, table_y, bw, table.total_height],
216            color,
217            kind: DecorationKind::TableBorder,
218        });
219
220        // Row borders
221        for r in 1..table.row_ys.len() {
222            let row_y = table.row_ys[r] - table.cell_padding;
223            decorations.push(DecorationRect {
224                rect: [0.0, table_y + row_y - bw / 2.0, table.total_width, bw],
225                color,
226                kind: DecorationKind::TableBorder,
227            });
228        }
229
230        // Column borders
231        for c in 1..table.column_xs.len() {
232            let col_x = table.column_xs[c] - table.cell_padding;
233            decorations.push(DecorationRect {
234                rect: [col_x - bw / 2.0, table_y, bw, table.total_height],
235                color,
236                kind: DecorationKind::TableBorder,
237            });
238        }
239    }
240
241    // Cell backgrounds
242    for cell in &table.cell_layouts {
243        if let Some(bg_color) = cell.background_color
244            && cell.row < table.row_ys.len()
245            && cell.column < table.column_xs.len()
246        {
247            let cx = table.column_xs[cell.column] - table.cell_padding;
248            let cy = table.row_ys[cell.row] - table.cell_padding;
249            let cw = table.column_content_widths[cell.column] + table.cell_padding * 2.0;
250            let ch = table.row_heights[cell.row] + table.cell_padding * 2.0;
251            decorations.push(DecorationRect {
252                rect: [cx, table_y + cy, cw, ch],
253                color: bg_color,
254                kind: DecorationKind::TableCellBackground,
255            });
256        }
257    }
258
259    decorations
260}