Skip to main content

rust_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    cell_padding_default: [Option<u16>; 4],
38    cell_padding: Vec<[Option<u16>; 4]>,
39}
40
41impl Tap {
42    pub fn from_grpprl(grpprl: &[u8]) -> Tap {
43        let mut tap = Tap::default();
44        let mut shd_queue: Vec<Shading> = Vec::new();
45        let mut shd_assigned = 0usize;
46        for sprm in sprm::iterate(grpprl) {
47            tap.apply_sprm(&sprm, &mut shd_queue, &mut shd_assigned);
48        }
49        // Distribute any cell shadings parsed before/after sprmTDefTable.
50        for (index, shading) in shd_queue.into_iter().enumerate() {
51            if let Some(cell) = tap.cells.get_mut(index) {
52                if cell.shd.is_none() && shading.is_visible() {
53                    cell.shd = Some(shading);
54                }
55            }
56        }
57        tap
58    }
59
60    fn apply_sprm(
61        &mut self,
62        sprm: &Sprm<'_>,
63        shd_queue: &mut Vec<Shading>,
64        shd_assigned: &mut usize,
65    ) {
66        match sprm.opcode {
67            0x5400 | 0x548A => self.jc = sprm.byte(),
68            0x9602 => self.dxa_gap_half = sprm.i16_operand(),
69            0x9407 => self.dya_row_height = sprm.i16_operand(),
70            0x3403 => self.f_cant_split = sprm.byte() == 1,
71            0x3404 => self.f_table_header = sprm.byte() == 1,
72            0xD608 => self.apply_tdef_table(sprm.operand),
73            0xD605 => {
74                for (index, chunk) in sprm.operand.chunks_exact(4).take(6).enumerate() {
75                    self.table_borders[index] = parse_brc80(chunk);
76                }
77            }
78            0xD613 => {
79                for (index, chunk) in sprm.operand.chunks_exact(8).take(6).enumerate() {
80                    let border = parse_brc97(chunk);
81                    if border.is_some() {
82                        self.table_borders[index] = border;
83                    }
84                }
85            }
86            0xD609 => {
87                // sprmTDefTableShd80: SHD80 per cell.
88                for chunk in sprm.operand.chunks_exact(2) {
89                    if let Some(shading) = parse_shd80(u16::from_le_bytes([chunk[0], chunk[1]])) {
90                        shd_queue.push(shading);
91                    }
92                }
93                let _ = shd_assigned;
94            }
95            0xD612 => {
96                // sprmTDefTableShd: SHDOperand per cell.
97                for chunk in sprm.operand.chunks_exact(10) {
98                    if let Some(shading) = parse_shd_operand(chunk) {
99                        shd_queue.push(shading);
100                    }
101                }
102            }
103            0xD620 => self.apply_set_brc80(sprm.operand),
104            0xD632 => self.apply_cell_padding(sprm.operand, false),
105            0xD634 => self.apply_cell_padding(sprm.operand, true),
106            0xD62B => {
107                // sprmTVertMerge: itc + merge code (0 none, 1 continue, 3 restart).
108                if sprm.operand.len() >= 2 {
109                    let itc = sprm.operand[0] as usize;
110                    let code = sprm.operand[1];
111                    if let Some(cell) = self.cells.get_mut(itc) {
112                        cell.f_vert_merge = code & 0x01 != 0;
113                        cell.f_vert_restart = code & 0x02 != 0;
114                    }
115                }
116            }
117            0xD62C => {
118                // sprmTVertAlign: itcFirst, itcLim, vertAlign.
119                if sprm.operand.len() >= 3 {
120                    let first = sprm.operand[0] as usize;
121                    let lim = sprm.operand[1] as usize;
122                    for itc in first..lim.min(self.cells.len()) {
123                        self.cells[itc].vert_align = sprm.operand[2];
124                    }
125                }
126            }
127            0x7627 | 0x7628 => {
128                // sprmTSetShd80/sprmTSetShdOdd80: itcFirst, itcLim?, shd — legacy;
129                // operand is 4 bytes: itcFirst, itcLim, SHD80.
130                if sprm.operand.len() >= 4 {
131                    let first = sprm.operand[0] as usize;
132                    let lim = sprm.operand[1] as usize;
133                    let shading = parse_shd80(u16::from_le_bytes([sprm.operand[2], sprm.operand[3]]));
134                    if let Some(shading) = shading {
135                        for itc in first..lim.min(self.cells.len()) {
136                            self.cells[itc].shd = Some(shading.clone());
137                        }
138                    }
139                }
140            }
141            _ => {}
142        }
143    }
144
145    /// sprmTDefTable: itcMac, rgdxaCenter[itcMac+1], rgTc80[..itcMac].
146    fn apply_tdef_table(&mut self, operand: &[u8]) {
147        let Some(&itc_mac) = operand.first() else {
148            return;
149        };
150        let count = itc_mac as usize;
151        let centers_end = 1 + (count + 1) * 2;
152        if operand.len() < centers_end || count == 0 {
153            return;
154        }
155        let mut centers = Vec::with_capacity(count + 1);
156        for index in 0..=count {
157            let offset = 1 + index * 2;
158            centers.push(i16::from_le_bytes([operand[offset], operand[offset + 1]]) as i32);
159        }
160
161        self.cells = (0..count)
162            .map(|index| {
163                let mut cell = TableCell {
164                    boundary_left: centers[index],
165                    boundary_right: centers[index + 1],
166                    ..TableCell::default()
167                };
168                let tc_offset = centers_end + index * 20;
169                if let Some(tc) = operand.get(tc_offset..tc_offset + 20) {
170                    let rgf = u16::from_le_bytes([tc[0], tc[1]]);
171                    cell.f_first_merged = rgf & 0x0001 != 0;
172                    cell.f_merged = rgf & 0x0002 != 0;
173                    cell.f_vertical = rgf & 0x0004 != 0;
174                    cell.f_backward = rgf & 0x0008 != 0;
175                    cell.f_vert_merge = rgf & 0x0020 != 0;
176                    cell.f_vert_restart = rgf & 0x0040 != 0;
177                    cell.vert_align = ((rgf >> 7) & 0x3) as u8;
178                    cell.brc_top = parse_brc80(&tc[4..8]);
179                    cell.brc_left = parse_brc80(&tc[8..12]);
180                    cell.brc_bottom = parse_brc80(&tc[12..16]);
181                    cell.brc_right = parse_brc80(&tc[16..20]);
182                }
183                cell
184            })
185            .collect();
186    }
187
188    /// sprmTSetBrc80: itcFirst, itcLim, grfbrc, BRC80 — applies one border to a
189    /// range of cells on the sides selected by grfbrc.
190    fn apply_set_brc80(&mut self, operand: &[u8]) {
191        if operand.len() < 7 {
192            return;
193        }
194        let first = operand[0] as usize;
195        let lim = operand[1] as usize;
196        let grfbrc = operand[2];
197        let border = parse_brc80(&operand[3..7]);
198        for itc in first..lim.min(self.cells.len()) {
199            let cell = &mut self.cells[itc];
200            if grfbrc & 0x01 != 0 {
201                cell.brc_top = border.clone();
202            }
203            if grfbrc & 0x02 != 0 {
204                cell.brc_left = border.clone();
205            }
206            if grfbrc & 0x04 != 0 {
207                cell.brc_bottom = border.clone();
208            }
209            if grfbrc & 0x08 != 0 {
210                cell.brc_right = border.clone();
211            }
212        }
213    }
214
215    pub fn row_left_edge(&self) -> i32 {
216        self.cells.first().map(|cell| cell.boundary_left).unwrap_or(0)
217    }
218
219    fn apply_cell_padding(&mut self, operand: &[u8], row_default: bool) {
220        if operand.len() != 6 || operand[2] & !0x0F != 0 {
221            return;
222        }
223        let first = operand[0] as usize;
224        let limit = operand[1] as usize;
225        if first >= limit || limit > 64 || (row_default && (first != 0 || limit != 1)) {
226            return;
227        }
228        let width = u16::from_le_bytes([operand[4], operand[5]]);
229        let width = match operand[3] {
230            0 if width == 0 => None,
231            3 if width <= 31680 => Some(width),
232            _ => return,
233        };
234        let apply = |padding: &mut [Option<u16>; 4]| {
235            for (side, value) in padding.iter_mut().enumerate() {
236                if operand[2] & (1 << side) != 0 {
237                    *value = width;
238                }
239            }
240        };
241        if row_default {
242            apply(&mut self.cell_padding_default);
243        } else {
244            self.cell_padding
245                .resize(self.cell_padding.len().max(limit), [None; 4]);
246            for padding in &mut self.cell_padding[first..limit] {
247                apply(padding);
248            }
249        }
250    }
251
252    pub fn default_cell_padding_twips(&self) -> [u16; 4] {
253        let gap = self.dxa_gap_half.max(0) as u16;
254        let fallback = [0, gap, 0, gap];
255        std::array::from_fn(|side| self.cell_padding_default[side].unwrap_or(fallback[side]))
256    }
257
258    pub fn cell_padding_twips(&self, cell_index: usize) -> [u16; 4] {
259        let defaults = self.default_cell_padding_twips();
260        std::array::from_fn(|side| {
261            self.cell_padding
262                .get(cell_index)
263                .and_then(|padding| padding[side])
264                .unwrap_or(defaults[side])
265        })
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    fn tdef_operand(centers: &[i16], tc_flags: &[u16]) -> Vec<u8> {
274        let count = centers.len() - 1;
275        let mut operand = vec![count as u8];
276        for center in centers {
277            operand.extend_from_slice(&center.to_le_bytes());
278        }
279        for index in 0..count {
280            let mut tc = [0u8; 20];
281            tc[..2].copy_from_slice(&tc_flags.get(index).copied().unwrap_or(0).to_le_bytes());
282            operand.extend_from_slice(&tc);
283        }
284        operand
285    }
286
287    #[test]
288    fn parses_tdef_table_boundaries() {
289        let operand = tdef_operand(&[0, 2400, 4800, 9360], &[0, 0, 0]);
290        let mut grpprl: Vec<u8> = vec![0x08, 0xD6];
291        grpprl.extend_from_slice(&((operand.len() + 1) as u16).to_le_bytes());
292        grpprl.extend_from_slice(&operand);
293
294        let tap = Tap::from_grpprl(&grpprl);
295        assert_eq!(tap.cells.len(), 3);
296        assert_eq!(tap.cells[0].boundary_left, 0);
297        assert_eq!(tap.cells[0].boundary_right, 2400);
298        assert_eq!(tap.cells[2].boundary_right, 9360);
299    }
300
301    #[test]
302    fn parses_vert_merge_flags() {
303        let operand = tdef_operand(&[0, 1000, 2000], &[0x0040, 0x0020]);
304        let mut grpprl: Vec<u8> = vec![0x08, 0xD6];
305        grpprl.extend_from_slice(&((operand.len() + 1) as u16).to_le_bytes());
306        grpprl.extend_from_slice(&operand);
307
308        let tap = Tap::from_grpprl(&grpprl);
309        assert!(tap.cells[0].f_vert_restart);
310        assert!(tap.cells[1].f_vert_merge);
311        assert!(!tap.cells[1].f_vert_restart);
312    }
313}