use super::container::EscherContainer;
use super::types::EscherRecordType;
use super::properties::{EscherProperties, ShapeAnchor};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EscherShapeType {
Rectangle,
Ellipse,
TextBox,
Line,
Polygon,
Group,
Picture,
AutoShape,
Connector,
Unknown,
}
#[derive(Debug, Clone)]
pub struct EscherShape<'data> {
container: EscherContainer<'data>,
shape_type: EscherShapeType,
shape_id: Option<u32>,
properties: EscherProperties,
anchor: Option<ShapeAnchor>,
}
impl<'data> EscherShape<'data> {
pub fn from_container(container: EscherContainer<'data>) -> Self {
let shape_type = Self::detect_shape_type(&container);
let shape_id = Self::extract_shape_id(&container);
let properties = EscherProperties::from_container(&container);
let anchor = Self::extract_anchor(&container);
Self {
container,
shape_type,
shape_id,
properties,
anchor,
}
}
#[inline]
pub fn shape_type(&self) -> EscherShapeType {
self.shape_type
}
#[inline]
pub fn shape_id(&self) -> Option<u32> {
self.shape_id
}
#[inline]
pub fn properties(&self) -> &EscherProperties {
&self.properties
}
#[inline]
pub fn anchor(&self) -> Option<&ShapeAnchor> {
self.anchor.as_ref()
}
pub fn can_contain_text(&self) -> bool {
matches!(
self.shape_type,
EscherShapeType::TextBox
| EscherShapeType::Rectangle
| EscherShapeType::AutoShape
)
}
pub fn text(&self) -> Option<String> {
if let Some(textbox) = self.container.find_child(EscherRecordType::ClientTextbox) {
super::text::extract_text_from_textbox(&textbox)
} else {
None
}
}
#[inline]
pub fn container(&self) -> &EscherContainer<'data> {
&self.container
}
fn detect_shape_type(container: &EscherContainer<'data>) -> EscherShapeType {
if let Some(sp) = container.find_child(EscherRecordType::Sp) {
let shape_type_id = sp.instance;
return match shape_type_id {
75 if Self::has_picture_data(container) => EscherShapeType::Picture,
75 | 202 => EscherShapeType::TextBox,
1 => EscherShapeType::Rectangle,
3 => EscherShapeType::Ellipse,
20 => EscherShapeType::Line,
0 => EscherShapeType::Group,
_ if shape_type_id < 203 => EscherShapeType::AutoShape,
_ => EscherShapeType::Unknown,
};
}
if container.find_child(EscherRecordType::ClientTextbox).is_some() {
return EscherShapeType::TextBox;
}
for child_result in container.children() {
if let Ok(child) = child_result {
if child.record_type == EscherRecordType::SpgrContainer {
return EscherShapeType::Group;
}
}
}
EscherShapeType::Unknown
}
fn has_picture_data(container: &EscherContainer<'data>) -> bool {
for child_result in container.children() {
if let Ok(child) = child_result {
match child.record_type {
EscherRecordType::BlipJpeg
| EscherRecordType::BlipPng
| EscherRecordType::BlipDib
| EscherRecordType::BlipTiff
| EscherRecordType::BlipEmf
| EscherRecordType::BlipWmf
| EscherRecordType::BlipPict => {
return true;
}
_ => {}
}
}
}
false
}
fn extract_shape_id(container: &EscherContainer<'data>) -> Option<u32> {
if let Some(sp) = container.find_child(EscherRecordType::Sp) {
if sp.data.len() >= 4 {
let id = u32::from_le_bytes([
sp.data[0],
sp.data[1],
sp.data[2],
sp.data[3],
]);
return Some(id);
}
}
None
}
fn extract_anchor(container: &EscherContainer<'data>) -> Option<ShapeAnchor> {
if let Some(child_anchor) = container.find_child(EscherRecordType::ChildAnchor) {
if let Some(anchor) = ShapeAnchor::from_child_anchor(&child_anchor) {
return Some(anchor);
}
}
if let Some(client_anchor) = container.find_child(EscherRecordType::ClientAnchor) {
if let Some(anchor) = ShapeAnchor::from_client_anchor(&client_anchor) {
return Some(anchor);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shape_type_detection() {
let shape_type = EscherShapeType::TextBox;
assert_eq!(shape_type, EscherShapeType::TextBox);
}
}