Skip to main content

fop_layout/layout/table/
grid.rs

1//! Grid creation, cell placement, and table-level layout
2
3use crate::area::{Area, AreaId, AreaTree, AreaType};
4use fop_types::{Length, Point, Rect, Result, Size};
5
6use super::types::{BorderCollapse, GridCell, TableLayout};
7
8impl TableLayout {
9    /// Create table grid from rows and cells
10    pub fn create_grid(&self, rows: usize, cols: usize) -> Vec<Vec<Option<GridCell>>> {
11        vec![vec![None; cols]; rows]
12    }
13
14    /// Place a cell in the grid
15    pub fn place_cell(&self, grid: &mut [Vec<Option<GridCell>>], cell: GridCell) -> Result<()> {
16        let rows = grid.len();
17        let cols = if rows > 0 { grid[0].len() } else { 0 };
18
19        // Check bounds
20        if cell.row >= rows || cell.col >= cols {
21            return Err(fop_types::FopError::Generic(
22                "Cell position out of bounds".to_string(),
23            ));
24        }
25
26        // Place cell and mark spanned cells
27        #[allow(clippy::needless_range_loop)]
28        for r in cell.row..(cell.row + cell.rowspan).min(rows) {
29            for c in cell.col..(cell.col + cell.colspan).min(cols) {
30                if r == cell.row && c == cell.col {
31                    grid[r][c] = Some(cell.clone());
32                } else {
33                    // Mark as occupied by span
34                    grid[r][c] = Some(GridCell {
35                        row: cell.row,
36                        col: cell.col,
37                        rowspan: 0, // Marker: this is a spanned cell
38                        colspan: 0,
39                        content_id: None,
40                    });
41                }
42            }
43        }
44
45        Ok(())
46    }
47
48    /// Layout table into area tree
49    pub fn layout_table(
50        &self,
51        area_tree: &mut AreaTree,
52        column_widths: &[Length],
53        grid: &[Vec<Option<GridCell>>],
54        y_offset: Length,
55    ) -> Result<AreaId> {
56        let rows = grid.len();
57        let cols = if rows > 0 { grid[0].len() } else { 0 };
58
59        // Calculate table dimensions based on border-collapse model
60        let table_width: Length = column_widths
61            .iter()
62            .copied()
63            .fold(Length::ZERO, |acc, len| acc + len);
64
65        // For collapsed borders, no spacing between cells
66        let total_spacing = match self.border_collapse {
67            BorderCollapse::Separate => self.border_spacing * (cols + 1) as i32,
68            BorderCollapse::Collapse => Length::ZERO,
69        };
70        let total_width = table_width + total_spacing;
71
72        // Create table area
73        let table_rect = Rect::from_point_size(
74            Point::new(Length::ZERO, y_offset),
75            Size::new(total_width, Length::from_pt(100.0)), // Height calculated later
76        );
77        let table_area = Area::new(AreaType::Block, table_rect);
78        let table_id = area_tree.add_area(table_area);
79
80        // Layout cells with appropriate spacing
81        let (initial_x, initial_y, cell_spacing_x, cell_spacing_y) = match self.border_collapse {
82            BorderCollapse::Separate => (
83                self.border_spacing,
84                self.border_spacing,
85                self.border_spacing,
86                self.border_spacing,
87            ),
88            BorderCollapse::Collapse => (Length::ZERO, Length::ZERO, Length::ZERO, Length::ZERO),
89        };
90
91        let mut current_y = initial_y;
92        for row in grid {
93            let mut current_x = initial_x;
94            let row_height = Length::from_pt(20.0); // Default row height
95
96            for (col_idx, cell_opt) in row.iter().enumerate() {
97                if let Some(cell) = cell_opt {
98                    // Only process actual cells (not span markers)
99                    if cell.rowspan > 0 && cell.colspan > 0 {
100                        let cell_width = column_widths[col_idx];
101                        let cell_rect = Rect::from_point_size(
102                            Point::new(current_x, current_y),
103                            Size::new(cell_width, row_height),
104                        );
105                        let cell_area = Area::new(AreaType::Block, cell_rect);
106                        area_tree.add_area(cell_area);
107                    }
108                }
109
110                if col_idx < column_widths.len() {
111                    current_x += column_widths[col_idx] + cell_spacing_x;
112                }
113            }
114
115            current_y += row_height + cell_spacing_y;
116        }
117
118        Ok(table_id)
119    }
120}