1use crate::error::Error;
4
5#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
6pub struct Table {
7 pub id: String,
8 pub rows: u16,
9 pub cols: u16,
10 pub caption: Option<String>,
11 pub cells: Vec<Vec<Option<Cell>>>,
12}
13
14#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
15pub struct Cell {
16 pub col: u16,
17 pub row: u16,
18 pub col_span: u16,
19 pub row_span: u16,
20 pub text: String,
21 pub paragraphs: Vec<String>,
22}
23
24#[derive(Debug, Clone)]
25pub(crate) struct TableProps {
26 pub rows: u16,
27 pub cols: u16,
28}
29
30pub(crate) fn parse_table_payload(p: &[u8]) -> Result<TableProps, Error> {
31 if p.len() < 22 {
32 return Err(Error::Record(format!(
33 "HWPTAG_TABLE too short: {}",
34 p.len()
35 )));
36 }
37 let rows = u16::from_le_bytes(p[4..6].try_into().unwrap());
38 let cols = u16::from_le_bytes(p[6..8].try_into().unwrap());
39 Ok(TableProps { rows, cols })
41}
42
43#[derive(Debug, Clone)]
44pub(crate) struct CellListHeader {
45 pub para_count: i16,
46 pub col: u16,
47 pub row: u16,
48 pub col_span: u16,
49 pub row_span: u16,
50}
51
52pub(crate) fn parse_cell_list_header(p: &[u8]) -> Result<CellListHeader, Error> {
53 if p.len() < 34 {
54 return Err(Error::Record(format!(
55 "cell LIST_HEADER too short: {}",
56 p.len()
57 )));
58 }
59 let para_count = i16::from_le_bytes(p[0..2].try_into().unwrap());
62 let col = u16::from_le_bytes(p[8..10].try_into().unwrap());
63 let row = u16::from_le_bytes(p[10..12].try_into().unwrap());
64 let col_span = u16::from_le_bytes(p[12..14].try_into().unwrap());
65 let row_span = u16::from_le_bytes(p[14..16].try_into().unwrap());
66 Ok(CellListHeader {
67 para_count,
68 col,
69 row,
70 col_span,
71 row_span,
72 })
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn build_table_props(rows: u16, cols: u16, zones: u16) -> Vec<u8> {
80 let mut p = Vec::new();
81 p.extend_from_slice(&0u32.to_le_bytes()); p.extend_from_slice(&rows.to_le_bytes());
83 p.extend_from_slice(&cols.to_le_bytes());
84 p.extend_from_slice(&0u16.to_le_bytes()); p.extend_from_slice(&[0u8; 8]); for _ in 0..rows {
87 p.extend_from_slice(&1000u16.to_le_bytes());
88 }
89 p.extend_from_slice(&0u16.to_le_bytes()); p.extend_from_slice(&zones.to_le_bytes());
91 for _ in 0..zones {
92 p.extend_from_slice(&[0u8; 10]);
93 }
94 p
95 }
96
97 #[test]
98 fn parses_2x3_no_zones() {
99 let p = build_table_props(2, 3, 0);
100 assert_eq!(p.len(), 22 + 4);
101 let tp = parse_table_payload(&p).unwrap();
102 assert_eq!((tp.rows, tp.cols), (2, 3));
103 }
104
105 #[test]
106 fn parses_with_zones() {
107 let p = build_table_props(3, 1, 1);
108 assert_eq!(p.len(), 22 + 6 + 10);
109 let tp = parse_table_payload(&p).unwrap();
110 assert_eq!((tp.rows, tp.cols), (3, 1));
111 }
112
113 #[test]
114 fn rejects_truncated() {
115 let p = vec![0u8; 10];
116 assert!(parse_table_payload(&p).is_err());
117 }
118
119 #[test]
120 fn parses_cell_list_header() {
121 let mut p = Vec::new();
122 p.extend_from_slice(&1i16.to_le_bytes()); p.extend_from_slice(&[0u8; 4]); p.extend_from_slice(&[0u8; 2]); p.extend_from_slice(&2u16.to_le_bytes()); p.extend_from_slice(&1u16.to_le_bytes()); p.extend_from_slice(&1u16.to_le_bytes()); p.extend_from_slice(&1u16.to_le_bytes()); p.extend_from_slice(&[0u8; 18]); let h = parse_cell_list_header(&p).unwrap();
131 assert_eq!((h.col, h.row, h.col_span, h.row_span), (2, 1, 1, 1));
132 assert_eq!(h.para_count, 1);
133 }
134}