1use serde::{Deserialize, Serialize};
4
5use crate::style::{ColorRef, Fill, Line};
6use crate::text::{Anchor, TextBody};
7
8#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
9#[serde(default, rename_all = "camelCase")]
10pub struct Cell {
11 pub text: TextBody,
12 pub fill: Option<Fill>,
13 pub borders: [Option<Line>; 4],
15 pub diag_down: Option<Line>,
16 pub diag_up: Option<Line>,
17 pub margins: Option<[f64; 4]>,
19 pub anchor: Option<Anchor>,
20 pub grid_span: u32,
21 pub row_span: u32,
22 pub h_merge: bool,
24 pub v_merge: bool,
25 pub vertical: Option<crate::text::TextDir>,
26}
27
28impl Cell {
29 pub fn new(text: &str) -> Self {
30 Cell {
31 text: TextBody::from_text(text),
32 grid_span: 1,
33 row_span: 1,
34 ..Default::default()
35 }
36 }
37 pub fn is_covered(&self) -> bool {
38 self.h_merge || self.v_merge
39 }
40}
41
42#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
43#[serde(default, rename_all = "camelCase")]
44pub struct Row {
45 pub height: f64,
46 pub cells: Vec<Cell>,
47}
48
49#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
50#[serde(default, rename_all = "camelCase")]
51pub struct Table {
52 pub cols: Vec<f64>,
54 pub rows: Vec<Row>,
55 pub style: String,
57 pub first_row: bool,
58 pub first_col: bool,
59 pub last_row: bool,
60 pub last_col: bool,
61 pub band_row: bool,
62 pub band_col: bool,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub background: Option<Fill>,
66}
67
68impl Default for Table {
69 fn default() -> Self {
70 Table {
71 cols: vec![],
72 rows: vec![],
73 style: "medium2-accent1".into(),
74 first_row: true,
75 first_col: false,
76 last_row: false,
77 last_col: false,
78 band_row: true,
79 band_col: false,
80 background: None,
81 }
82 }
83}
84
85impl Table {
86 pub fn new(rows: usize, cols: usize, width: f64, row_height: f64) -> Self {
87 let rows = rows.clamp(1, 1000);
88 let cols = cols.clamp(1, 1000);
89 let cw = if cols > 0 { width / cols as f64 } else { width };
90 Table {
91 cols: vec![cw; cols],
92 rows: (0..rows)
93 .map(|_| Row {
94 height: row_height,
95 cells: (0..cols).map(|_| Cell::new("")).collect(),
96 })
97 .collect(),
98 ..Default::default()
99 }
100 }
101 pub fn n_rows(&self) -> usize {
102 self.rows.len()
103 }
104 pub fn n_cols(&self) -> usize {
105 self.cols.len()
106 }
107 pub fn cell(&self, r: usize, c: usize) -> Option<&Cell> {
108 self.rows.get(r).and_then(|row| row.cells.get(c))
109 }
110 pub fn cell_mut(&mut self, r: usize, c: usize) -> Option<&mut Cell> {
111 self.rows.get_mut(r).and_then(|row| row.cells.get_mut(c))
112 }
113 pub fn width(&self) -> f64 {
114 self.cols.iter().sum()
115 }
116 pub fn height(&self) -> f64 {
117 self.rows.iter().map(|r| r.height).sum()
118 }
119 pub fn insert_row(&mut self, at: usize) {
120 let at = at.min(self.rows.len());
121 let h = self
122 .rows
123 .get(at.saturating_sub(1))
124 .map(|r| r.height)
125 .unwrap_or(30.0);
126 self.rows.insert(
127 at,
128 Row {
129 height: h,
130 cells: (0..self.cols.len()).map(|_| Cell::new("")).collect(),
131 },
132 );
133 }
134 pub fn insert_col(&mut self, at: usize) {
135 let at = at.min(self.cols.len());
136 let w = self.cols.get(at.saturating_sub(1)).copied().unwrap_or(80.0);
137 self.cols.insert(at, w);
138 for r in &mut self.rows {
139 let i = at.min(r.cells.len());
140 r.cells.insert(i, Cell::new(""));
141 }
142 }
143 pub fn delete_row(&mut self, at: usize) -> bool {
144 if self.rows.len() <= 1 || at >= self.rows.len() {
145 return false;
146 }
147 self.rows.remove(at);
148 true
149 }
150 pub fn delete_col(&mut self, at: usize) -> bool {
151 if self.cols.len() <= 1 || at >= self.cols.len() {
152 return false;
153 }
154 self.cols.remove(at);
155 for r in &mut self.rows {
156 if at < r.cells.len() {
157 r.cells.remove(at);
158 }
159 }
160 true
161 }
162 pub fn merge(&mut self, r0: usize, c0: usize, r1: usize, c1: usize) -> bool {
164 let (r0, r1) = (r0.min(r1), r0.max(r1));
165 let (c0, c1) = (c0.min(c1), c0.max(c1));
166 if r1 >= self.rows.len() || c1 >= self.cols.len() || (r0 == r1 && c0 == c1) {
167 return false;
168 }
169 let mut text = vec![];
170 for r in r0..=r1 {
171 for c in c0..=c1 {
172 if let Some(cell) = self.cell_mut(r, c) {
173 if !cell.text.is_empty() {
174 text.append(&mut cell.text.paragraphs);
175 }
176 cell.h_merge = c > c0;
177 cell.v_merge = r > r0;
178 cell.grid_span = 1;
179 cell.row_span = 1;
180 }
181 }
182 }
183 if let Some(cell) = self.cell_mut(r0, c0) {
184 cell.grid_span = (c1 - c0 + 1) as u32;
185 cell.row_span = (r1 - r0 + 1) as u32;
186 cell.h_merge = false;
187 cell.v_merge = false;
188 if !text.is_empty() {
189 cell.text.paragraphs = text;
190 }
191 }
192 true
193 }
194 pub fn split(&mut self, r: usize, c: usize) -> bool {
196 let Some(cell) = self.cell(r, c) else {
197 return false;
198 };
199 let (gs, rs) = (
200 cell.grid_span.max(1) as usize,
201 cell.row_span.max(1) as usize,
202 );
203 if gs == 1 && rs == 1 {
204 return false;
205 }
206 for rr in r..r + rs {
207 for cc in c..c + gs {
208 if let Some(x) = self.cell_mut(rr, cc) {
209 x.h_merge = false;
210 x.v_merge = false;
211 x.grid_span = 1;
212 x.row_span = 1;
213 }
214 }
215 }
216 true
217 }
218 pub fn normalize(&mut self) {
220 let n = self.cols.len();
221 for r in &mut self.rows {
222 r.cells.resize_with(n, || Cell::new(""));
223 for c in &mut r.cells {
224 c.grid_span = c.grid_span.max(1);
225 c.row_span = c.row_span.max(1);
226 }
227 }
228 }
229}
230
231#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
235#[serde(default, rename_all = "camelCase")]
236pub struct TableStyleDef {
237 pub id: String,
239 pub name: String,
240 pub parts: Vec<(TablePart, TablePartStyle)>,
241 pub background: Option<TablePartStyle>,
243 #[serde(skip_serializing_if = "String::is_empty")]
245 pub raw: String,
246}
247
248impl TableStyleDef {
249 pub fn part(&self, p: TablePart) -> Option<&TablePartStyle> {
250 self.parts.iter().find(|(k, _)| *k == p).map(|(_, s)| s)
251 }
252}
253
254#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
256#[serde(rename_all = "camelCase")]
257pub enum TablePart {
258 WholeTbl,
259 Band1H,
260 Band2H,
261 Band1V,
262 Band2V,
263 LastCol,
264 FirstCol,
265 LastRow,
266 FirstRow,
267 SeCell,
268 SwCell,
269 NeCell,
270 NwCell,
271}
272
273impl TablePart {
274 pub const ALL: [TablePart; 13] = [
275 TablePart::WholeTbl,
276 TablePart::Band1H,
277 TablePart::Band2H,
278 TablePart::Band1V,
279 TablePart::Band2V,
280 TablePart::LastCol,
281 TablePart::FirstCol,
282 TablePart::LastRow,
283 TablePart::FirstRow,
284 TablePart::SeCell,
285 TablePart::SwCell,
286 TablePart::NeCell,
287 TablePart::NwCell,
288 ];
289
290 pub fn xml(self) -> &'static str {
291 match self {
292 TablePart::WholeTbl => "wholeTbl",
293 TablePart::Band1H => "band1H",
294 TablePart::Band2H => "band2H",
295 TablePart::Band1V => "band1V",
296 TablePart::Band2V => "band2V",
297 TablePart::LastCol => "lastCol",
298 TablePart::FirstCol => "firstCol",
299 TablePart::LastRow => "lastRow",
300 TablePart::FirstRow => "firstRow",
301 TablePart::SeCell => "seCell",
302 TablePart::SwCell => "swCell",
303 TablePart::NeCell => "neCell",
304 TablePart::NwCell => "nwCell",
305 }
306 }
307
308 pub fn from_xml(s: &str) -> Option<TablePart> {
309 TablePart::ALL.into_iter().find(|p| p.xml() == s)
310 }
311}
312
313#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
316#[serde(default, rename_all = "camelCase")]
317pub struct TablePartStyle {
318 pub fill: Option<Fill>,
319 pub fill_ref: Option<(u32, ColorRef)>,
320 pub borders: [Option<TableBorder>; 6],
322 pub text_color: Option<ColorRef>,
323 pub bold: Option<bool>,
324 pub italic: Option<bool>,
325}
326
327#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
329#[serde(rename_all = "camelCase", tag = "kind")]
330pub enum TableBorder {
331 Line { line: Line },
332 Ref { idx: u32, color: ColorRef },
333}
334
335pub fn table_styles() -> Vec<(String, String)> {
337 let mut v = vec![
338 ("none".to_string(), "No Style, No Grid".to_string()),
339 ("grid".to_string(), "No Style, Table Grid".to_string()),
340 ];
341 for (kind, label) in [
342 ("light1", "Light Style 1"),
343 ("light2", "Light Style 2"),
344 ("medium2", "Medium Style 2"),
345 ("medium4", "Medium Style 4"),
346 ("dark1", "Dark Style 1"),
347 ] {
348 v.push((format!("{kind}-tx1"), label.to_string()));
349 for a in 1..=6 {
350 v.push((format!("{kind}-accent{a}"), format!("{label} - Accent {a}")));
351 }
352 }
353 v
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn insert_delete_merge_split() {
362 let mut t = Table::new(3, 3, 300.0, 30.0);
363 assert_eq!((t.n_rows(), t.n_cols()), (3, 3));
364 t.insert_row(1);
365 t.insert_col(3);
366 assert_eq!((t.n_rows(), t.n_cols()), (4, 4));
367 assert!(t.delete_row(0) && t.delete_col(0));
368 assert_eq!((t.n_rows(), t.n_cols()), (3, 3));
369 assert!(t.merge(0, 0, 1, 1));
370 assert_eq!(
371 t.cell(0, 0).map(|c| (c.grid_span, c.row_span)),
372 Some((2, 2))
373 );
374 assert!(t.cell(1, 1).is_some_and(|c| c.is_covered()));
375 assert!(t.split(0, 0));
376 assert!(!t.cell(1, 1).is_some_and(|c| c.is_covered()));
377 assert!(!t.merge(5, 5, 6, 6));
378 assert!(!t.delete_row(99));
379 let mut one = Table::new(1, 1, 10.0, 10.0);
380 assert!(!one.delete_row(0) && !one.delete_col(0));
381 assert!(table_styles().len() > 30);
382 }
383}