1pub mod grid;
39
40use hwpforge_foundation::{Color, HwpUnit};
41use schemars::JsonSchema;
42use serde::{Deserialize, Serialize};
43
44use crate::caption::Caption;
45use crate::object_id::ObjectId;
46use crate::paragraph::Paragraph;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
50#[serde(rename_all = "snake_case")]
51pub enum TablePageBreak {
52 #[default]
54 Cell,
55 Table,
57 None,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
63#[serde(rename_all = "snake_case")]
64pub enum TableVerticalAlign {
65 Top,
67 #[default]
69 Center,
70 Bottom,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
76pub struct TableMargin {
77 pub left: HwpUnit,
79 pub right: HwpUnit,
81 pub top: HwpUnit,
83 pub bottom: HwpUnit,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
94#[non_exhaustive]
95pub struct TableLayoutCache {
96 pub saved_sz_height: Option<HwpUnit>,
102 pub default_flow_pos: bool,
107}
108
109impl TableLayoutCache {
110 #[must_use]
112 pub fn new(saved_sz_height: Option<HwpUnit>, default_flow_pos: bool) -> Self {
113 Self { saved_sz_height, default_flow_pos }
114 }
115}
116
117fn default_repeat_header() -> bool {
118 true
119}
120
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
143#[non_exhaustive]
144pub struct Table {
145 pub rows: Vec<TableRow>,
147 pub width: Option<HwpUnit>,
149 pub caption: Option<Caption>,
151 #[serde(default)]
153 pub page_break: TablePageBreak,
154 #[serde(default = "default_repeat_header")]
157 pub repeat_header: bool,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub cell_spacing: Option<HwpUnit>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub border_fill_id: Option<u32>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub inst_id: Option<ObjectId>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub out_margin: Option<TableMargin>,
174 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub in_margin: Option<TableMargin>,
178 #[serde(default, skip_serializing_if = "Option::is_none")]
181 pub layout_cache: Option<TableLayoutCache>,
182}
183
184impl Table {
185 #[must_use]
196 pub fn new(rows: Vec<TableRow>) -> Self {
197 Self {
198 rows,
199 width: None,
200 caption: None,
201 page_break: TablePageBreak::Cell,
202 repeat_header: true,
203 cell_spacing: None,
204 border_fill_id: None,
205 inst_id: None,
206 out_margin: None,
207 in_margin: None,
208 layout_cache: None,
209 }
210 }
211
212 pub(crate) fn walk_paragraphs_mut(
214 &mut self,
215 f: &mut dyn FnMut(&mut crate::paragraph::Paragraph),
216 ) {
217 for row in &mut self.rows {
218 for cell in &mut row.cells {
219 for p in &mut cell.paragraphs {
220 p.walk_paragraphs_mut(f);
221 }
222 }
223 }
224 if let Some(caption) = &mut self.caption {
225 caption.walk_paragraphs_mut(f);
226 }
227 }
228
229 pub(crate) fn walk_paragraphs(&self, f: &mut dyn FnMut(&crate::paragraph::Paragraph)) {
232 for row in &self.rows {
233 for cell in &row.cells {
234 for p in &cell.paragraphs {
235 p.walk_paragraphs(f);
236 }
237 }
238 }
239 if let Some(caption) = &self.caption {
240 caption.walk_paragraphs(f);
241 }
242 }
243
244 #[must_use]
246 pub fn with_width(mut self, width: HwpUnit) -> Self {
247 self.width = Some(width);
248 self
249 }
250
251 #[must_use]
253 pub fn with_caption(mut self, caption: Caption) -> Self {
254 self.caption = Some(caption);
255 self
256 }
257
258 #[must_use]
260 pub fn with_page_break(mut self, page_break: TablePageBreak) -> Self {
261 self.page_break = page_break;
262 self
263 }
264
265 #[must_use]
267 pub fn with_repeat_header(mut self, repeat_header: bool) -> Self {
268 self.repeat_header = repeat_header;
269 self
270 }
271
272 #[must_use]
274 pub fn with_cell_spacing(mut self, cell_spacing: HwpUnit) -> Self {
275 self.cell_spacing = Some(cell_spacing);
276 self
277 }
278
279 #[must_use]
281 pub fn with_border_fill_id(mut self, border_fill_id: u32) -> Self {
282 self.border_fill_id = Some(border_fill_id);
283 self
284 }
285
286 #[must_use]
288 pub fn with_out_margin(mut self, out_margin: TableMargin) -> Self {
289 self.out_margin = Some(out_margin);
290 self
291 }
292
293 #[must_use]
295 pub fn with_in_margin(mut self, in_margin: TableMargin) -> Self {
296 self.in_margin = Some(in_margin);
297 self
298 }
299
300 #[must_use]
302 pub fn with_layout_cache(mut self, layout_cache: TableLayoutCache) -> Self {
303 self.layout_cache = Some(layout_cache);
304 self
305 }
306
307 pub fn row_count(&self) -> usize {
309 self.rows.len()
310 }
311
312 pub fn col_count(&self) -> usize {
316 self.rows.first().map_or(0, |r| r.cells.len())
317 }
318
319 pub fn is_empty(&self) -> bool {
321 self.rows.is_empty()
322 }
323}
324
325impl std::fmt::Display for Table {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 write!(f, "Table({}x{})", self.row_count(), self.col_count())
328 }
329}
330
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
347#[non_exhaustive]
348pub struct TableRow {
349 pub cells: Vec<TableCell>,
351 pub height: Option<HwpUnit>,
353 #[serde(default)]
355 pub is_header: bool,
356}
357
358impl TableRow {
359 #[must_use]
376 pub fn new(cells: Vec<TableCell>) -> Self {
377 Self { cells, height: None, is_header: false }
378 }
379
380 #[must_use]
397 pub fn with_height(cells: Vec<TableCell>, height: HwpUnit) -> Self {
398 Self { cells, height: Some(height), is_header: false }
399 }
400
401 #[must_use]
403 pub fn with_header(mut self, is_header: bool) -> Self {
404 self.is_header = is_header;
405 self
406 }
407}
408
409#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
429#[non_exhaustive]
430pub struct TableCell {
431 pub paragraphs: Vec<Paragraph>,
433 pub col_span: u16,
435 pub row_span: u16,
437 pub width: HwpUnit,
439 #[serde(default, skip_serializing_if = "Option::is_none")]
441 pub height: Option<HwpUnit>,
442 pub background: Option<Color>,
444 #[serde(default, skip_serializing_if = "Option::is_none")]
446 pub border_fill_id: Option<u32>,
447 #[serde(default, skip_serializing_if = "Option::is_none")]
449 pub margin: Option<TableMargin>,
450 #[serde(default, skip_serializing_if = "Option::is_none")]
452 pub vertical_align: Option<TableVerticalAlign>,
453}
454
455impl TableCell {
456 #[must_use]
474 pub fn new(paragraphs: Vec<Paragraph>, width: HwpUnit) -> Self {
475 Self {
476 paragraphs,
477 col_span: 1,
478 row_span: 1,
479 width,
480 height: None,
481 background: None,
482 border_fill_id: None,
483 margin: None,
484 vertical_align: None,
485 }
486 }
487
488 #[must_use]
507 pub fn with_span(
508 paragraphs: Vec<Paragraph>,
509 width: HwpUnit,
510 col_span: u16,
511 row_span: u16,
512 ) -> Self {
513 Self {
514 paragraphs,
515 col_span,
516 row_span,
517 width,
518 height: None,
519 background: None,
520 border_fill_id: None,
521 margin: None,
522 vertical_align: None,
523 }
524 }
525
526 #[must_use]
528 pub fn with_height(mut self, height: HwpUnit) -> Self {
529 self.height = Some(height);
530 self
531 }
532
533 #[must_use]
535 pub fn with_background(mut self, background: Color) -> Self {
536 self.background = Some(background);
537 self
538 }
539
540 #[must_use]
542 pub fn with_border_fill_id(mut self, border_fill_id: u32) -> Self {
543 self.border_fill_id = Some(border_fill_id);
544 self
545 }
546
547 #[must_use]
549 pub fn with_margin(mut self, margin: TableMargin) -> Self {
550 self.margin = Some(margin);
551 self
552 }
553
554 #[must_use]
556 pub fn with_vertical_align(mut self, vertical_align: TableVerticalAlign) -> Self {
557 self.vertical_align = Some(vertical_align);
558 self
559 }
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565 use crate::run::Run;
566 use hwpforge_foundation::{CharShapeIndex, ParaShapeIndex};
567
568 fn simple_paragraph() -> Paragraph {
569 Paragraph::with_runs(
570 vec![Run::text("cell", CharShapeIndex::new(0))],
571 ParaShapeIndex::new(0),
572 )
573 }
574
575 fn simple_cell() -> TableCell {
576 TableCell::new(vec![simple_paragraph()], HwpUnit::from_mm(50.0).unwrap())
577 }
578
579 fn simple_row() -> TableRow {
580 TableRow::new(vec![simple_cell(), simple_cell()])
581 }
582
583 fn simple_table() -> Table {
584 Table::new(vec![simple_row(), simple_row()])
585 }
586
587 #[test]
588 fn table_new() {
589 let t = simple_table();
590 assert_eq!(t.row_count(), 2);
591 assert_eq!(t.col_count(), 2);
592 assert!(!t.is_empty());
593 assert!(t.width.is_none());
594 assert!(t.caption.is_none());
595 assert_eq!(t.page_break, TablePageBreak::Cell);
596 assert!(t.repeat_header);
597 assert!(t.cell_spacing.is_none());
598 assert!(t.border_fill_id.is_none());
599 }
600
601 #[test]
602 fn empty_table() {
603 let t = Table::new(vec![]);
604 assert_eq!(t.row_count(), 0);
605 assert_eq!(t.col_count(), 0);
606 assert!(t.is_empty());
607 }
608
609 #[test]
610 fn table_with_caption() {
611 let t = simple_table().with_caption(crate::caption::Caption::default());
612 assert!(t.caption.is_some());
613 }
614
615 #[test]
616 fn table_with_width() {
617 let t = simple_table().with_width(HwpUnit::from_mm(150.0).unwrap());
618 assert!(t.width.is_some());
619 }
620
621 #[test]
622 fn table_with_page_break() {
623 let t = simple_table().with_page_break(TablePageBreak::Table);
624 assert_eq!(t.page_break, TablePageBreak::Table);
625 }
626
627 #[test]
628 fn table_with_repeat_header_disabled() {
629 let t = simple_table().with_repeat_header(false);
630 assert!(!t.repeat_header);
631 }
632
633 #[test]
634 fn cell_new_defaults() {
635 let cell = simple_cell();
636 assert_eq!(cell.col_span, 1);
637 assert_eq!(cell.row_span, 1);
638 assert!(cell.height.is_none());
639 assert!(cell.background.is_none());
640 assert!(cell.border_fill_id.is_none());
641 assert!(cell.margin.is_none());
642 assert!(cell.vertical_align.is_none());
643 assert_eq!(cell.paragraphs.len(), 1);
644 }
645
646 #[test]
647 fn cell_with_span() {
648 let cell =
649 TableCell::with_span(vec![simple_paragraph()], HwpUnit::from_mm(100.0).unwrap(), 3, 2);
650 assert_eq!(cell.col_span, 3);
651 assert_eq!(cell.row_span, 2);
652 }
653
654 #[test]
655 fn cell_with_background() {
656 let cell = simple_cell().with_background(Color::from_rgb(200, 200, 200));
657 assert!(cell.background.is_some());
658 }
659
660 #[test]
661 fn table_display() {
662 let t = simple_table();
663 assert_eq!(t.to_string(), "Table(2x2)");
664 }
665
666 #[test]
667 fn single_cell_table() {
668 let table = Table::new(vec![TableRow::with_height(
669 vec![simple_cell()],
670 HwpUnit::from_mm(10.0).unwrap(),
671 )]);
672 assert_eq!(table.row_count(), 1);
673 assert_eq!(table.col_count(), 1);
674 }
675
676 #[test]
677 fn row_with_fixed_height() {
678 let row = TableRow::with_height(vec![simple_cell()], HwpUnit::from_mm(25.0).unwrap());
679 assert!(row.height.is_some());
680 }
681
682 #[test]
683 fn row_new_auto_height() {
684 let row = TableRow::new(vec![simple_cell(), simple_cell()]);
685 assert_eq!(row.cells.len(), 2);
686 assert!(row.height.is_none());
687 }
688
689 #[test]
690 fn row_new_empty_cells() {
691 let row = TableRow::new(vec![]);
692 assert!(row.cells.is_empty());
693 assert!(row.height.is_none());
694 }
695
696 #[test]
697 fn row_with_height_constructor() {
698 let h = HwpUnit::from_mm(20.0).unwrap();
699 let row = TableRow::with_height(vec![simple_cell()], h);
700 assert_eq!(row.cells.len(), 1);
701 assert_eq!(row.height, Some(h));
702 }
703
704 #[test]
705 fn equality() {
706 let a = simple_table();
707 let b = simple_table();
708 assert_eq!(a, b);
709 }
710
711 #[test]
712 fn clone_independence() {
713 let t = simple_table();
714 let mut cloned = t.clone();
715 cloned.caption = Some(crate::caption::Caption::default());
716 assert!(t.caption.is_none());
717 }
718
719 #[test]
720 fn serde_roundtrip() {
721 let t = simple_table();
722 let json = serde_json::to_string(&t).unwrap();
723 let back: Table = serde_json::from_str(&json).unwrap();
724 assert_eq!(t, back);
725 }
726
727 #[test]
728 fn serde_with_all_optional_fields() {
729 let mut t = simple_table()
730 .with_width(HwpUnit::from_mm(150.0).unwrap())
731 .with_caption(crate::caption::Caption::default())
732 .with_page_break(TablePageBreak::None)
733 .with_repeat_header(false)
734 .with_cell_spacing(HwpUnit::from_mm(2.0).unwrap())
735 .with_border_fill_id(7);
736 t.rows[0].height = Some(HwpUnit::from_mm(20.0).unwrap());
737 t.rows[0].cells[0] = t.rows[0].cells[0]
738 .clone()
739 .with_background(Color::from_rgb(255, 0, 0))
740 .with_height(HwpUnit::from_mm(8.0).unwrap())
741 .with_border_fill_id(9)
742 .with_margin(TableMargin {
743 left: HwpUnit::from_mm(1.0).unwrap(),
744 right: HwpUnit::from_mm(2.0).unwrap(),
745 top: HwpUnit::from_mm(0.5).unwrap(),
746 bottom: HwpUnit::from_mm(0.25).unwrap(),
747 })
748 .with_vertical_align(TableVerticalAlign::Bottom);
749
750 let json = serde_json::to_string(&t).unwrap();
751 let back: Table = serde_json::from_str(&json).unwrap();
752 assert_eq!(t, back);
753 }
754
755 #[test]
756 fn serde_defaults_missing_new_fields() {
757 let json = r#"{"rows":[],"width":null,"caption":null}"#;
758 let back: Table = serde_json::from_str(json).unwrap();
759 assert_eq!(back.page_break, TablePageBreak::Cell);
760 assert!(back.repeat_header);
761 assert!(back.cell_spacing.is_none());
762 assert!(back.border_fill_id.is_none());
763 }
764
765 #[test]
766 fn table_margin_defaults_to_zero() {
767 let margin = TableMargin::default();
768 assert_eq!(margin.left, HwpUnit::ZERO);
769 assert_eq!(margin.right, HwpUnit::ZERO);
770 assert_eq!(margin.top, HwpUnit::ZERO);
771 assert_eq!(margin.bottom, HwpUnit::ZERO);
772 }
773
774 #[test]
775 fn cell_zero_span_allowed_at_construction() {
776 let cell = TableCell::with_span(
778 vec![simple_paragraph()],
779 HwpUnit::from_mm(50.0).unwrap(),
780 0, 0,
782 );
783 assert_eq!(cell.col_span, 0);
784 assert_eq!(cell.row_span, 0);
785 }
786
787 #[test]
788 fn row_new_sets_expected_defaults() {
789 let cells = vec![simple_cell()];
790 let row = TableRow::new(cells.clone());
791 assert_eq!(row.cells, cells);
792 assert!(row.height.is_none());
793 assert!(!row.is_header);
794 }
795}