use super::fmt::{parse_brc80, parse_brc97, parse_shd80, parse_shd_operand, Border, Shading};
use super::sprm::{self, Sprm};
#[derive(Debug, Default, Clone)]
pub struct TableCell {
pub boundary_left: i32,
pub boundary_right: i32,
pub f_first_merged: bool,
pub f_merged: bool,
pub f_vert_merge: bool,
pub f_vert_restart: bool,
pub f_vertical: bool,
pub f_backward: bool,
pub vert_align: u8,
pub brc_top: Option<Border>,
pub brc_left: Option<Border>,
pub brc_bottom: Option<Border>,
pub brc_right: Option<Border>,
pub shd: Option<Shading>,
}
#[derive(Debug, Default, Clone)]
pub struct Tap {
pub jc: u8,
pub dxa_gap_half: i16,
pub dya_row_height: i16,
pub f_cant_split: bool,
pub f_table_header: bool,
pub cells: Vec<TableCell>,
pub table_borders: [Option<Border>; 6], cell_padding_default: [Option<u16>; 4],
cell_padding: Vec<[Option<u16>; 4]>,
}
impl Tap {
pub fn from_grpprl(grpprl: &[u8]) -> Tap {
let mut tap = Tap::default();
let mut shd_queue: Vec<Shading> = Vec::new();
let mut shd_assigned = 0usize;
for sprm in sprm::iterate(grpprl) {
tap.apply_sprm(&sprm, &mut shd_queue, &mut shd_assigned);
}
for (index, shading) in shd_queue.into_iter().enumerate() {
if let Some(cell) = tap.cells.get_mut(index) {
if cell.shd.is_none() && shading.is_visible() {
cell.shd = Some(shading);
}
}
}
tap
}
fn apply_sprm(
&mut self,
sprm: &Sprm<'_>,
shd_queue: &mut Vec<Shading>,
shd_assigned: &mut usize,
) {
match sprm.opcode {
0x5400 | 0x548A => self.jc = sprm.byte(),
0x9602 => self.dxa_gap_half = sprm.i16_operand(),
0x9407 => self.dya_row_height = sprm.i16_operand(),
0x3403 => self.f_cant_split = sprm.byte() == 1,
0x3404 => self.f_table_header = sprm.byte() == 1,
0xD608 => self.apply_tdef_table(sprm.operand),
0xD605 => {
for (index, chunk) in sprm.operand.chunks_exact(4).take(6).enumerate() {
self.table_borders[index] = parse_brc80(chunk);
}
}
0xD613 => {
for (index, chunk) in sprm.operand.chunks_exact(8).take(6).enumerate() {
let border = parse_brc97(chunk);
if border.is_some() {
self.table_borders[index] = border;
}
}
}
0xD609 => {
for chunk in sprm.operand.chunks_exact(2) {
if let Some(shading) = parse_shd80(u16::from_le_bytes([chunk[0], chunk[1]])) {
shd_queue.push(shading);
}
}
let _ = shd_assigned;
}
0xD612 => {
for chunk in sprm.operand.chunks_exact(10) {
if let Some(shading) = parse_shd_operand(chunk) {
shd_queue.push(shading);
}
}
}
0xD620 => self.apply_set_brc80(sprm.operand),
0xD632 => self.apply_cell_padding(sprm.operand, false),
0xD634 => self.apply_cell_padding(sprm.operand, true),
0xD62B => {
if sprm.operand.len() >= 2 {
let itc = sprm.operand[0] as usize;
let code = sprm.operand[1];
if let Some(cell) = self.cells.get_mut(itc) {
cell.f_vert_merge = code & 0x01 != 0;
cell.f_vert_restart = code & 0x02 != 0;
}
}
}
0xD62C => {
if sprm.operand.len() >= 3 {
let first = sprm.operand[0] as usize;
let lim = sprm.operand[1] as usize;
for itc in first..lim.min(self.cells.len()) {
self.cells[itc].vert_align = sprm.operand[2];
}
}
}
0x7627 | 0x7628 => {
if sprm.operand.len() >= 4 {
let first = sprm.operand[0] as usize;
let lim = sprm.operand[1] as usize;
let shading = parse_shd80(u16::from_le_bytes([sprm.operand[2], sprm.operand[3]]));
if let Some(shading) = shading {
for itc in first..lim.min(self.cells.len()) {
self.cells[itc].shd = Some(shading.clone());
}
}
}
}
_ => {}
}
}
fn apply_tdef_table(&mut self, operand: &[u8]) {
let Some(&itc_mac) = operand.first() else {
return;
};
let count = itc_mac as usize;
let centers_end = 1 + (count + 1) * 2;
if operand.len() < centers_end || count == 0 {
return;
}
let mut centers = Vec::with_capacity(count + 1);
for index in 0..=count {
let offset = 1 + index * 2;
centers.push(i16::from_le_bytes([operand[offset], operand[offset + 1]]) as i32);
}
self.cells = (0..count)
.map(|index| {
let mut cell = TableCell {
boundary_left: centers[index],
boundary_right: centers[index + 1],
..TableCell::default()
};
let tc_offset = centers_end + index * 20;
if let Some(tc) = operand.get(tc_offset..tc_offset + 20) {
let rgf = u16::from_le_bytes([tc[0], tc[1]]);
cell.f_first_merged = rgf & 0x0001 != 0;
cell.f_merged = rgf & 0x0002 != 0;
cell.f_vertical = rgf & 0x0004 != 0;
cell.f_backward = rgf & 0x0008 != 0;
cell.f_vert_merge = rgf & 0x0020 != 0;
cell.f_vert_restart = rgf & 0x0040 != 0;
cell.vert_align = ((rgf >> 7) & 0x3) as u8;
cell.brc_top = parse_brc80(&tc[4..8]);
cell.brc_left = parse_brc80(&tc[8..12]);
cell.brc_bottom = parse_brc80(&tc[12..16]);
cell.brc_right = parse_brc80(&tc[16..20]);
}
cell
})
.collect();
}
fn apply_set_brc80(&mut self, operand: &[u8]) {
if operand.len() < 7 {
return;
}
let first = operand[0] as usize;
let lim = operand[1] as usize;
let grfbrc = operand[2];
let border = parse_brc80(&operand[3..7]);
for itc in first..lim.min(self.cells.len()) {
let cell = &mut self.cells[itc];
if grfbrc & 0x01 != 0 {
cell.brc_top = border.clone();
}
if grfbrc & 0x02 != 0 {
cell.brc_left = border.clone();
}
if grfbrc & 0x04 != 0 {
cell.brc_bottom = border.clone();
}
if grfbrc & 0x08 != 0 {
cell.brc_right = border.clone();
}
}
}
pub fn row_left_edge(&self) -> i32 {
self.cells.first().map(|cell| cell.boundary_left).unwrap_or(0)
}
fn apply_cell_padding(&mut self, operand: &[u8], row_default: bool) {
if operand.len() != 6 || operand[2] & !0x0F != 0 {
return;
}
let first = operand[0] as usize;
let limit = operand[1] as usize;
if first >= limit || limit > 64 || (row_default && (first != 0 || limit != 1)) {
return;
}
let width = u16::from_le_bytes([operand[4], operand[5]]);
let width = match operand[3] {
0 if width == 0 => None,
3 if width <= 31680 => Some(width),
_ => return,
};
let apply = |padding: &mut [Option<u16>; 4]| {
for (side, value) in padding.iter_mut().enumerate() {
if operand[2] & (1 << side) != 0 {
*value = width;
}
}
};
if row_default {
apply(&mut self.cell_padding_default);
} else {
self.cell_padding
.resize(self.cell_padding.len().max(limit), [None; 4]);
for padding in &mut self.cell_padding[first..limit] {
apply(padding);
}
}
}
pub fn default_cell_padding_twips(&self) -> [u16; 4] {
let gap = self.dxa_gap_half.max(0) as u16;
let fallback = [0, gap, 0, gap];
std::array::from_fn(|side| self.cell_padding_default[side].unwrap_or(fallback[side]))
}
pub fn cell_padding_twips(&self, cell_index: usize) -> [u16; 4] {
let defaults = self.default_cell_padding_twips();
std::array::from_fn(|side| {
self.cell_padding
.get(cell_index)
.and_then(|padding| padding[side])
.unwrap_or(defaults[side])
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tdef_operand(centers: &[i16], tc_flags: &[u16]) -> Vec<u8> {
let count = centers.len() - 1;
let mut operand = vec![count as u8];
for center in centers {
operand.extend_from_slice(¢er.to_le_bytes());
}
for index in 0..count {
let mut tc = [0u8; 20];
tc[..2].copy_from_slice(&tc_flags.get(index).copied().unwrap_or(0).to_le_bytes());
operand.extend_from_slice(&tc);
}
operand
}
#[test]
fn parses_tdef_table_boundaries() {
let operand = tdef_operand(&[0, 2400, 4800, 9360], &[0, 0, 0]);
let mut grpprl: Vec<u8> = vec![0x08, 0xD6];
grpprl.extend_from_slice(&((operand.len() + 1) as u16).to_le_bytes());
grpprl.extend_from_slice(&operand);
let tap = Tap::from_grpprl(&grpprl);
assert_eq!(tap.cells.len(), 3);
assert_eq!(tap.cells[0].boundary_left, 0);
assert_eq!(tap.cells[0].boundary_right, 2400);
assert_eq!(tap.cells[2].boundary_right, 9360);
}
#[test]
fn parses_vert_merge_flags() {
let operand = tdef_operand(&[0, 1000, 2000], &[0x0040, 0x0020]);
let mut grpprl: Vec<u8> = vec![0x08, 0xD6];
grpprl.extend_from_slice(&((operand.len() + 1) as u16).to_le_bytes());
grpprl.extend_from_slice(&operand);
let tap = Tap::from_grpprl(&grpprl);
assert!(tap.cells[0].f_vert_restart);
assert!(tap.cells[1].f_vert_merge);
assert!(!tap.cells[1].f_vert_restart);
}
}