use super::super::package::Result;
#[derive(Debug, Clone)]
pub struct TableProperties {
pub cell_count: usize,
pub cell_boundaries: Vec<i16>,
pub cell_properties: Vec<CellProperties>,
pub justification: TableJustification,
pub indent_left: i16,
pub preferred_width: Option<TableWidth>,
pub row_height: Option<i16>,
pub is_header_row: bool,
pub allow_row_break: bool,
}
#[derive(Debug, Clone, Default)]
pub struct CellProperties {
pub merge_status: CellMergeStatus,
pub vertical_alignment: VerticalAlignment,
pub background_color: Option<(u8, u8, u8)>,
pub borders: CellBorders,
pub text_direction: TextDirection,
pub preferred_width: Option<TableWidth>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CellMergeStatus {
#[default]
None,
First,
Merged,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VerticalAlignment {
#[default]
Top,
Center,
Bottom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TableJustification {
#[default]
Left,
Center,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextDirection {
#[default]
LrTb,
TbRl,
BtLr,
LrBt,
TbLr,
}
#[derive(Debug, Clone, Copy)]
pub struct TableWidth {
pub value: i16,
pub width_type: WidthType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WidthType {
Twips,
Percentage,
Auto,
}
#[derive(Debug, Clone, Default)]
pub struct CellBorders {
pub top: Option<BorderStyle>,
pub left: Option<BorderStyle>,
pub bottom: Option<BorderStyle>,
pub right: Option<BorderStyle>,
}
#[derive(Debug, Clone, Copy)]
pub struct BorderStyle {
pub width: u8,
pub color: Option<(u8, u8, u8)>,
pub border_type: BorderType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BorderType {
None,
Single,
Thick,
Double,
Dotted,
Dashed,
DotDash,
DotDotDash,
Triple,
ThinThickSmall,
ThickThinSmall,
ThinThickThinSmall,
}
impl Default for TableProperties {
fn default() -> Self {
Self {
cell_count: 0,
cell_boundaries: Vec::new(),
cell_properties: Vec::new(),
justification: TableJustification::Left,
indent_left: 0,
preferred_width: None,
row_height: None,
is_header_row: false,
allow_row_break: true,
}
}
}
impl TableProperties {
pub fn new() -> Self {
Self::default()
}
pub fn from_sprm(grpprl: &[u8]) -> Result<Self> {
let mut tap = Self::default();
let mut offset = 0;
while offset < grpprl.len() {
if offset + 1 > grpprl.len() {
break;
}
let sprm = u16::from_le_bytes([grpprl[offset], grpprl[offset + 1]]);
offset += 2;
match sprm {
0x5400 => {
if offset < grpprl.len() {
tap.justification = match grpprl[offset] {
0 => TableJustification::Left,
1 => TableJustification::Center,
2 => TableJustification::Right,
_ => TableJustification::Left,
};
offset += 1;
}
}
0xD608 => {
if offset + 1 < grpprl.len() {
let size = u16::from_le_bytes([grpprl[offset], grpprl[offset + 1]]) as usize;
offset += 2;
if offset + size <= grpprl.len() {
let def_data = &grpprl[offset..offset + size];
tap.parse_table_definition(def_data)?;
offset += size;
}
}
}
0x9407 => {
if offset + 1 < grpprl.len() {
tap.row_height = Some(i16::from_le_bytes([grpprl[offset], grpprl[offset + 1]]));
offset += 2;
}
}
0x3403 => {
if offset < grpprl.len() {
tap.is_header_row = grpprl[offset] != 0;
offset += 1;
}
}
0x3404 => {
if offset < grpprl.len() {
tap.allow_row_break = grpprl[offset] == 0;
offset += 1;
}
}
0x9601 => {
if offset + 1 < grpprl.len() {
tap.indent_left = i16::from_le_bytes([grpprl[offset], grpprl[offset + 1]]);
offset += 2;
}
}
0xD605..=0xD620 => {
let size = Self::get_sprm_size(sprm);
if offset + size <= grpprl.len() {
offset += size;
}
}
_ => {
let size = Self::get_sprm_size(sprm);
offset += size;
}
}
}
Ok(tap)
}
fn parse_table_definition(&mut self, data: &[u8]) -> Result<()> {
if data.is_empty() {
return Ok(());
}
self.cell_count = data[0] as usize;
let mut offset = 1;
self.cell_boundaries.clear();
for _ in 0..=self.cell_count {
if offset + 1 < data.len() {
let boundary = i16::from_le_bytes([data[offset], data[offset + 1]]);
self.cell_boundaries.push(boundary);
offset += 2;
}
}
self.cell_properties = vec![CellProperties::default(); self.cell_count];
Ok(())
}
fn get_sprm_size(sprm: u16) -> usize {
let sprm_type = sprm & 0x07;
match sprm_type {
0 | 1 => 1,
2 | 4 | 5 => 2,
3 => 4,
6 => 1, 7 => 3,
_ => 1,
}
}
pub fn get_cell_width(&self, cell_index: usize) -> Option<i16> {
if cell_index < self.cell_boundaries.len().saturating_sub(1) {
Some(self.cell_boundaries[cell_index + 1] - self.cell_boundaries[cell_index])
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_tap() {
let tap = TableProperties::new();
assert_eq!(tap.cell_count, 0);
assert_eq!(tap.justification, TableJustification::Left);
assert!(tap.allow_row_break);
}
#[test]
fn test_cell_merge_status() {
let none = CellMergeStatus::None;
let first = CellMergeStatus::First;
assert_ne!(none, first);
}
#[test]
fn test_vertical_alignment() {
let top = VerticalAlignment::Top;
let center = VerticalAlignment::Center;
assert_ne!(top, center);
}
#[test]
fn test_table_definition() {
let mut tap = TableProperties::new();
let data = vec![
2, 0, 0, 100, 0, 200, 0, ];
tap.parse_table_definition(&data).unwrap();
assert_eq!(tap.cell_count, 2);
assert_eq!(tap.cell_boundaries.len(), 3);
assert_eq!(tap.get_cell_width(0), Some(100));
assert_eq!(tap.get_cell_width(1), Some(100));
}
}