1use crate::oxml::shape::{Graphic as OxmlGraphic, GraphicFrame as OxmlFrame};
35use crate::oxml::table::{Cell as OxmlCell, Col as OxmlCol, Row as OxmlRow, Table as OxmlTable};
36use crate::oxml::txbody::TextBody;
37use crate::shape::base::Shape;
38use crate::units::Emu;
39
40#[derive(Clone, Debug, Default)]
42pub struct TableShape {
43 pub(crate) frame: OxmlFrame,
45}
46
47impl TableShape {
48 #[allow(clippy::field_reassign_with_default)]
50 pub fn new(rows: usize, cols: usize, col_width: Emu, row_height: Emu) -> Self {
51 let mut t = OxmlTable::default();
52 t.cols = (0..cols).map(|_| OxmlCol { width: col_width }).collect();
53 t.rows = (0..rows)
54 .map(|_| OxmlRow {
55 height: row_height,
56 cells: (0..cols).map(|_| OxmlCell::default()).collect(),
57 header: false,
58 })
59 .collect();
60 let frame = OxmlFrame {
61 graphic: OxmlGraphic::Table(t),
62 ..Default::default()
63 };
64 TableShape { frame }
65 }
66
67 pub fn from_frame(frame: OxmlFrame) -> Self {
69 TableShape { frame }
70 }
71
72 pub fn table(&self) -> &OxmlTable {
74 match &self.frame.graphic {
75 OxmlGraphic::Table(t) => t,
76 _ => unreachable!("TableShape.table(): frame.graphic 不是 Table 变体"),
81 }
82 }
83 pub fn table_mut(&mut self) -> &mut OxmlTable {
85 match &mut self.frame.graphic {
86 OxmlGraphic::Table(t) => t,
87 _ => unreachable!("TableShape.table_mut(): frame.graphic 不是 Table 变体"),
88 }
89 }
90
91 pub fn dims(&self) -> (usize, usize) {
93 let t = self.table();
94 (t.rows.len(), t.cols.len())
95 }
96
97 pub fn first_row(&self) -> bool {
101 self.table().tbl_look.first_row
102 }
103 pub fn set_first_row(&mut self, v: bool) {
105 self.table_mut().tbl_look.first_row = v;
106 }
107
108 pub fn last_row(&self) -> bool {
112 self.table().tbl_look.last_row
113 }
114 pub fn set_last_row(&mut self, v: bool) {
116 self.table_mut().tbl_look.last_row = v;
117 }
118
119 pub fn first_column(&self) -> bool {
123 self.table().tbl_look.first_column
124 }
125 pub fn set_first_column(&mut self, v: bool) {
127 self.table_mut().tbl_look.first_column = v;
128 }
129
130 pub fn last_column(&self) -> bool {
134 self.table().tbl_look.last_column
135 }
136 pub fn set_last_column(&mut self, v: bool) {
138 self.table_mut().tbl_look.last_column = v;
139 }
140
141 pub fn horz_banding(&self) -> bool {
146 !self.table().tbl_look.no_h_band
147 }
148 pub fn set_horz_banding(&mut self, v: bool) {
150 self.table_mut().tbl_look.no_h_band = !v;
151 }
152
153 pub fn vert_banding(&self) -> bool {
158 !self.table().tbl_look.no_v_band
159 }
160 pub fn set_vert_banding(&mut self, v: bool) {
162 self.table_mut().tbl_look.no_v_band = !v;
163 }
164
165 pub fn set_style(&mut self, name: &str) -> bool {
189 self.table_mut().set_style(name)
190 }
191
192 pub fn set_style_id(&mut self, guid: impl Into<String>) {
199 self.table_mut().set_style_id(guid);
200 }
201
202 pub fn table_style(&self) -> Option<&crate::oxml::table::TableStyle> {
204 self.table().table_style.as_ref()
205 }
206
207 pub fn clear_style(&mut self) {
209 self.table_mut().clear_style();
210 }
211
212 pub fn cell_text(&self, row: usize, col: usize) -> Option<String> {
214 let t = self.table();
215 let r = t.rows.get(row)?;
216 let c = r.cells.get(col)?;
217 let mut s = String::new();
218 for p in &c.text.paragraphs {
219 if !s.is_empty() {
220 s.push('\n');
221 }
222 for run in &p.runs {
223 s.push_str(&run.text);
224 }
225 }
226 Some(s)
227 }
228
229 pub fn set_cell_text(&mut self, row: usize, col: usize, text: &str) -> crate::Result<()> {
234 let t = self.table_mut();
235 let r = t
236 .rows
237 .get_mut(row)
238 .ok_or(crate::Error::IndexOutOfRange(row))?;
239 let c = r
240 .cells
241 .get_mut(col)
242 .ok_or(crate::Error::IndexOutOfRange(col))?;
243 let mut tb = TextBody::new();
244 let mut p = crate::oxml::txbody::Paragraph::new();
245 p.runs.push(crate::oxml::txbody::Run::new(text));
246 tb.paragraphs.push(p);
247 c.text = tb;
248 Ok(())
249 }
250
251 pub fn cell_mut(&mut self, row: usize, col: usize) -> &mut OxmlCell {
256 let t = self.table_mut();
257 while t.rows.len() <= row {
259 let ncols = t.cols.len().max(1);
260 t.rows.push(OxmlRow {
261 height: Emu(0),
262 cells: (0..ncols).map(|_| OxmlCell::default()).collect(),
263 header: false,
264 });
265 }
266 let r = &mut t.rows[row];
267 while r.cells.len() <= col {
268 r.cells.push(OxmlCell::default());
269 }
270 &mut r.cells[col]
271 }
272
273 pub fn cell(&self, row: usize, col: usize) -> Option<&OxmlCell> {
275 let t = self.table();
276 t.rows.get(row)?.cells.get(col)
277 }
278
279 pub fn set_column_width(&mut self, col: usize, w: Emu) -> crate::Result<()> {
281 let t = self.table_mut();
282 let c = t
283 .cols
284 .get_mut(col)
285 .ok_or(crate::Error::IndexOutOfRange(col))?;
286 c.width = w;
287 Ok(())
288 }
289
290 pub fn column_width(&self, col: usize) -> Option<Emu> {
292 self.table().cols.get(col).map(|c| c.width)
293 }
294
295 pub fn set_row_height(&mut self, row: usize, h: Emu) -> crate::Result<()> {
297 let t = self.table_mut();
298 let r = t
299 .rows
300 .get_mut(row)
301 .ok_or(crate::Error::IndexOutOfRange(row))?;
302 r.height = h;
303 Ok(())
304 }
305
306 pub fn row_height(&self, row: usize) -> Option<Emu> {
308 self.table().rows.get(row).map(|r| r.height)
309 }
310
311 pub fn set_header_row(&mut self, row: usize, is_header: bool) -> crate::Result<()> {
313 let t = self.table_mut();
314 let r = t
315 .rows
316 .get_mut(row)
317 .ok_or(crate::Error::IndexOutOfRange(row))?;
318 r.header = is_header;
319 Ok(())
320 }
321
322 pub fn row_count(&self) -> usize {
324 self.table().rows.len()
325 }
326 pub fn column_count(&self) -> usize {
328 self.table().cols.len()
329 }
330
331 pub fn row(&self, idx: usize) -> Option<&OxmlRow> {
335 self.table().rows.get(idx)
336 }
337 pub fn row_mut(&mut self, idx: usize) -> Option<&mut OxmlRow> {
339 self.table_mut().rows.get_mut(idx)
340 }
341 pub fn column(&self, idx: usize) -> Option<&OxmlCol> {
343 self.table().cols.get(idx)
344 }
345 pub fn column_mut(&mut self, idx: usize) -> Option<&mut OxmlCol> {
347 self.table_mut().cols.get_mut(idx)
348 }
349
350 pub fn set_cell_fill(
354 &mut self,
355 row: usize,
356 col: usize,
357 c: crate::oxml::color::Color,
358 ) -> crate::Result<()> {
359 let t = self.table_mut();
360 let r = t
361 .rows
362 .get_mut(row)
363 .ok_or(crate::Error::IndexOutOfRange(row))?;
364 let cidx = r
365 .cells
366 .get_mut(col)
367 .ok_or(crate::Error::IndexOutOfRange(col))?;
368 cidx.fill = c;
369 Ok(())
370 }
371
372 pub fn cell_fill(&self, row: usize, col: usize) -> Option<crate::oxml::color::Color> {
374 self.cell(row, col).map(|c| c.fill.clone())
375 }
376
377 pub fn set_cell_margins(
379 &mut self,
380 row: usize,
381 col: usize,
382 top: Emu,
383 left: Emu,
384 bottom: Emu,
385 right: Emu,
386 ) -> crate::Result<()> {
387 let t = self.table_mut();
388 let r = t
389 .rows
390 .get_mut(row)
391 .ok_or(crate::Error::IndexOutOfRange(row))?;
392 let c = r
393 .cells
394 .get_mut(col)
395 .ok_or(crate::Error::IndexOutOfRange(col))?;
396 c.margin = (Some(top), Some(left), Some(bottom), Some(right));
397 Ok(())
398 }
399
400 pub fn cell_text_frame_mut(&mut self, row: usize, col: usize) -> &mut TextBody {
405 &mut self.cell_mut(row, col).text
406 }
407
408 pub fn add_row(&mut self, height: Emu) -> &mut OxmlRow {
412 let t = self.table_mut();
413 let ncols = t.cols.len().max(1);
414 t.rows.push(OxmlRow {
415 height,
416 cells: (0..ncols).map(|_| OxmlCell::default()).collect(),
417 header: false,
418 });
419 let idx = t.rows.len() - 1;
421 &mut t.rows[idx]
422 }
423
424 pub fn add_column(&mut self, width: Emu) -> &mut OxmlCol {
426 let t = self.table_mut();
427 t.cols.push(OxmlCol { width });
428 let idx = t.cols.len() - 1;
430 &mut t.cols[idx]
431 }
432
433 pub fn remove_row(&mut self, idx: usize) -> crate::Result<()> {
443 let t = self.table_mut();
444 if idx >= t.rows.len() {
445 return Err(crate::Error::IndexOutOfRange(idx));
446 }
447 t.rows.remove(idx);
448 Ok(())
449 }
450
451 pub fn remove_column(&mut self, idx: usize) -> crate::Result<()> {
461 let t = self.table_mut();
462 if idx >= t.cols.len() {
463 return Err(crate::Error::IndexOutOfRange(idx));
464 }
465 t.cols.remove(idx);
466 for r in &mut t.rows {
468 if idx < r.cells.len() {
469 r.cells.remove(idx);
470 }
471 }
472 Ok(())
473 }
474
475 pub fn merge_cells(
492 &mut self,
493 row1: usize,
494 col1: usize,
495 row2: usize,
496 col2: usize,
497 ) -> crate::Result<()> {
498 if row2 < row1 || col2 < col1 {
499 return Err(crate::Error::Other(
500 "merge_cells: row2/col2 不能小于 row1/col1".into(),
501 ));
502 }
503 let nrows = self.row_count();
504 let ncols = self.column_count();
505 if row2 >= nrows || col2 >= ncols {
506 return Err(crate::Error::IndexOutOfRange(row2.max(col2)));
507 }
508 let grid_span = (col2 - col1 + 1) as u32;
509 let row_span = (row2 - row1 + 1) as u32;
510 if grid_span == 1 && row_span == 1 {
512 return Ok(());
513 }
514 let t = self.table_mut();
515 {
517 let cell = &mut t.rows[row1].cells[col1];
518 cell.grid_span = grid_span;
519 cell.row_span = row_span;
520 cell.h_merge = false;
521 cell.v_merge = false;
522 }
523 for r in row1..=row2 {
525 for c in col1..=col2 {
526 if r == row1 && c == col1 {
527 continue; }
529 let cell = &mut t.rows[r].cells[c];
530 cell.grid_span = 1;
531 cell.row_span = 1;
532 cell.h_merge = r == row1;
534 cell.v_merge = r != row1;
536 }
537 }
538 Ok(())
539 }
540
541 pub fn split_cell(&mut self, row: usize, col: usize) -> crate::Result<()> {
561 let nrows = self.row_count();
562 let ncols = self.column_count();
563 if row >= nrows || col >= ncols {
564 return Err(crate::Error::IndexOutOfRange(row.max(col)));
565 }
566 let t = self.table_mut();
567 let cell = &t.rows[row].cells[col];
568 if cell.h_merge || cell.v_merge {
570 return Err(crate::Error::Other(
571 "split_cell: 目标单元格是虚拟单元格,请对合并源调用 split_cell".into(),
572 ));
573 }
574 let grid_span = cell.grid_span;
575 let row_span = cell.row_span;
576 if grid_span <= 1 && row_span <= 1 {
578 return Err(crate::Error::Other(
579 "split_cell: 目标单元格不是合并源(grid_span 和 row_span 均为 1)".into(),
580 ));
581 }
582 let row2 = row + row_span as usize - 1;
584 let col2 = col + grid_span as usize - 1;
585 {
587 let cell = &mut t.rows[row].cells[col];
588 cell.grid_span = 1;
589 cell.row_span = 1;
590 cell.h_merge = false;
591 cell.v_merge = false;
592 }
593 for r in row..=row2 {
595 for c in col..=col2 {
596 if r == row && c == col {
597 continue; }
599 let cell = &mut t.rows[r].cells[c];
600 cell.grid_span = 1;
601 cell.row_span = 1;
602 cell.h_merge = false;
603 cell.v_merge = false;
604 }
605 }
606 Ok(())
607 }
608
609 pub fn set_cell_border(
623 &mut self,
624 row: usize,
625 col: usize,
626 side: BorderSide,
627 width: Emu,
628 color: crate::oxml::color::Color,
629 no_fill: bool,
630 ) -> crate::Result<()> {
631 let t = self.table_mut();
632 let r = t
633 .rows
634 .get_mut(row)
635 .ok_or(crate::Error::IndexOutOfRange(row))?;
636 let c = r
637 .cells
638 .get_mut(col)
639 .ok_or(crate::Error::IndexOutOfRange(col))?;
640 let border = crate::oxml::table::CellBorder {
641 color,
642 width,
643 no_fill,
644 };
645 match side {
646 BorderSide::Left => c.border_left = Some(border),
647 BorderSide::Right => c.border_right = Some(border),
648 BorderSide::Top => c.border_top = Some(border),
649 BorderSide::Bottom => c.border_bottom = Some(border),
650 }
651 Ok(())
652 }
653
654 pub fn cell_border(
656 &self,
657 row: usize,
658 col: usize,
659 side: BorderSide,
660 ) -> Option<crate::oxml::table::CellBorder> {
661 let c = self.cell(row, col)?;
662 match side {
663 BorderSide::Left => c.border_left.clone(),
664 BorderSide::Right => c.border_right.clone(),
665 BorderSide::Top => c.border_top.clone(),
666 BorderSide::Bottom => c.border_bottom.clone(),
667 }
668 }
669
670 pub fn set_placeholder(&mut self, ph_idx: u32, ph_type: Option<&str>) {
682 self.frame.is_placeholder = true;
683 self.frame.ph_idx = Some(ph_idx);
684 self.frame.ph_type = ph_type.map(|s| s.to_string());
685 }
686
687 pub fn clear_placeholder(&mut self) {
689 self.frame.is_placeholder = false;
690 self.frame.ph_idx = None;
691 self.frame.ph_type = None;
692 }
693
694 pub fn is_placeholder(&self) -> bool {
696 self.frame.is_placeholder
697 }
698
699 pub fn ph_idx(&self) -> Option<u32> {
701 self.frame.ph_idx
702 }
703
704 pub fn ph_type(&self) -> Option<&str> {
706 self.frame.ph_type.as_deref()
707 }
708}
709
710#[derive(Clone, Copy, Debug, PartialEq, Eq)]
712pub enum BorderSide {
713 Left,
715 Right,
717 Top,
719 Bottom,
721}
722
723impl Shape for TableShape {
724 fn id(&self) -> u32 {
725 self.frame.id
726 }
727 fn set_id(&mut self, id: u32) {
728 self.frame.id = id;
729 }
730 fn name(&self) -> &str {
731 &self.frame.name
732 }
733 fn set_name(&mut self, name: String) {
734 self.frame.name = name;
735 }
736 fn shape_type(&self) -> &'static str {
737 "table"
738 }
739
740 fn left(&self) -> Emu {
741 self.frame.properties.xfrm.off_x.unwrap_or_default()
742 }
743 fn set_left(&mut self, emu: Emu) {
744 self.frame.properties.xfrm.off_x = Some(emu);
745 }
746 fn top(&self) -> Emu {
747 self.frame.properties.xfrm.off_y.unwrap_or_default()
748 }
749 fn set_top(&mut self, emu: Emu) {
750 self.frame.properties.xfrm.off_y = Some(emu);
751 }
752 fn width(&self) -> Emu {
753 self.frame.properties.xfrm.ext_cx.unwrap_or_default()
754 }
755 fn set_width(&mut self, emu: Emu) {
756 self.frame.properties.xfrm.ext_cx = Some(emu);
757 }
758 fn height(&self) -> Emu {
759 self.frame.properties.xfrm.ext_cy.unwrap_or_default()
760 }
761 fn set_height(&mut self, emu: Emu) {
762 self.frame.properties.xfrm.ext_cy = Some(emu);
763 }
764
765 fn rotation(&self) -> f64 {
767 0.0
768 }
769 fn set_rotation(&mut self, _deg: f64) {}
770}
771
772#[cfg(test)]
773mod tests {
774 use super::*;
775 use crate::oxml::color::Color;
776 use crate::units::{Emu, RGBColor};
777
778 #[test]
780 fn merge_cells_sets_attributes() {
781 let mut t = TableShape::new(3, 3, Emu(1000), Emu(500));
782 t.merge_cells(0, 0, 1, 2).unwrap();
784 let origin = t.cell(0, 0).unwrap();
786 assert_eq!(origin.grid_span, 3);
787 assert_eq!(origin.row_span, 2);
788 assert!(!origin.h_merge);
789 assert!(!origin.v_merge);
790 let h1 = t.cell(0, 1).unwrap();
792 assert!(h1.h_merge);
793 assert!(!h1.v_merge);
794 let v1 = t.cell(1, 0).unwrap();
796 assert!(!v1.h_merge);
797 assert!(v1.v_merge);
798 }
799
800 #[test]
802 fn merge_cells_1x1_is_noop() {
803 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
804 t.merge_cells(0, 0, 0, 0).unwrap();
805 let c = t.cell(0, 0).unwrap();
806 assert_eq!(c.grid_span, 0); assert_eq!(c.row_span, 0);
808 }
809
810 #[test]
812 fn merge_cells_out_of_bounds() {
813 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
814 assert!(t.merge_cells(0, 0, 5, 5).is_err());
815 assert!(t.merge_cells(1, 0, 0, 0).is_err()); }
817
818 #[test]
820 fn set_cell_border_works() {
821 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
822 t.set_cell_border(
823 0,
824 0,
825 BorderSide::Top,
826 Emu(9525),
827 Color::RGB(RGBColor::RED),
828 false,
829 )
830 .unwrap();
831 let b = t.cell_border(0, 0, BorderSide::Top).unwrap();
832 assert_eq!(b.width.value(), 9525);
833 assert!(!b.no_fill);
834 }
835
836 #[test]
841 fn tbl_look_boolean_attributes() {
842 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
843 assert!(t.first_row(), "默认 firstRow=true");
845 assert!(!t.last_row(), "默认 lastRow=false");
846 assert!(t.first_column(), "默认 firstColumn=true");
847 assert!(!t.last_column(), "默认 lastColumn=false");
848 assert!(t.horz_banding(), "默认 horz_banding=true");
849 assert!(!t.vert_banding(), "默认 vert_banding=false");
850
851 t.set_first_row(false);
853 t.set_last_row(true);
854 t.set_first_column(false);
855 t.set_last_column(true);
856 t.set_horz_banding(false);
857 t.set_vert_banding(true);
858
859 assert!(!t.first_row());
861 assert!(t.last_row());
862 assert!(!t.first_column());
863 assert!(t.last_column());
864 assert!(!t.horz_banding());
865 assert!(t.vert_banding());
866
867 let lk = &t.table().tbl_look;
869 assert!(!lk.first_row);
870 assert!(lk.last_row);
871 assert!(!lk.first_column);
872 assert!(lk.last_column);
873 assert!(lk.no_h_band, "horz_banding=false → noHBand=true");
874 assert!(!lk.no_v_band, "vert_banding=true → noVBand=false");
875 }
876
877 #[test]
879 fn remove_row_and_column() {
880 let mut t = TableShape::new(3, 3, Emu(1000), Emu(500));
881 t.remove_row(1).unwrap();
882 assert_eq!(t.row_count(), 2);
883 t.remove_column(1).unwrap();
884 assert_eq!(t.column_count(), 2);
885 assert_eq!(t.table().rows[0].cells.len(), 2);
887 }
888
889 #[test]
891 fn remove_row_out_of_bounds() {
892 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
893 assert!(t.remove_row(5).is_err());
894 assert!(t.remove_column(5).is_err());
895 }
896
897 #[test]
901 fn set_style_builtin() {
902 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
903 assert!(t.set_style("Medium Style 2 - Accent 1"));
904 let style = t.table_style().expect("style 应已设置");
905 assert_eq!(style.style_id(), "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}");
906 assert_eq!(style.style_name(), Some("Medium Style 2 - Accent 1"));
907 }
908
909 #[test]
911 fn set_style_unknown_returns_false() {
912 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
913 assert!(!t.set_style("Nonexistent Style"));
914 assert!(t.table_style().is_none());
915 }
916
917 #[test]
919 fn set_style_id_raw_guid() {
920 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
921 t.set_style_id("{5940675A-B579-460E-94D1-54222C63F5DA}");
922 let style = t.table_style().expect("style 应已设置");
923 assert_eq!(style.style_id(), "{5940675A-B579-460E-94D1-54222C63F5DA}");
924 }
925
926 #[test]
928 fn clear_style_works() {
929 let mut t = TableShape::new(2, 2, Emu(1000), Emu(500));
930 t.set_style("Medium Style 2 - Accent 1");
931 assert!(t.table_style().is_some());
932 t.clear_style();
933 assert!(t.table_style().is_none());
934 }
935
936 #[test]
938 fn table_style_serializes() {
939 let mut t = TableShape::new(1, 1, Emu(1000), Emu(500));
940 t.set_style("No Style, Table Grid");
941 let mut w = crate::oxml::writer::XmlWriter::new();
942 t.table().write_xml(&mut w);
943 let xml = &w.buf;
944 assert!(xml.contains("<a:tableStyleId>"));
945 assert!(xml.contains("{5940675A-B579-460E-94D1-54222C63F5DA}"));
946 assert!(xml.contains("</a:tableStyleId>"));
947 }
948}