use super::super::package::Result;
use crate::ole::binary::{read_i16_le, read_u16_le};
#[derive(Debug, Clone, Default)]
pub struct ParagraphProperties {
pub justification: Justification,
pub indent_left: Option<i32>,
pub indent_right: Option<i32>,
pub indent_first_line: Option<i32>,
pub space_before: Option<i32>,
pub space_after: Option<i32>,
pub line_spacing: Option<i32>,
pub line_spacing_type: LineSpacingType,
pub keep_on_page: bool,
pub keep_with_next: bool,
pub page_break_before: bool,
pub widow_control: bool,
pub tab_stops: Vec<TabStop>,
pub borders: Borders,
pub shading: Option<Shading>,
pub in_table: bool,
pub is_table_row_end: bool,
pub table_level: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Justification {
#[default]
Left,
Center,
Right,
Justified,
Distributed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LineSpacingType {
#[default]
Single,
OnePointFive,
Double,
AtLeast,
Exactly,
Multiple,
}
#[derive(Debug, Clone, Copy)]
pub struct TabStop {
pub position: i32,
pub alignment: TabAlignment,
pub leader: TabLeader,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TabAlignment {
Left,
Center,
Right,
Decimal,
Bar,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TabLeader {
None,
Dots,
Hyphens,
Underline,
Heavy,
MiddleDot,
}
#[derive(Debug, Clone, Default)]
pub struct Borders {
pub top: Option<Border>,
pub left: Option<Border>,
pub bottom: Option<Border>,
pub right: Option<Border>,
pub between: Option<Border>,
}
#[derive(Debug, Clone, Copy)]
pub struct Border {
pub style: BorderStyle,
pub width: u8,
pub color: (u8, u8, u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BorderStyle {
None,
Single,
Thick,
Double,
Dotted,
Dashed,
DotDash,
DotDotDash,
Triple,
ThinThickSmallGap,
ThickThinSmallGap,
ThinThickThinSmallGap,
}
#[derive(Debug, Clone, Copy)]
pub struct Shading {
pub background_color: (u8, u8, u8),
pub foreground_color: (u8, u8, u8),
pub pattern: ShadingPattern,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShadingPattern {
Clear,
Solid,
Percent5,
Percent10,
Percent20,
Percent25,
Percent30,
Percent40,
Percent50,
Percent60,
Percent70,
Percent75,
Percent80,
Percent90,
DarkHorizontal,
DarkVertical,
DarkForwardDiagonal,
DarkBackwardDiagonal,
DarkCross,
DarkDiagonalCross,
}
impl ParagraphProperties {
pub fn new() -> Self {
Self::default()
}
pub fn from_sprm(grpprl: &[u8]) -> Result<Self> {
let mut pap = Self::default();
let mut offset = 0;
while offset < grpprl.len() {
if offset + 1 > grpprl.len() {
break;
}
let sprm = read_u16_le(grpprl, offset).unwrap_or(0);
offset += 2;
match sprm {
0x2403 | 0x0003 => {
if offset < grpprl.len() {
pap.justification = match grpprl[offset] {
0 => Justification::Left,
1 => Justification::Center,
2 => Justification::Right,
3 => Justification::Justified,
4 => Justification::Distributed,
_ => Justification::Left,
};
offset += 1;
}
}
0x840F | 0x000F => {
if offset + 1 < grpprl.len() {
pap.indent_left = Some(read_i16_le(grpprl, offset).unwrap_or(0) as i32);
offset += 2;
}
}
0x8411 | 0x0011 => {
if offset + 1 < grpprl.len() {
pap.indent_right = Some(read_i16_le(grpprl, offset).unwrap_or(0) as i32);
offset += 2;
}
}
0x8416 | 0x0016 => {
if offset + 1 < grpprl.len() {
pap.indent_first_line = Some(read_i16_le(grpprl, offset).unwrap_or(0) as i32);
offset += 2;
}
}
0xA413 | 0x0013 => {
if offset + 1 < grpprl.len() {
pap.space_before = Some(read_u16_le(grpprl, offset).unwrap_or(0) as i32);
offset += 2;
}
}
0xA414 | 0x0014 => {
if offset + 1 < grpprl.len() {
pap.space_after = Some(read_u16_le(grpprl, offset).unwrap_or(0) as i32);
offset += 2;
}
}
0x6412 | 0x0012 => {
if offset + 3 < grpprl.len() {
pap.line_spacing = Some(read_i16_le(grpprl, offset).unwrap_or(0) as i32);
let spacing_type = read_u16_le(grpprl, offset + 2).unwrap_or(0);
pap.line_spacing_type = match spacing_type {
0 => LineSpacingType::Single,
1 => LineSpacingType::OnePointFive,
2 => LineSpacingType::Double,
3 => LineSpacingType::AtLeast,
4 => LineSpacingType::Exactly,
5 => LineSpacingType::Multiple,
_ => LineSpacingType::Single,
};
offset += 4;
}
}
0x2405 | 0x0005 => {
if offset < grpprl.len() {
pap.keep_on_page = grpprl[offset] != 0;
offset += 1;
}
}
0x2406 | 0x0006 => {
if offset < grpprl.len() {
pap.keep_with_next = grpprl[offset] != 0;
offset += 1;
}
}
0x2407 | 0x0007 => {
if offset < grpprl.len() {
pap.page_break_before = grpprl[offset] != 0;
offset += 1;
}
}
0x240E | 0x000E => {
if offset < grpprl.len() {
pap.widow_control = grpprl[offset] != 0;
offset += 1;
}
}
0xC615 | 0x0015 => {
if offset < grpprl.len() {
let tab_count = grpprl[offset] as usize;
offset += 1;
for _ in 0..tab_count.min(64) {
if offset + 3 < grpprl.len() {
let position = read_i16_le(grpprl, offset).unwrap_or(0) as i32;
let alignment_val = grpprl[offset + 2];
let leader_val = grpprl[offset + 3];
let alignment = match alignment_val {
0 => TabAlignment::Left,
1 => TabAlignment::Center,
2 => TabAlignment::Right,
3 => TabAlignment::Decimal,
4 => TabAlignment::Bar,
_ => TabAlignment::Left,
};
let leader = match leader_val {
0 => TabLeader::None,
1 => TabLeader::Dots,
2 => TabLeader::Hyphens,
3 => TabLeader::Underline,
4 => TabLeader::Heavy,
5 => TabLeader::MiddleDot,
_ => TabLeader::None,
};
pap.tab_stops.push(TabStop {
position,
alignment,
leader,
});
offset += 4;
}
}
}
}
_ => {
let size = Self::get_sprm_size(sprm);
offset += size;
}
}
}
Ok(pap)
}
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 has_formatting(&self) -> bool {
self.justification != Justification::Left
|| self.indent_left.is_some()
|| self.indent_right.is_some()
|| self.indent_first_line.is_some()
|| self.space_before.is_some()
|| self.space_after.is_some()
|| self.line_spacing.is_some()
|| self.keep_on_page
|| self.keep_with_next
|| self.page_break_before
|| self.widow_control
|| !self.tab_stops.is_empty()
}
pub fn get_indent_left_inches(&self) -> f32 {
self.indent_left.map(|v| v as f32 / 1440.0).unwrap_or(0.0)
}
pub fn get_indent_right_inches(&self) -> f32 {
self.indent_right.map(|v| v as f32 / 1440.0).unwrap_or(0.0)
}
pub fn get_indent_first_line_inches(&self) -> f32 {
self.indent_first_line.map(|v| v as f32 / 1440.0).unwrap_or(0.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_pap() {
let pap = ParagraphProperties::new();
assert_eq!(pap.justification, Justification::Left);
assert!(!pap.keep_on_page);
assert!(!pap.has_formatting());
}
#[test]
fn test_justification() {
let left = Justification::Left;
let center = Justification::Center;
assert_ne!(left, center);
assert_eq!(left, Justification::Left);
}
#[test]
fn test_line_spacing_type() {
let single = LineSpacingType::Single;
let double = LineSpacingType::Double;
assert_ne!(single, double);
}
#[test]
fn test_indent_conversion() {
let mut pap = ParagraphProperties::new();
pap.indent_left = Some(1440); assert_eq!(pap.get_indent_left_inches(), 1.0);
}
}