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 #[must_use]
231 pub fn with_width(mut self, width: HwpUnit) -> Self {
232 self.width = Some(width);
233 self
234 }
235
236 #[must_use]
238 pub fn with_caption(mut self, caption: Caption) -> Self {
239 self.caption = Some(caption);
240 self
241 }
242
243 #[must_use]
245 pub fn with_page_break(mut self, page_break: TablePageBreak) -> Self {
246 self.page_break = page_break;
247 self
248 }
249
250 #[must_use]
252 pub fn with_repeat_header(mut self, repeat_header: bool) -> Self {
253 self.repeat_header = repeat_header;
254 self
255 }
256
257 #[must_use]
259 pub fn with_cell_spacing(mut self, cell_spacing: HwpUnit) -> Self {
260 self.cell_spacing = Some(cell_spacing);
261 self
262 }
263
264 #[must_use]
266 pub fn with_border_fill_id(mut self, border_fill_id: u32) -> Self {
267 self.border_fill_id = Some(border_fill_id);
268 self
269 }
270
271 #[must_use]
273 pub fn with_out_margin(mut self, out_margin: TableMargin) -> Self {
274 self.out_margin = Some(out_margin);
275 self
276 }
277
278 #[must_use]
280 pub fn with_in_margin(mut self, in_margin: TableMargin) -> Self {
281 self.in_margin = Some(in_margin);
282 self
283 }
284
285 #[must_use]
287 pub fn with_layout_cache(mut self, layout_cache: TableLayoutCache) -> Self {
288 self.layout_cache = Some(layout_cache);
289 self
290 }
291
292 pub fn row_count(&self) -> usize {
294 self.rows.len()
295 }
296
297 pub fn col_count(&self) -> usize {
301 self.rows.first().map_or(0, |r| r.cells.len())
302 }
303
304 pub fn is_empty(&self) -> bool {
306 self.rows.is_empty()
307 }
308}
309
310impl std::fmt::Display for Table {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 write!(f, "Table({}x{})", self.row_count(), self.col_count())
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
332#[non_exhaustive]
333pub struct TableRow {
334 pub cells: Vec<TableCell>,
336 pub height: Option<HwpUnit>,
338 #[serde(default)]
340 pub is_header: bool,
341}
342
343impl TableRow {
344 #[must_use]
361 pub fn new(cells: Vec<TableCell>) -> Self {
362 Self { cells, height: None, is_header: false }
363 }
364
365 #[must_use]
382 pub fn with_height(cells: Vec<TableCell>, height: HwpUnit) -> Self {
383 Self { cells, height: Some(height), is_header: false }
384 }
385
386 #[must_use]
388 pub fn with_header(mut self, is_header: bool) -> Self {
389 self.is_header = is_header;
390 self
391 }
392}
393
394#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
414#[non_exhaustive]
415pub struct TableCell {
416 pub paragraphs: Vec<Paragraph>,
418 pub col_span: u16,
420 pub row_span: u16,
422 pub width: HwpUnit,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub height: Option<HwpUnit>,
427 pub background: Option<Color>,
429 #[serde(default, skip_serializing_if = "Option::is_none")]
431 pub border_fill_id: Option<u32>,
432 #[serde(default, skip_serializing_if = "Option::is_none")]
434 pub margin: Option<TableMargin>,
435 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub vertical_align: Option<TableVerticalAlign>,
438}
439
440impl TableCell {
441 #[must_use]
459 pub fn new(paragraphs: Vec<Paragraph>, width: HwpUnit) -> Self {
460 Self {
461 paragraphs,
462 col_span: 1,
463 row_span: 1,
464 width,
465 height: None,
466 background: None,
467 border_fill_id: None,
468 margin: None,
469 vertical_align: None,
470 }
471 }
472
473 #[must_use]
492 pub fn with_span(
493 paragraphs: Vec<Paragraph>,
494 width: HwpUnit,
495 col_span: u16,
496 row_span: u16,
497 ) -> Self {
498 Self {
499 paragraphs,
500 col_span,
501 row_span,
502 width,
503 height: None,
504 background: None,
505 border_fill_id: None,
506 margin: None,
507 vertical_align: None,
508 }
509 }
510
511 #[must_use]
513 pub fn with_height(mut self, height: HwpUnit) -> Self {
514 self.height = Some(height);
515 self
516 }
517
518 #[must_use]
520 pub fn with_background(mut self, background: Color) -> Self {
521 self.background = Some(background);
522 self
523 }
524
525 #[must_use]
527 pub fn with_border_fill_id(mut self, border_fill_id: u32) -> Self {
528 self.border_fill_id = Some(border_fill_id);
529 self
530 }
531
532 #[must_use]
534 pub fn with_margin(mut self, margin: TableMargin) -> Self {
535 self.margin = Some(margin);
536 self
537 }
538
539 #[must_use]
541 pub fn with_vertical_align(mut self, vertical_align: TableVerticalAlign) -> Self {
542 self.vertical_align = Some(vertical_align);
543 self
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550 use crate::run::Run;
551 use hwpforge_foundation::{CharShapeIndex, ParaShapeIndex};
552
553 fn simple_paragraph() -> Paragraph {
554 Paragraph::with_runs(
555 vec![Run::text("cell", CharShapeIndex::new(0))],
556 ParaShapeIndex::new(0),
557 )
558 }
559
560 fn simple_cell() -> TableCell {
561 TableCell::new(vec![simple_paragraph()], HwpUnit::from_mm(50.0).unwrap())
562 }
563
564 fn simple_row() -> TableRow {
565 TableRow::new(vec![simple_cell(), simple_cell()])
566 }
567
568 fn simple_table() -> Table {
569 Table::new(vec![simple_row(), simple_row()])
570 }
571
572 #[test]
573 fn table_new() {
574 let t = simple_table();
575 assert_eq!(t.row_count(), 2);
576 assert_eq!(t.col_count(), 2);
577 assert!(!t.is_empty());
578 assert!(t.width.is_none());
579 assert!(t.caption.is_none());
580 assert_eq!(t.page_break, TablePageBreak::Cell);
581 assert!(t.repeat_header);
582 assert!(t.cell_spacing.is_none());
583 assert!(t.border_fill_id.is_none());
584 }
585
586 #[test]
587 fn empty_table() {
588 let t = Table::new(vec![]);
589 assert_eq!(t.row_count(), 0);
590 assert_eq!(t.col_count(), 0);
591 assert!(t.is_empty());
592 }
593
594 #[test]
595 fn table_with_caption() {
596 let t = simple_table().with_caption(crate::caption::Caption::default());
597 assert!(t.caption.is_some());
598 }
599
600 #[test]
601 fn table_with_width() {
602 let t = simple_table().with_width(HwpUnit::from_mm(150.0).unwrap());
603 assert!(t.width.is_some());
604 }
605
606 #[test]
607 fn table_with_page_break() {
608 let t = simple_table().with_page_break(TablePageBreak::Table);
609 assert_eq!(t.page_break, TablePageBreak::Table);
610 }
611
612 #[test]
613 fn table_with_repeat_header_disabled() {
614 let t = simple_table().with_repeat_header(false);
615 assert!(!t.repeat_header);
616 }
617
618 #[test]
619 fn cell_new_defaults() {
620 let cell = simple_cell();
621 assert_eq!(cell.col_span, 1);
622 assert_eq!(cell.row_span, 1);
623 assert!(cell.height.is_none());
624 assert!(cell.background.is_none());
625 assert!(cell.border_fill_id.is_none());
626 assert!(cell.margin.is_none());
627 assert!(cell.vertical_align.is_none());
628 assert_eq!(cell.paragraphs.len(), 1);
629 }
630
631 #[test]
632 fn cell_with_span() {
633 let cell =
634 TableCell::with_span(vec![simple_paragraph()], HwpUnit::from_mm(100.0).unwrap(), 3, 2);
635 assert_eq!(cell.col_span, 3);
636 assert_eq!(cell.row_span, 2);
637 }
638
639 #[test]
640 fn cell_with_background() {
641 let cell = simple_cell().with_background(Color::from_rgb(200, 200, 200));
642 assert!(cell.background.is_some());
643 }
644
645 #[test]
646 fn table_display() {
647 let t = simple_table();
648 assert_eq!(t.to_string(), "Table(2x2)");
649 }
650
651 #[test]
652 fn single_cell_table() {
653 let table = Table::new(vec![TableRow::with_height(
654 vec![simple_cell()],
655 HwpUnit::from_mm(10.0).unwrap(),
656 )]);
657 assert_eq!(table.row_count(), 1);
658 assert_eq!(table.col_count(), 1);
659 }
660
661 #[test]
662 fn row_with_fixed_height() {
663 let row = TableRow::with_height(vec![simple_cell()], HwpUnit::from_mm(25.0).unwrap());
664 assert!(row.height.is_some());
665 }
666
667 #[test]
668 fn row_new_auto_height() {
669 let row = TableRow::new(vec![simple_cell(), simple_cell()]);
670 assert_eq!(row.cells.len(), 2);
671 assert!(row.height.is_none());
672 }
673
674 #[test]
675 fn row_new_empty_cells() {
676 let row = TableRow::new(vec![]);
677 assert!(row.cells.is_empty());
678 assert!(row.height.is_none());
679 }
680
681 #[test]
682 fn row_with_height_constructor() {
683 let h = HwpUnit::from_mm(20.0).unwrap();
684 let row = TableRow::with_height(vec![simple_cell()], h);
685 assert_eq!(row.cells.len(), 1);
686 assert_eq!(row.height, Some(h));
687 }
688
689 #[test]
690 fn equality() {
691 let a = simple_table();
692 let b = simple_table();
693 assert_eq!(a, b);
694 }
695
696 #[test]
697 fn clone_independence() {
698 let t = simple_table();
699 let mut cloned = t.clone();
700 cloned.caption = Some(crate::caption::Caption::default());
701 assert!(t.caption.is_none());
702 }
703
704 #[test]
705 fn serde_roundtrip() {
706 let t = simple_table();
707 let json = serde_json::to_string(&t).unwrap();
708 let back: Table = serde_json::from_str(&json).unwrap();
709 assert_eq!(t, back);
710 }
711
712 #[test]
713 fn serde_with_all_optional_fields() {
714 let mut t = simple_table()
715 .with_width(HwpUnit::from_mm(150.0).unwrap())
716 .with_caption(crate::caption::Caption::default())
717 .with_page_break(TablePageBreak::None)
718 .with_repeat_header(false)
719 .with_cell_spacing(HwpUnit::from_mm(2.0).unwrap())
720 .with_border_fill_id(7);
721 t.rows[0].height = Some(HwpUnit::from_mm(20.0).unwrap());
722 t.rows[0].cells[0] = t.rows[0].cells[0]
723 .clone()
724 .with_background(Color::from_rgb(255, 0, 0))
725 .with_height(HwpUnit::from_mm(8.0).unwrap())
726 .with_border_fill_id(9)
727 .with_margin(TableMargin {
728 left: HwpUnit::from_mm(1.0).unwrap(),
729 right: HwpUnit::from_mm(2.0).unwrap(),
730 top: HwpUnit::from_mm(0.5).unwrap(),
731 bottom: HwpUnit::from_mm(0.25).unwrap(),
732 })
733 .with_vertical_align(TableVerticalAlign::Bottom);
734
735 let json = serde_json::to_string(&t).unwrap();
736 let back: Table = serde_json::from_str(&json).unwrap();
737 assert_eq!(t, back);
738 }
739
740 #[test]
741 fn serde_defaults_missing_new_fields() {
742 let json = r#"{"rows":[],"width":null,"caption":null}"#;
743 let back: Table = serde_json::from_str(json).unwrap();
744 assert_eq!(back.page_break, TablePageBreak::Cell);
745 assert!(back.repeat_header);
746 assert!(back.cell_spacing.is_none());
747 assert!(back.border_fill_id.is_none());
748 }
749
750 #[test]
751 fn table_margin_defaults_to_zero() {
752 let margin = TableMargin::default();
753 assert_eq!(margin.left, HwpUnit::ZERO);
754 assert_eq!(margin.right, HwpUnit::ZERO);
755 assert_eq!(margin.top, HwpUnit::ZERO);
756 assert_eq!(margin.bottom, HwpUnit::ZERO);
757 }
758
759 #[test]
760 fn cell_zero_span_allowed_at_construction() {
761 let cell = TableCell::with_span(
763 vec![simple_paragraph()],
764 HwpUnit::from_mm(50.0).unwrap(),
765 0, 0,
767 );
768 assert_eq!(cell.col_span, 0);
769 assert_eq!(cell.row_span, 0);
770 }
771
772 #[test]
773 fn row_new_sets_expected_defaults() {
774 let cells = vec![simple_cell()];
775 let row = TableRow::new(cells.clone());
776 assert_eq!(row.cells, cells);
777 assert!(row.height.is_none());
778 assert!(!row.is_header);
779 }
780}