Skip to main content

extend_docx/doc/
tap.rs

1//! Table properties (TAP): tables have no stored structure in the binary
2//! format — each row's geometry lives in table sprms on the row-end
3//! paragraph's PAPX (sprmTDefTable defines the cell boundaries and per-cell
4//! TC80 formatting). The document builder feeds those grpprls here.
5
6use super::fmt::{parse_brc80, parse_brc97, parse_shd80, parse_shd_operand, Border, Shading};
7use super::sprm::{self, Sprm};
8
9#[derive(Debug, Default, Clone)]
10pub struct TableCell {
11    /// Right boundary of the cell in twips, relative to the row origin.
12    pub boundary_left: i32,
13    pub boundary_right: i32,
14    pub f_first_merged: bool,
15    pub f_merged: bool,
16    pub f_vert_merge: bool,
17    pub f_vert_restart: bool,
18    pub f_vertical: bool,
19    pub f_backward: bool,
20    pub vert_align: u8,
21    pub brc_top: Option<Border>,
22    pub brc_left: Option<Border>,
23    pub brc_bottom: Option<Border>,
24    pub brc_right: Option<Border>,
25    pub shd: Option<Shading>,
26}
27
28#[derive(Debug, Default, Clone)]
29pub struct Tap {
30    pub jc: u8,
31    pub dxa_gap_half: i16,
32    pub dya_row_height: i16,
33    pub f_cant_split: bool,
34    pub f_table_header: bool,
35    pub cells: Vec<TableCell>,
36    pub table_borders: [Option<Border>; 6], // top, left, bottom, right, insideH, insideV
37}
38
39impl Tap {
40    pub fn from_grpprl(grpprl: &[u8]) -> Tap {
41        let mut tap = Tap::default();
42        let mut shd_queue: Vec<Shading> = Vec::new();
43        let mut shd_assigned = 0usize;
44        for sprm in sprm::iterate(grpprl) {
45            tap.apply_sprm(&sprm, &mut shd_queue, &mut shd_assigned);
46        }
47        // Distribute any cell shadings parsed before/after sprmTDefTable.
48        for (index, shading) in shd_queue.into_iter().enumerate() {
49            if let Some(cell) = tap.cells.get_mut(index) {
50                if cell.shd.is_none() && shading.is_visible() {
51                    cell.shd = Some(shading);
52                }
53            }
54        }
55        tap
56    }
57
58    fn apply_sprm(
59        &mut self,
60        sprm: &Sprm<'_>,
61        shd_queue: &mut Vec<Shading>,
62        shd_assigned: &mut usize,
63    ) {
64        match sprm.opcode {
65            0x5400 | 0x548A => self.jc = sprm.byte(),
66            0x9602 => self.dxa_gap_half = sprm.i16_operand(),
67            0x9407 => self.dya_row_height = sprm.i16_operand(),
68            0x3403 => self.f_cant_split = sprm.byte() == 1,
69            0x3404 => self.f_table_header = sprm.byte() == 1,
70            0xD608 => self.apply_tdef_table(sprm.operand),
71            0xD605 => {
72                for (index, chunk) in sprm.operand.chunks_exact(4).take(6).enumerate() {
73                    self.table_borders[index] = parse_brc80(chunk);
74                }
75            }
76            0xD613 => {
77                for (index, chunk) in sprm.operand.chunks_exact(8).take(6).enumerate() {
78                    let border = parse_brc97(chunk);
79                    if border.is_some() {
80                        self.table_borders[index] = border;
81                    }
82                }
83            }
84            0xD609 => {
85                // sprmTDefTableShd80: SHD80 per cell.
86                for chunk in sprm.operand.chunks_exact(2) {
87                    if let Some(shading) = parse_shd80(u16::from_le_bytes([chunk[0], chunk[1]])) {
88                        shd_queue.push(shading);
89                    }
90                }
91                let _ = shd_assigned;
92            }
93            0xD612 => {
94                // sprmTDefTableShd: SHDOperand per cell.
95                for chunk in sprm.operand.chunks_exact(10) {
96                    if let Some(shading) = parse_shd_operand(chunk) {
97                        shd_queue.push(shading);
98                    }
99                }
100            }
101            0xD620 => self.apply_set_brc80(sprm.operand),
102            0xD62B => {
103                // sprmTVertMerge: itc + merge code (0 none, 1 continue, 3 restart).
104                if sprm.operand.len() >= 2 {
105                    let itc = sprm.operand[0] as usize;
106                    let code = sprm.operand[1];
107                    if let Some(cell) = self.cells.get_mut(itc) {
108                        cell.f_vert_merge = code & 0x01 != 0;
109                        cell.f_vert_restart = code & 0x02 != 0;
110                    }
111                }
112            }
113            0xD62C => {
114                // sprmTVertAlign: itcFirst, itcLim, vertAlign.
115                if sprm.operand.len() >= 3 {
116                    let first = sprm.operand[0] as usize;
117                    let lim = sprm.operand[1] as usize;
118                    for itc in first..lim.min(self.cells.len()) {
119                        self.cells[itc].vert_align = sprm.operand[2];
120                    }
121                }
122            }
123            0x7627 | 0x7628 => {
124                // sprmTSetShd80/sprmTSetShdOdd80: itcFirst, itcLim?, shd — legacy;
125                // operand is 4 bytes: itcFirst, itcLim, SHD80.
126                if sprm.operand.len() >= 4 {
127                    let first = sprm.operand[0] as usize;
128                    let lim = sprm.operand[1] as usize;
129                    let shading = parse_shd80(u16::from_le_bytes([sprm.operand[2], sprm.operand[3]]));
130                    if let Some(shading) = shading {
131                        for itc in first..lim.min(self.cells.len()) {
132                            self.cells[itc].shd = Some(shading.clone());
133                        }
134                    }
135                }
136            }
137            _ => {}
138        }
139    }
140
141    /// sprmTDefTable: itcMac, rgdxaCenter[itcMac+1], rgTc80[..itcMac].
142    fn apply_tdef_table(&mut self, operand: &[u8]) {
143        let Some(&itc_mac) = operand.first() else {
144            return;
145        };
146        let count = itc_mac as usize;
147        let centers_end = 1 + (count + 1) * 2;
148        if operand.len() < centers_end || count == 0 {
149            return;
150        }
151        let mut centers = Vec::with_capacity(count + 1);
152        for index in 0..=count {
153            let offset = 1 + index * 2;
154            centers.push(i16::from_le_bytes([operand[offset], operand[offset + 1]]) as i32);
155        }
156
157        self.cells = (0..count)
158            .map(|index| {
159                let mut cell = TableCell {
160                    boundary_left: centers[index],
161                    boundary_right: centers[index + 1],
162                    ..TableCell::default()
163                };
164                let tc_offset = centers_end + index * 20;
165                if let Some(tc) = operand.get(tc_offset..tc_offset + 20) {
166                    let rgf = u16::from_le_bytes([tc[0], tc[1]]);
167                    cell.f_first_merged = rgf & 0x0001 != 0;
168                    cell.f_merged = rgf & 0x0002 != 0;
169                    cell.f_vertical = rgf & 0x0004 != 0;
170                    cell.f_backward = rgf & 0x0008 != 0;
171                    cell.f_vert_merge = rgf & 0x0020 != 0;
172                    cell.f_vert_restart = rgf & 0x0040 != 0;
173                    cell.vert_align = ((rgf >> 7) & 0x3) as u8;
174                    cell.brc_top = parse_brc80(&tc[4..8]);
175                    cell.brc_left = parse_brc80(&tc[8..12]);
176                    cell.brc_bottom = parse_brc80(&tc[12..16]);
177                    cell.brc_right = parse_brc80(&tc[16..20]);
178                }
179                cell
180            })
181            .collect();
182    }
183
184    /// sprmTSetBrc80: itcFirst, itcLim, grfbrc, BRC80 — applies one border to a
185    /// range of cells on the sides selected by grfbrc.
186    fn apply_set_brc80(&mut self, operand: &[u8]) {
187        if operand.len() < 7 {
188            return;
189        }
190        let first = operand[0] as usize;
191        let lim = operand[1] as usize;
192        let grfbrc = operand[2];
193        let border = parse_brc80(&operand[3..7]);
194        for itc in first..lim.min(self.cells.len()) {
195            let cell = &mut self.cells[itc];
196            if grfbrc & 0x01 != 0 {
197                cell.brc_top = border.clone();
198            }
199            if grfbrc & 0x02 != 0 {
200                cell.brc_left = border.clone();
201            }
202            if grfbrc & 0x04 != 0 {
203                cell.brc_bottom = border.clone();
204            }
205            if grfbrc & 0x08 != 0 {
206                cell.brc_right = border.clone();
207            }
208        }
209    }
210
211    pub fn row_left_edge(&self) -> i32 {
212        self.cells.first().map(|cell| cell.boundary_left).unwrap_or(0)
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    fn tdef_operand(centers: &[i16], tc_flags: &[u16]) -> Vec<u8> {
221        let count = centers.len() - 1;
222        let mut operand = vec![count as u8];
223        for center in centers {
224            operand.extend_from_slice(&center.to_le_bytes());
225        }
226        for index in 0..count {
227            let mut tc = [0u8; 20];
228            tc[..2].copy_from_slice(&tc_flags.get(index).copied().unwrap_or(0).to_le_bytes());
229            operand.extend_from_slice(&tc);
230        }
231        operand
232    }
233
234    #[test]
235    fn parses_tdef_table_boundaries() {
236        let operand = tdef_operand(&[0, 2400, 4800, 9360], &[0, 0, 0]);
237        let mut grpprl: Vec<u8> = vec![0x08, 0xD6];
238        grpprl.extend_from_slice(&((operand.len() + 1) as u16).to_le_bytes());
239        grpprl.extend_from_slice(&operand);
240
241        let tap = Tap::from_grpprl(&grpprl);
242        assert_eq!(tap.cells.len(), 3);
243        assert_eq!(tap.cells[0].boundary_left, 0);
244        assert_eq!(tap.cells[0].boundary_right, 2400);
245        assert_eq!(tap.cells[2].boundary_right, 9360);
246    }
247
248    #[test]
249    fn parses_vert_merge_flags() {
250        let operand = tdef_operand(&[0, 1000, 2000], &[0x0040, 0x0020]);
251        let mut grpprl: Vec<u8> = vec![0x08, 0xD6];
252        grpprl.extend_from_slice(&((operand.len() + 1) as u16).to_le_bytes());
253        grpprl.extend_from_slice(&operand);
254
255        let tap = Tap::from_grpprl(&grpprl);
256        assert!(tap.cells[0].f_vert_restart);
257        assert!(tap.cells[1].f_vert_merge);
258        assert!(!tap.cells[1].f_vert_restart);
259    }
260}