Skip to main content

text_document/
text_table.rs

1//! Read-only table and table cell handles.
2
3use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use frontend::commands::{block_commands, frame_commands, table_cell_commands, table_commands};
8use frontend::common::types::EntityId;
9
10use crate::convert::to_usize;
11use crate::flow::{BlockSnapshot, CellFormat, TableFormat, TableSnapshot};
12use crate::inner::TextDocumentInner;
13use crate::text_block::TextBlock;
14use crate::text_frame::{cell_dto_to_format, table_dto_to_format};
15
16/// A read-only handle to a table in the document.
17///
18/// Obtained from [`FlowElement::Table`](crate::FlowElement::Table) during flow traversal.
19#[derive(Clone)]
20pub struct TextTable {
21    pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
22    pub(crate) table_id: usize,
23}
24
25impl TextTable {
26    /// Stable entity ID. O(1).
27    pub fn id(&self) -> usize {
28        self.table_id
29    }
30
31    /// Number of rows. O(1).
32    pub fn rows(&self) -> usize {
33        let inner = self.doc.lock();
34        table_commands::get_table(&inner.ctx, &(self.table_id as u64))
35            .ok()
36            .flatten()
37            .map(|t| to_usize(t.rows))
38            .unwrap_or(0)
39    }
40
41    /// Number of columns. O(1).
42    pub fn columns(&self) -> usize {
43        let inner = self.doc.lock();
44        table_commands::get_table(&inner.ctx, &(self.table_id as u64))
45            .ok()
46            .flatten()
47            .map(|t| to_usize(t.columns))
48            .unwrap_or(0)
49    }
50
51    /// Cell at grid position. O(c) where c = total cells.
52    pub fn cell(&self, row: usize, col: usize) -> Option<TextTableCell> {
53        let inner = self.doc.lock();
54        let table_dto = table_commands::get_table(&inner.ctx, &(self.table_id as u64))
55            .ok()
56            .flatten()?;
57
58        for &cell_id in &table_dto.cells {
59            if let Some(cell_dto) = table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
60                .ok()
61                .flatten()
62                && cell_dto.row as usize == row
63                && cell_dto.column as usize == col
64            {
65                return Some(TextTableCell {
66                    doc: Arc::clone(&self.doc),
67                    cell_id: cell_dto.id as usize,
68                });
69            }
70        }
71        None
72    }
73
74    /// Column widths. O(1).
75    pub fn column_widths(&self) -> Vec<i32> {
76        let inner = self.doc.lock();
77        table_commands::get_table(&inner.ctx, &(self.table_id as u64))
78            .ok()
79            .flatten()
80            .map(|t| t.column_widths.iter().map(|&v| v as i32).collect())
81            .unwrap_or_default()
82    }
83
84    /// Table-level formatting. O(1).
85    pub fn format(&self) -> TableFormat {
86        let inner = self.doc.lock();
87        table_commands::get_table(&inner.ctx, &(self.table_id as u64))
88            .ok()
89            .flatten()
90            .map(|t| table_dto_to_format(&t))
91            .unwrap_or_default()
92    }
93
94    /// All cells with block snapshots. O(c·k).
95    pub fn snapshot(&self) -> TableSnapshot {
96        let inner = self.doc.lock();
97        crate::text_frame::build_table_snapshot(&inner, self.table_id as u64).unwrap_or_else(|| {
98            TableSnapshot {
99                table_id: self.table_id,
100                rows: 0,
101                columns: 0,
102                column_widths: Vec::new(),
103                format: TableFormat::default(),
104                cells: Vec::new(),
105            }
106        })
107    }
108}
109
110/// A read-only handle to a single cell within a table.
111#[derive(Clone)]
112pub struct TextTableCell {
113    pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
114    pub(crate) cell_id: usize,
115}
116
117impl TextTableCell {
118    /// Stable entity ID. O(1).
119    pub fn id(&self) -> usize {
120        self.cell_id
121    }
122
123    /// Cell row index. O(1).
124    pub fn row(&self) -> usize {
125        let inner = self.doc.lock();
126        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
127            .ok()
128            .flatten()
129            .map(|c| to_usize(c.row))
130            .unwrap_or(0)
131    }
132
133    /// Cell column index. O(1).
134    pub fn column(&self) -> usize {
135        let inner = self.doc.lock();
136        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
137            .ok()
138            .flatten()
139            .map(|c| to_usize(c.column))
140            .unwrap_or(0)
141    }
142
143    /// Row span. O(1).
144    pub fn row_span(&self) -> usize {
145        let inner = self.doc.lock();
146        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
147            .ok()
148            .flatten()
149            .map(|c| to_usize(c.row_span))
150            .unwrap_or(1)
151    }
152
153    /// Column span. O(1).
154    pub fn column_span(&self) -> usize {
155        let inner = self.doc.lock();
156        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
157            .ok()
158            .flatten()
159            .map(|c| to_usize(c.column_span))
160            .unwrap_or(1)
161    }
162
163    /// Cell-level formatting. O(1).
164    pub fn format(&self) -> CellFormat {
165        let inner = self.doc.lock();
166        table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
167            .ok()
168            .flatten()
169            .map(|c| cell_dto_to_format(&c))
170            .unwrap_or_default()
171    }
172
173    /// Blocks within this cell's frame. Returns empty `Vec` if `cell_frame` is `None`.
174    pub fn blocks(&self) -> Vec<TextBlock> {
175        let inner = self.doc.lock();
176        let cell_dto = match table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
177            .ok()
178            .flatten()
179        {
180            Some(c) => c,
181            None => return Vec::new(),
182        };
183
184        let cell_frame_id = match cell_dto.cell_frame {
185            Some(id) => id,
186            None => return Vec::new(),
187        };
188
189        let frame_dto = match frame_commands::get_frame(&inner.ctx, &(cell_frame_id as EntityId))
190            .ok()
191            .flatten()
192        {
193            Some(f) => f,
194            None => return Vec::new(),
195        };
196
197        // Get blocks sorted by document_position
198        let mut block_dtos: Vec<_> = frame_dto
199            .blocks
200            .iter()
201            .filter_map(|&id| {
202                block_commands::get_block(&inner.ctx, &{ id })
203                    .ok()
204                    .flatten()
205            })
206            .collect();
207        block_dtos.sort_by_key(|b| b.document_position);
208
209        block_dtos
210            .iter()
211            .map(|b| TextBlock {
212                doc: Arc::clone(&self.doc),
213                block_id: b.id as usize,
214            })
215            .collect()
216    }
217
218    /// Snapshot all cell blocks in one lock. Returns empty `Vec` if `cell_frame` is `None`.
219    pub fn snapshot_blocks(&self) -> Vec<BlockSnapshot> {
220        let inner = self.doc.lock();
221        let cell_dto = match table_cell_commands::get_table_cell(&inner.ctx, &(self.cell_id as u64))
222            .ok()
223            .flatten()
224        {
225            Some(c) => c,
226            None => return Vec::new(),
227        };
228
229        match cell_dto.cell_frame {
230            Some(frame_id) => crate::text_block::build_blocks_snapshot_for_frame(&inner, frame_id),
231            None => Vec::new(),
232        }
233    }
234}