use crate::error::{HwpError, Result};
use crate::parser::record::Record;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TextBoxAlignment {
Inline = 0,
Left = 1,
Center = 2,
Right = 3,
Absolute = 4,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TextBoxBorderStyle {
None = 0,
Solid = 1,
Dotted = 2,
Dashed = 3,
Double = 4,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TextBoxFillType {
None = 0,
Solid = 1,
Gradient = 2,
Image = 3,
}
#[derive(Debug, Clone)]
pub struct TextBox {
pub text: String,
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub alignment: TextBoxAlignment,
pub border_style: TextBoxBorderStyle,
pub border_width: u8,
pub border_color: u32,
pub fill_type: TextBoxFillType,
pub background_color: u32,
pub padding: u16,
pub char_shape_id: u16,
pub para_shape_id: u16,
pub z_order: u16,
pub opacity: u8,
pub rotation: i16,
}
impl Default for TextBox {
fn default() -> Self {
Self {
text: String::new(),
x: 0,
y: 0,
width: 5000, height: 2000, alignment: TextBoxAlignment::Inline,
border_style: TextBoxBorderStyle::Solid,
border_width: 1,
border_color: 0x000000, fill_type: TextBoxFillType::None,
background_color: 0xFFFFFF, padding: 100, char_shape_id: 0,
para_shape_id: 0,
z_order: 0,
opacity: 255, rotation: 0,
}
}
}
impl TextBox {
pub fn new(text: &str) -> Self {
Self {
text: text.to_string(),
..Default::default()
}
}
pub fn with_position_mm(mut self, x_mm: i32, y_mm: i32) -> Self {
self.x = x_mm * 100; self.y = y_mm * 100;
self
}
pub fn with_size_mm(mut self, width_mm: u32, height_mm: u32) -> Self {
self.width = width_mm * 100; self.height = height_mm * 100;
self
}
pub fn with_alignment(mut self, alignment: TextBoxAlignment) -> Self {
self.alignment = alignment;
self
}
pub fn with_border(mut self, style: TextBoxBorderStyle, width: u8, color: u32) -> Self {
self.border_style = style;
self.border_width = width;
self.border_color = color;
self
}
pub fn with_background(mut self, color: u32) -> Self {
self.fill_type = TextBoxFillType::Solid;
self.background_color = color;
self
}
pub fn with_transparent_background(mut self) -> Self {
self.fill_type = TextBoxFillType::None;
self
}
pub fn with_padding_mm(mut self, padding_mm: u16) -> Self {
self.padding = padding_mm * 100; self
}
pub fn with_opacity(mut self, opacity: u8) -> Self {
self.opacity = opacity;
self
}
pub fn with_rotation(mut self, degrees: i16) -> Self {
self.rotation = degrees;
self
}
pub fn with_z_order(mut self, z_order: u16) -> Self {
self.z_order = z_order;
self
}
pub fn to_bytes(&self) -> Vec<u8> {
use crate::utils::encoding::string_to_utf16le;
use byteorder::{LittleEndian, WriteBytesExt};
use std::io::{Cursor, Write};
let mut data = Vec::new();
let mut writer = Cursor::new(&mut data);
writer.write_i32::<LittleEndian>(self.x).unwrap();
writer.write_i32::<LittleEndian>(self.y).unwrap();
writer.write_u32::<LittleEndian>(self.width).unwrap();
writer.write_u32::<LittleEndian>(self.height).unwrap();
writer.write_u8(self.alignment as u8).unwrap();
writer.write_u8(self.border_style as u8).unwrap();
writer.write_u8(self.border_width).unwrap();
writer.write_u32::<LittleEndian>(self.border_color).unwrap();
writer.write_u8(self.fill_type as u8).unwrap();
writer
.write_u32::<LittleEndian>(self.background_color)
.unwrap();
writer.write_u16::<LittleEndian>(self.padding).unwrap();
writer
.write_u16::<LittleEndian>(self.char_shape_id)
.unwrap();
writer
.write_u16::<LittleEndian>(self.para_shape_id)
.unwrap();
writer.write_u16::<LittleEndian>(self.z_order).unwrap();
writer.write_u8(self.opacity).unwrap();
writer.write_i16::<LittleEndian>(self.rotation).unwrap();
let text_utf16 = string_to_utf16le(&self.text);
writer
.write_u16::<LittleEndian>(text_utf16.len() as u16 / 2)
.unwrap();
writer.write_all(&text_utf16).unwrap();
data
}
pub fn from_record(record: &Record) -> Result<Self> {
let mut reader = record.data_reader();
let x = reader.read_i32()?;
let y = reader.read_i32()?;
let width = reader.read_u32()?;
let height = reader.read_u32()?;
let alignment = match reader.read_u8()? {
0 => TextBoxAlignment::Inline,
1 => TextBoxAlignment::Left,
2 => TextBoxAlignment::Center,
3 => TextBoxAlignment::Right,
4 => TextBoxAlignment::Absolute,
_ => TextBoxAlignment::Inline,
};
let border_style = match reader.read_u8()? {
0 => TextBoxBorderStyle::None,
1 => TextBoxBorderStyle::Solid,
2 => TextBoxBorderStyle::Dotted,
3 => TextBoxBorderStyle::Dashed,
4 => TextBoxBorderStyle::Double,
_ => TextBoxBorderStyle::Solid,
};
let border_width = reader.read_u8()?;
let border_color = reader.read_u32()?;
let fill_type = match reader.read_u8()? {
0 => TextBoxFillType::None,
1 => TextBoxFillType::Solid,
2 => TextBoxFillType::Gradient,
3 => TextBoxFillType::Image,
_ => TextBoxFillType::None,
};
let background_color = reader.read_u32()?;
let padding = reader.read_u16()?;
let char_shape_id = reader.read_u16()?;
let para_shape_id = reader.read_u16()?;
let z_order = reader.read_u16()?;
let opacity = reader.read_u8()?;
let rotation = reader.read_u16()? as i16;
let text_len = reader.read_u16()? as usize;
let text_bytes = reader.read_bytes(text_len * 2)?;
let mut utf16_chars = Vec::new();
for chunk in text_bytes.chunks_exact(2) {
let char_value = u16::from_le_bytes([chunk[0], chunk[1]]);
utf16_chars.push(char_value);
}
let text = String::from_utf16(&utf16_chars)
.map_err(|_| HwpError::InvalidFormat("Invalid UTF-16 text".to_string()))?;
Ok(Self {
text,
x,
y,
width,
height,
alignment,
border_style,
border_width,
border_color,
fill_type,
background_color,
padding,
char_shape_id,
para_shape_id,
z_order,
opacity,
rotation,
})
}
}
impl TextBox {
pub fn basic(text: &str) -> Self {
Self::new(text)
.with_border(TextBoxBorderStyle::Solid, 1, 0x000000)
.with_background(0xFFFFFF)
}
pub fn highlight(text: &str) -> Self {
Self::new(text)
.with_border(TextBoxBorderStyle::Solid, 2, 0x000000)
.with_background(0xFFFF00) }
pub fn warning(text: &str) -> Self {
Self::new(text)
.with_border(TextBoxBorderStyle::Solid, 2, 0xFF0000) .with_background(0xFFE0E0) }
pub fn info(text: &str) -> Self {
Self::new(text)
.with_border(TextBoxBorderStyle::Solid, 1, 0x0000FF) .with_background(0xE0E0FF) }
pub fn transparent(text: &str) -> Self {
Self::new(text)
.with_border(TextBoxBorderStyle::Dashed, 1, 0x808080) .with_transparent_background()
}
pub fn bubble(text: &str) -> Self {
Self::new(text)
.with_border(TextBoxBorderStyle::Solid, 2, 0x000000)
.with_background(0xF0F0F0) .with_padding_mm(5)
}
}