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
87fn default_repeat_header() -> bool {
88 true
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
113#[non_exhaustive]
114pub struct Table {
115 pub rows: Vec<TableRow>,
117 pub width: Option<HwpUnit>,
119 pub caption: Option<Caption>,
121 #[serde(default)]
123 pub page_break: TablePageBreak,
124 #[serde(default = "default_repeat_header")]
126 pub repeat_header: bool,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub cell_spacing: Option<HwpUnit>,
130 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub border_fill_id: Option<u32>,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub inst_id: Option<ObjectId>,
139}
140
141impl Table {
142 #[must_use]
153 pub fn new(rows: Vec<TableRow>) -> Self {
154 Self {
155 rows,
156 width: None,
157 caption: None,
158 page_break: TablePageBreak::Cell,
159 repeat_header: true,
160 cell_spacing: None,
161 border_fill_id: None,
162 inst_id: None,
163 }
164 }
165
166 pub(crate) fn walk_paragraphs_mut(
168 &mut self,
169 f: &mut dyn FnMut(&mut crate::paragraph::Paragraph),
170 ) {
171 for row in &mut self.rows {
172 for cell in &mut row.cells {
173 for p in &mut cell.paragraphs {
174 p.walk_paragraphs_mut(f);
175 }
176 }
177 }
178 if let Some(caption) = &mut self.caption {
179 caption.walk_paragraphs_mut(f);
180 }
181 }
182
183 #[must_use]
185 pub fn with_width(mut self, width: HwpUnit) -> Self {
186 self.width = Some(width);
187 self
188 }
189
190 #[must_use]
192 pub fn with_caption(mut self, caption: Caption) -> Self {
193 self.caption = Some(caption);
194 self
195 }
196
197 #[must_use]
199 pub fn with_page_break(mut self, page_break: TablePageBreak) -> Self {
200 self.page_break = page_break;
201 self
202 }
203
204 #[must_use]
206 pub fn with_repeat_header(mut self, repeat_header: bool) -> Self {
207 self.repeat_header = repeat_header;
208 self
209 }
210
211 #[must_use]
213 pub fn with_cell_spacing(mut self, cell_spacing: HwpUnit) -> Self {
214 self.cell_spacing = Some(cell_spacing);
215 self
216 }
217
218 #[must_use]
220 pub fn with_border_fill_id(mut self, border_fill_id: u32) -> Self {
221 self.border_fill_id = Some(border_fill_id);
222 self
223 }
224
225 pub fn row_count(&self) -> usize {
227 self.rows.len()
228 }
229
230 pub fn col_count(&self) -> usize {
234 self.rows.first().map_or(0, |r| r.cells.len())
235 }
236
237 pub fn is_empty(&self) -> bool {
239 self.rows.is_empty()
240 }
241}
242
243impl std::fmt::Display for Table {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 write!(f, "Table({}x{})", self.row_count(), self.col_count())
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
265#[non_exhaustive]
266pub struct TableRow {
267 pub cells: Vec<TableCell>,
269 pub height: Option<HwpUnit>,
271 #[serde(default)]
273 pub is_header: bool,
274}
275
276impl TableRow {
277 #[must_use]
294 pub fn new(cells: Vec<TableCell>) -> Self {
295 Self { cells, height: None, is_header: false }
296 }
297
298 #[must_use]
315 pub fn with_height(cells: Vec<TableCell>, height: HwpUnit) -> Self {
316 Self { cells, height: Some(height), is_header: false }
317 }
318
319 #[must_use]
321 pub fn with_header(mut self, is_header: bool) -> Self {
322 self.is_header = is_header;
323 self
324 }
325}
326
327#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
347#[non_exhaustive]
348pub struct TableCell {
349 pub paragraphs: Vec<Paragraph>,
351 pub col_span: u16,
353 pub row_span: u16,
355 pub width: HwpUnit,
357 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub height: Option<HwpUnit>,
360 pub background: Option<Color>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
364 pub border_fill_id: Option<u32>,
365 #[serde(default, skip_serializing_if = "Option::is_none")]
367 pub margin: Option<TableMargin>,
368 #[serde(default, skip_serializing_if = "Option::is_none")]
370 pub vertical_align: Option<TableVerticalAlign>,
371}
372
373impl TableCell {
374 #[must_use]
392 pub fn new(paragraphs: Vec<Paragraph>, width: HwpUnit) -> Self {
393 Self {
394 paragraphs,
395 col_span: 1,
396 row_span: 1,
397 width,
398 height: None,
399 background: None,
400 border_fill_id: None,
401 margin: None,
402 vertical_align: None,
403 }
404 }
405
406 #[must_use]
425 pub fn with_span(
426 paragraphs: Vec<Paragraph>,
427 width: HwpUnit,
428 col_span: u16,
429 row_span: u16,
430 ) -> Self {
431 Self {
432 paragraphs,
433 col_span,
434 row_span,
435 width,
436 height: None,
437 background: None,
438 border_fill_id: None,
439 margin: None,
440 vertical_align: None,
441 }
442 }
443
444 #[must_use]
446 pub fn with_height(mut self, height: HwpUnit) -> Self {
447 self.height = Some(height);
448 self
449 }
450
451 #[must_use]
453 pub fn with_background(mut self, background: Color) -> Self {
454 self.background = Some(background);
455 self
456 }
457
458 #[must_use]
460 pub fn with_border_fill_id(mut self, border_fill_id: u32) -> Self {
461 self.border_fill_id = Some(border_fill_id);
462 self
463 }
464
465 #[must_use]
467 pub fn with_margin(mut self, margin: TableMargin) -> Self {
468 self.margin = Some(margin);
469 self
470 }
471
472 #[must_use]
474 pub fn with_vertical_align(mut self, vertical_align: TableVerticalAlign) -> Self {
475 self.vertical_align = Some(vertical_align);
476 self
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483 use crate::run::Run;
484 use hwpforge_foundation::{CharShapeIndex, ParaShapeIndex};
485
486 fn simple_paragraph() -> Paragraph {
487 Paragraph::with_runs(
488 vec![Run::text("cell", CharShapeIndex::new(0))],
489 ParaShapeIndex::new(0),
490 )
491 }
492
493 fn simple_cell() -> TableCell {
494 TableCell::new(vec![simple_paragraph()], HwpUnit::from_mm(50.0).unwrap())
495 }
496
497 fn simple_row() -> TableRow {
498 TableRow::new(vec![simple_cell(), simple_cell()])
499 }
500
501 fn simple_table() -> Table {
502 Table::new(vec![simple_row(), simple_row()])
503 }
504
505 #[test]
506 fn table_new() {
507 let t = simple_table();
508 assert_eq!(t.row_count(), 2);
509 assert_eq!(t.col_count(), 2);
510 assert!(!t.is_empty());
511 assert!(t.width.is_none());
512 assert!(t.caption.is_none());
513 assert_eq!(t.page_break, TablePageBreak::Cell);
514 assert!(t.repeat_header);
515 assert!(t.cell_spacing.is_none());
516 assert!(t.border_fill_id.is_none());
517 }
518
519 #[test]
520 fn empty_table() {
521 let t = Table::new(vec![]);
522 assert_eq!(t.row_count(), 0);
523 assert_eq!(t.col_count(), 0);
524 assert!(t.is_empty());
525 }
526
527 #[test]
528 fn table_with_caption() {
529 let t = simple_table().with_caption(crate::caption::Caption::default());
530 assert!(t.caption.is_some());
531 }
532
533 #[test]
534 fn table_with_width() {
535 let t = simple_table().with_width(HwpUnit::from_mm(150.0).unwrap());
536 assert!(t.width.is_some());
537 }
538
539 #[test]
540 fn table_with_page_break() {
541 let t = simple_table().with_page_break(TablePageBreak::Table);
542 assert_eq!(t.page_break, TablePageBreak::Table);
543 }
544
545 #[test]
546 fn table_with_repeat_header_disabled() {
547 let t = simple_table().with_repeat_header(false);
548 assert!(!t.repeat_header);
549 }
550
551 #[test]
552 fn cell_new_defaults() {
553 let cell = simple_cell();
554 assert_eq!(cell.col_span, 1);
555 assert_eq!(cell.row_span, 1);
556 assert!(cell.height.is_none());
557 assert!(cell.background.is_none());
558 assert!(cell.border_fill_id.is_none());
559 assert!(cell.margin.is_none());
560 assert!(cell.vertical_align.is_none());
561 assert_eq!(cell.paragraphs.len(), 1);
562 }
563
564 #[test]
565 fn cell_with_span() {
566 let cell =
567 TableCell::with_span(vec![simple_paragraph()], HwpUnit::from_mm(100.0).unwrap(), 3, 2);
568 assert_eq!(cell.col_span, 3);
569 assert_eq!(cell.row_span, 2);
570 }
571
572 #[test]
573 fn cell_with_background() {
574 let cell = simple_cell().with_background(Color::from_rgb(200, 200, 200));
575 assert!(cell.background.is_some());
576 }
577
578 #[test]
579 fn table_display() {
580 let t = simple_table();
581 assert_eq!(t.to_string(), "Table(2x2)");
582 }
583
584 #[test]
585 fn single_cell_table() {
586 let table = Table::new(vec![TableRow::with_height(
587 vec![simple_cell()],
588 HwpUnit::from_mm(10.0).unwrap(),
589 )]);
590 assert_eq!(table.row_count(), 1);
591 assert_eq!(table.col_count(), 1);
592 }
593
594 #[test]
595 fn row_with_fixed_height() {
596 let row = TableRow::with_height(vec![simple_cell()], HwpUnit::from_mm(25.0).unwrap());
597 assert!(row.height.is_some());
598 }
599
600 #[test]
601 fn row_new_auto_height() {
602 let row = TableRow::new(vec![simple_cell(), simple_cell()]);
603 assert_eq!(row.cells.len(), 2);
604 assert!(row.height.is_none());
605 }
606
607 #[test]
608 fn row_new_empty_cells() {
609 let row = TableRow::new(vec![]);
610 assert!(row.cells.is_empty());
611 assert!(row.height.is_none());
612 }
613
614 #[test]
615 fn row_with_height_constructor() {
616 let h = HwpUnit::from_mm(20.0).unwrap();
617 let row = TableRow::with_height(vec![simple_cell()], h);
618 assert_eq!(row.cells.len(), 1);
619 assert_eq!(row.height, Some(h));
620 }
621
622 #[test]
623 fn equality() {
624 let a = simple_table();
625 let b = simple_table();
626 assert_eq!(a, b);
627 }
628
629 #[test]
630 fn clone_independence() {
631 let t = simple_table();
632 let mut cloned = t.clone();
633 cloned.caption = Some(crate::caption::Caption::default());
634 assert!(t.caption.is_none());
635 }
636
637 #[test]
638 fn serde_roundtrip() {
639 let t = simple_table();
640 let json = serde_json::to_string(&t).unwrap();
641 let back: Table = serde_json::from_str(&json).unwrap();
642 assert_eq!(t, back);
643 }
644
645 #[test]
646 fn serde_with_all_optional_fields() {
647 let mut t = simple_table()
648 .with_width(HwpUnit::from_mm(150.0).unwrap())
649 .with_caption(crate::caption::Caption::default())
650 .with_page_break(TablePageBreak::None)
651 .with_repeat_header(false)
652 .with_cell_spacing(HwpUnit::from_mm(2.0).unwrap())
653 .with_border_fill_id(7);
654 t.rows[0].height = Some(HwpUnit::from_mm(20.0).unwrap());
655 t.rows[0].cells[0] = t.rows[0].cells[0]
656 .clone()
657 .with_background(Color::from_rgb(255, 0, 0))
658 .with_height(HwpUnit::from_mm(8.0).unwrap())
659 .with_border_fill_id(9)
660 .with_margin(TableMargin {
661 left: HwpUnit::from_mm(1.0).unwrap(),
662 right: HwpUnit::from_mm(2.0).unwrap(),
663 top: HwpUnit::from_mm(0.5).unwrap(),
664 bottom: HwpUnit::from_mm(0.25).unwrap(),
665 })
666 .with_vertical_align(TableVerticalAlign::Bottom);
667
668 let json = serde_json::to_string(&t).unwrap();
669 let back: Table = serde_json::from_str(&json).unwrap();
670 assert_eq!(t, back);
671 }
672
673 #[test]
674 fn serde_defaults_missing_new_fields() {
675 let json = r#"{"rows":[],"width":null,"caption":null}"#;
676 let back: Table = serde_json::from_str(json).unwrap();
677 assert_eq!(back.page_break, TablePageBreak::Cell);
678 assert!(back.repeat_header);
679 assert!(back.cell_spacing.is_none());
680 assert!(back.border_fill_id.is_none());
681 }
682
683 #[test]
684 fn table_margin_defaults_to_zero() {
685 let margin = TableMargin::default();
686 assert_eq!(margin.left, HwpUnit::ZERO);
687 assert_eq!(margin.right, HwpUnit::ZERO);
688 assert_eq!(margin.top, HwpUnit::ZERO);
689 assert_eq!(margin.bottom, HwpUnit::ZERO);
690 }
691
692 #[test]
693 fn cell_zero_span_allowed_at_construction() {
694 let cell = TableCell::with_span(
696 vec![simple_paragraph()],
697 HwpUnit::from_mm(50.0).unwrap(),
698 0, 0,
700 );
701 assert_eq!(cell.col_span, 0);
702 assert_eq!(cell.row_span, 0);
703 }
704
705 #[test]
706 fn row_new_sets_expected_defaults() {
707 let cells = vec![simple_cell()];
708 let row = TableRow::new(cells.clone());
709 assert_eq!(row.cells, cells);
710 assert!(row.height.is_none());
711 assert!(!row.is_header);
712 }
713}