1use hwpforge_foundation::{Color, HwpUnit};
39use schemars::JsonSchema;
40use serde::{Deserialize, Serialize};
41
42use crate::caption::Caption;
43use crate::paragraph::Paragraph;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
47#[serde(rename_all = "snake_case")]
48pub enum TablePageBreak {
49 #[default]
51 Cell,
52 Table,
54 None,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
60#[serde(rename_all = "snake_case")]
61pub enum TableVerticalAlign {
62 Top,
64 #[default]
66 Center,
67 Bottom,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
73pub struct TableMargin {
74 pub left: HwpUnit,
76 pub right: HwpUnit,
78 pub top: HwpUnit,
80 pub bottom: HwpUnit,
82}
83
84fn default_repeat_header() -> bool {
85 true
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
110#[non_exhaustive]
111pub struct Table {
112 pub rows: Vec<TableRow>,
114 pub width: Option<HwpUnit>,
116 pub caption: Option<Caption>,
118 #[serde(default)]
120 pub page_break: TablePageBreak,
121 #[serde(default = "default_repeat_header")]
123 pub repeat_header: bool,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub cell_spacing: Option<HwpUnit>,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub border_fill_id: Option<u32>,
130 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub inst_id: Option<u64>,
136}
137
138impl Table {
139 #[must_use]
150 pub fn new(rows: Vec<TableRow>) -> Self {
151 Self {
152 rows,
153 width: None,
154 caption: None,
155 page_break: TablePageBreak::Cell,
156 repeat_header: true,
157 cell_spacing: None,
158 border_fill_id: None,
159 inst_id: None,
160 }
161 }
162
163 #[must_use]
165 pub fn with_width(mut self, width: HwpUnit) -> Self {
166 self.width = Some(width);
167 self
168 }
169
170 #[must_use]
172 pub fn with_caption(mut self, caption: Caption) -> Self {
173 self.caption = Some(caption);
174 self
175 }
176
177 #[must_use]
179 pub fn with_page_break(mut self, page_break: TablePageBreak) -> Self {
180 self.page_break = page_break;
181 self
182 }
183
184 #[must_use]
186 pub fn with_repeat_header(mut self, repeat_header: bool) -> Self {
187 self.repeat_header = repeat_header;
188 self
189 }
190
191 #[must_use]
193 pub fn with_cell_spacing(mut self, cell_spacing: HwpUnit) -> Self {
194 self.cell_spacing = Some(cell_spacing);
195 self
196 }
197
198 #[must_use]
200 pub fn with_border_fill_id(mut self, border_fill_id: u32) -> Self {
201 self.border_fill_id = Some(border_fill_id);
202 self
203 }
204
205 pub fn row_count(&self) -> usize {
207 self.rows.len()
208 }
209
210 pub fn col_count(&self) -> usize {
214 self.rows.first().map_or(0, |r| r.cells.len())
215 }
216
217 pub fn is_empty(&self) -> bool {
219 self.rows.is_empty()
220 }
221}
222
223impl std::fmt::Display for Table {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 write!(f, "Table({}x{})", self.row_count(), self.col_count())
226 }
227}
228
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
245#[non_exhaustive]
246pub struct TableRow {
247 pub cells: Vec<TableCell>,
249 pub height: Option<HwpUnit>,
251 #[serde(default)]
253 pub is_header: bool,
254}
255
256impl TableRow {
257 #[must_use]
274 pub fn new(cells: Vec<TableCell>) -> Self {
275 Self { cells, height: None, is_header: false }
276 }
277
278 #[must_use]
295 pub fn with_height(cells: Vec<TableCell>, height: HwpUnit) -> Self {
296 Self { cells, height: Some(height), is_header: false }
297 }
298
299 #[must_use]
301 pub fn with_header(mut self, is_header: bool) -> Self {
302 self.is_header = is_header;
303 self
304 }
305}
306
307#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
327#[non_exhaustive]
328pub struct TableCell {
329 pub paragraphs: Vec<Paragraph>,
331 pub col_span: u16,
333 pub row_span: u16,
335 pub width: HwpUnit,
337 #[serde(default, skip_serializing_if = "Option::is_none")]
339 pub height: Option<HwpUnit>,
340 pub background: Option<Color>,
342 #[serde(default, skip_serializing_if = "Option::is_none")]
344 pub border_fill_id: Option<u32>,
345 #[serde(default, skip_serializing_if = "Option::is_none")]
347 pub margin: Option<TableMargin>,
348 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub vertical_align: Option<TableVerticalAlign>,
351}
352
353impl TableCell {
354 #[must_use]
372 pub fn new(paragraphs: Vec<Paragraph>, width: HwpUnit) -> Self {
373 Self {
374 paragraphs,
375 col_span: 1,
376 row_span: 1,
377 width,
378 height: None,
379 background: None,
380 border_fill_id: None,
381 margin: None,
382 vertical_align: None,
383 }
384 }
385
386 #[must_use]
405 pub fn with_span(
406 paragraphs: Vec<Paragraph>,
407 width: HwpUnit,
408 col_span: u16,
409 row_span: u16,
410 ) -> Self {
411 Self {
412 paragraphs,
413 col_span,
414 row_span,
415 width,
416 height: None,
417 background: None,
418 border_fill_id: None,
419 margin: None,
420 vertical_align: None,
421 }
422 }
423
424 #[must_use]
426 pub fn with_height(mut self, height: HwpUnit) -> Self {
427 self.height = Some(height);
428 self
429 }
430
431 #[must_use]
433 pub fn with_background(mut self, background: Color) -> Self {
434 self.background = Some(background);
435 self
436 }
437
438 #[must_use]
440 pub fn with_border_fill_id(mut self, border_fill_id: u32) -> Self {
441 self.border_fill_id = Some(border_fill_id);
442 self
443 }
444
445 #[must_use]
447 pub fn with_margin(mut self, margin: TableMargin) -> Self {
448 self.margin = Some(margin);
449 self
450 }
451
452 #[must_use]
454 pub fn with_vertical_align(mut self, vertical_align: TableVerticalAlign) -> Self {
455 self.vertical_align = Some(vertical_align);
456 self
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463 use crate::run::Run;
464 use hwpforge_foundation::{CharShapeIndex, ParaShapeIndex};
465
466 fn simple_paragraph() -> Paragraph {
467 Paragraph::with_runs(
468 vec![Run::text("cell", CharShapeIndex::new(0))],
469 ParaShapeIndex::new(0),
470 )
471 }
472
473 fn simple_cell() -> TableCell {
474 TableCell::new(vec![simple_paragraph()], HwpUnit::from_mm(50.0).unwrap())
475 }
476
477 fn simple_row() -> TableRow {
478 TableRow::new(vec![simple_cell(), simple_cell()])
479 }
480
481 fn simple_table() -> Table {
482 Table::new(vec![simple_row(), simple_row()])
483 }
484
485 #[test]
486 fn table_new() {
487 let t = simple_table();
488 assert_eq!(t.row_count(), 2);
489 assert_eq!(t.col_count(), 2);
490 assert!(!t.is_empty());
491 assert!(t.width.is_none());
492 assert!(t.caption.is_none());
493 assert_eq!(t.page_break, TablePageBreak::Cell);
494 assert!(t.repeat_header);
495 assert!(t.cell_spacing.is_none());
496 assert!(t.border_fill_id.is_none());
497 }
498
499 #[test]
500 fn empty_table() {
501 let t = Table::new(vec![]);
502 assert_eq!(t.row_count(), 0);
503 assert_eq!(t.col_count(), 0);
504 assert!(t.is_empty());
505 }
506
507 #[test]
508 fn table_with_caption() {
509 let t = simple_table().with_caption(crate::caption::Caption::default());
510 assert!(t.caption.is_some());
511 }
512
513 #[test]
514 fn table_with_width() {
515 let t = simple_table().with_width(HwpUnit::from_mm(150.0).unwrap());
516 assert!(t.width.is_some());
517 }
518
519 #[test]
520 fn table_with_page_break() {
521 let t = simple_table().with_page_break(TablePageBreak::Table);
522 assert_eq!(t.page_break, TablePageBreak::Table);
523 }
524
525 #[test]
526 fn table_with_repeat_header_disabled() {
527 let t = simple_table().with_repeat_header(false);
528 assert!(!t.repeat_header);
529 }
530
531 #[test]
532 fn cell_new_defaults() {
533 let cell = simple_cell();
534 assert_eq!(cell.col_span, 1);
535 assert_eq!(cell.row_span, 1);
536 assert!(cell.height.is_none());
537 assert!(cell.background.is_none());
538 assert!(cell.border_fill_id.is_none());
539 assert!(cell.margin.is_none());
540 assert!(cell.vertical_align.is_none());
541 assert_eq!(cell.paragraphs.len(), 1);
542 }
543
544 #[test]
545 fn cell_with_span() {
546 let cell =
547 TableCell::with_span(vec![simple_paragraph()], HwpUnit::from_mm(100.0).unwrap(), 3, 2);
548 assert_eq!(cell.col_span, 3);
549 assert_eq!(cell.row_span, 2);
550 }
551
552 #[test]
553 fn cell_with_background() {
554 let cell = simple_cell().with_background(Color::from_rgb(200, 200, 200));
555 assert!(cell.background.is_some());
556 }
557
558 #[test]
559 fn table_display() {
560 let t = simple_table();
561 assert_eq!(t.to_string(), "Table(2x2)");
562 }
563
564 #[test]
565 fn single_cell_table() {
566 let table = Table::new(vec![TableRow::with_height(
567 vec![simple_cell()],
568 HwpUnit::from_mm(10.0).unwrap(),
569 )]);
570 assert_eq!(table.row_count(), 1);
571 assert_eq!(table.col_count(), 1);
572 }
573
574 #[test]
575 fn row_with_fixed_height() {
576 let row = TableRow::with_height(vec![simple_cell()], HwpUnit::from_mm(25.0).unwrap());
577 assert!(row.height.is_some());
578 }
579
580 #[test]
581 fn row_new_auto_height() {
582 let row = TableRow::new(vec![simple_cell(), simple_cell()]);
583 assert_eq!(row.cells.len(), 2);
584 assert!(row.height.is_none());
585 }
586
587 #[test]
588 fn row_new_empty_cells() {
589 let row = TableRow::new(vec![]);
590 assert!(row.cells.is_empty());
591 assert!(row.height.is_none());
592 }
593
594 #[test]
595 fn row_with_height_constructor() {
596 let h = HwpUnit::from_mm(20.0).unwrap();
597 let row = TableRow::with_height(vec![simple_cell()], h);
598 assert_eq!(row.cells.len(), 1);
599 assert_eq!(row.height, Some(h));
600 }
601
602 #[test]
603 fn equality() {
604 let a = simple_table();
605 let b = simple_table();
606 assert_eq!(a, b);
607 }
608
609 #[test]
610 fn clone_independence() {
611 let t = simple_table();
612 let mut cloned = t.clone();
613 cloned.caption = Some(crate::caption::Caption::default());
614 assert!(t.caption.is_none());
615 }
616
617 #[test]
618 fn serde_roundtrip() {
619 let t = simple_table();
620 let json = serde_json::to_string(&t).unwrap();
621 let back: Table = serde_json::from_str(&json).unwrap();
622 assert_eq!(t, back);
623 }
624
625 #[test]
626 fn serde_with_all_optional_fields() {
627 let mut t = simple_table()
628 .with_width(HwpUnit::from_mm(150.0).unwrap())
629 .with_caption(crate::caption::Caption::default())
630 .with_page_break(TablePageBreak::None)
631 .with_repeat_header(false)
632 .with_cell_spacing(HwpUnit::from_mm(2.0).unwrap())
633 .with_border_fill_id(7);
634 t.rows[0].height = Some(HwpUnit::from_mm(20.0).unwrap());
635 t.rows[0].cells[0] = t.rows[0].cells[0]
636 .clone()
637 .with_background(Color::from_rgb(255, 0, 0))
638 .with_height(HwpUnit::from_mm(8.0).unwrap())
639 .with_border_fill_id(9)
640 .with_margin(TableMargin {
641 left: HwpUnit::from_mm(1.0).unwrap(),
642 right: HwpUnit::from_mm(2.0).unwrap(),
643 top: HwpUnit::from_mm(0.5).unwrap(),
644 bottom: HwpUnit::from_mm(0.25).unwrap(),
645 })
646 .with_vertical_align(TableVerticalAlign::Bottom);
647
648 let json = serde_json::to_string(&t).unwrap();
649 let back: Table = serde_json::from_str(&json).unwrap();
650 assert_eq!(t, back);
651 }
652
653 #[test]
654 fn serde_defaults_missing_new_fields() {
655 let json = r#"{"rows":[],"width":null,"caption":null}"#;
656 let back: Table = serde_json::from_str(json).unwrap();
657 assert_eq!(back.page_break, TablePageBreak::Cell);
658 assert!(back.repeat_header);
659 assert!(back.cell_spacing.is_none());
660 assert!(back.border_fill_id.is_none());
661 }
662
663 #[test]
664 fn table_margin_defaults_to_zero() {
665 let margin = TableMargin::default();
666 assert_eq!(margin.left, HwpUnit::ZERO);
667 assert_eq!(margin.right, HwpUnit::ZERO);
668 assert_eq!(margin.top, HwpUnit::ZERO);
669 assert_eq!(margin.bottom, HwpUnit::ZERO);
670 }
671
672 #[test]
673 fn cell_zero_span_allowed_at_construction() {
674 let cell = TableCell::with_span(
676 vec![simple_paragraph()],
677 HwpUnit::from_mm(50.0).unwrap(),
678 0, 0,
680 );
681 assert_eq!(cell.col_span, 0);
682 assert_eq!(cell.row_span, 0);
683 }
684
685 #[test]
686 fn row_new_sets_expected_defaults() {
687 let cells = vec![simple_cell()];
688 let row = TableRow::new(cells.clone());
689 assert_eq!(row.cells, cells);
690 assert!(row.height.is_none());
691 assert!(!row.is_header);
692 }
693}