use super::record::EscherRecord;
use super::types::EscherRecordType;
use super::container::EscherContainer;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
pub enum EscherPropertyId {
Rotation = 0x0004,
Left = 0x0082,
Top = 0x0083,
Right = 0x0084,
Bottom = 0x0085,
FillColor = 0x0181,
FillType = 0x0182,
FillOpacity = 0x0183,
LineColor = 0x01C0,
LineWidth = 0x01CB,
LineStyle = 0x01C9,
TextId = 0x0080,
TextLeftMargin = 0x0065,
TextTopMargin = 0x0066,
TextRightMargin = 0x0067,
TextBottomMargin = 0x0068,
PictureId = 0x0104,
PictureName = 0x0105,
LockRotation = 0x0077,
LockAspectRatio = 0x0078,
Unknown = 0xFFFF,
}
impl From<u16> for EscherPropertyId {
fn from(value: u16) -> Self {
match value {
0x0004 => Self::Rotation,
0x0082 => Self::Left,
0x0083 => Self::Top,
0x0084 => Self::Right,
0x0085 => Self::Bottom,
0x0181 => Self::FillColor,
0x0182 => Self::FillType,
0x0183 => Self::FillOpacity,
0x01C0 => Self::LineColor,
0x01C9 => Self::LineStyle,
0x01CB => Self::LineWidth,
0x0080 => Self::TextId,
0x0065 => Self::TextLeftMargin,
0x0066 => Self::TextTopMargin,
0x0067 => Self::TextRightMargin,
0x0068 => Self::TextBottomMargin,
0x0104 => Self::PictureId,
0x0105 => Self::PictureName,
0x0077 => Self::LockRotation,
0x0078 => Self::LockAspectRatio,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone)]
pub enum EscherPropertyValue {
Integer(i32),
Boolean(bool),
Color(u32),
Binary(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct EscherProperties {
properties: HashMap<EscherPropertyId, EscherPropertyValue>,
}
impl EscherProperties {
#[inline]
pub fn new() -> Self {
Self {
properties: HashMap::new(),
}
}
pub fn from_opt_record(opt: &EscherRecord) -> Self {
let mut properties = HashMap::with_capacity(opt.instance as usize);
if opt.data.len() < 6 {
return Self { properties };
}
let mut offset = 0;
while offset + 6 <= opt.data.len() {
let prop_id_raw = u16::from_le_bytes([opt.data[offset], opt.data[offset + 1]]);
let prop_id = EscherPropertyId::from(prop_id_raw & 0x3FFF); let is_complex = (prop_id_raw & 0x8000) != 0;
let value_bytes = [
opt.data[offset + 2],
opt.data[offset + 3],
opt.data[offset + 4],
opt.data[offset + 5],
];
let value = i32::from_le_bytes(value_bytes);
let prop_value = if is_complex {
EscherPropertyValue::Binary(Vec::new()) } else {
EscherPropertyValue::Integer(value)
};
properties.insert(prop_id, prop_value);
offset += 6;
}
Self { properties }
}
pub fn from_container(container: &EscherContainer) -> Self {
if let Some(opt) = container.find_child(EscherRecordType::Opt) {
Self::from_opt_record(&opt)
} else {
Self::new()
}
}
#[inline]
pub fn get_int(&self, id: EscherPropertyId) -> Option<i32> {
match self.properties.get(&id) {
Some(EscherPropertyValue::Integer(v)) => Some(*v),
_ => None,
}
}
#[inline]
pub fn get_color(&self, id: EscherPropertyId) -> Option<u32> {
self.get_int(id).map(|v| v as u32)
}
#[inline]
pub fn get_bool(&self, id: EscherPropertyId) -> Option<bool> {
match self.properties.get(&id) {
Some(EscherPropertyValue::Boolean(v)) => Some(*v),
Some(EscherPropertyValue::Integer(v)) => Some(*v != 0),
_ => None,
}
}
#[inline]
pub fn get_binary(&self, id: EscherPropertyId) -> Option<&[u8]> {
match self.properties.get(&id) {
Some(EscherPropertyValue::Binary(v)) => Some(v),
_ => None,
}
}
#[inline]
pub fn has(&self, id: EscherPropertyId) -> bool {
self.properties.contains_key(&id)
}
}
impl Default for EscherProperties {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ShapeAnchor {
pub left: i32,
pub top: i32,
pub right: i32,
pub bottom: i32,
}
impl ShapeAnchor {
#[inline]
pub const fn new(left: i32, top: i32, right: i32, bottom: i32) -> Self {
Self { left, top, right, bottom }
}
#[inline]
pub const fn width(&self) -> i32 {
self.right - self.left
}
#[inline]
pub const fn height(&self) -> i32 {
self.bottom - self.top
}
pub fn from_child_anchor(anchor: &EscherRecord) -> Option<Self> {
if anchor.data.len() < 16 {
return None;
}
let left = i32::from_le_bytes([
anchor.data[0], anchor.data[1], anchor.data[2], anchor.data[3]
]);
let top = i32::from_le_bytes([
anchor.data[4], anchor.data[5], anchor.data[6], anchor.data[7]
]);
let right = i32::from_le_bytes([
anchor.data[8], anchor.data[9], anchor.data[10], anchor.data[11]
]);
let bottom = i32::from_le_bytes([
anchor.data[12], anchor.data[13], anchor.data[14], anchor.data[15]
]);
Some(Self::new(left, top, right, bottom))
}
pub fn from_client_anchor(anchor: &EscherRecord) -> Option<Self> {
Self::from_child_anchor(anchor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_anchor_dimensions() {
let anchor = ShapeAnchor::new(100, 200, 500, 600);
assert_eq!(anchor.width(), 400);
assert_eq!(anchor.height(), 400);
}
}