use super::shape::{Shape, ShapeProperties, ShapeContainer};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlaceholderSize {
Quarter,
Half,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlaceholderType {
None,
Title,
Body,
CenterTitle,
SubTitle,
Chart,
Table,
ClipArt,
Diagram,
MediaClip,
Object,
Content,
Picture,
SlideImage,
VerticalTextTitle,
VerticalTextBody,
NotesSlideImage,
NotesSlideText,
Header,
Footer,
SlideNumber,
DateAndTime,
VerticalObject,
Copyright,
Custom(u16),
}
impl From<u16> for PlaceholderType {
fn from(value: u16) -> Self {
match value {
0 => PlaceholderType::None,
1 => PlaceholderType::Title,
2 => PlaceholderType::Body,
3 => PlaceholderType::CenterTitle,
4 => PlaceholderType::SubTitle,
5 => PlaceholderType::Chart,
6 => PlaceholderType::Table,
7 => PlaceholderType::ClipArt,
8 => PlaceholderType::Diagram,
9 => PlaceholderType::MediaClip,
10 => PlaceholderType::Object,
11 => PlaceholderType::SlideImage,
19 => PlaceholderType::Content,
26 => PlaceholderType::Picture,
12 => PlaceholderType::VerticalTextTitle,
13 => PlaceholderType::VerticalTextBody,
14 => PlaceholderType::NotesSlideImage,
15 => PlaceholderType::NotesSlideText,
16 => PlaceholderType::Header,
17 => PlaceholderType::Footer,
18 => PlaceholderType::SlideNumber,
20 => PlaceholderType::VerticalObject,
21 => PlaceholderType::Copyright,
other => PlaceholderType::Custom(other),
}
}
}
impl std::fmt::Display for PlaceholderType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PlaceholderType::None => write!(f, "None"),
PlaceholderType::Title => write!(f, "Title"),
PlaceholderType::Body => write!(f, "Body"),
PlaceholderType::CenterTitle => write!(f, "CenterTitle"),
PlaceholderType::SubTitle => write!(f, "SubTitle"),
PlaceholderType::Chart => write!(f, "Chart"),
PlaceholderType::Table => write!(f, "Table"),
PlaceholderType::ClipArt => write!(f, "ClipArt"),
PlaceholderType::Diagram => write!(f, "Diagram"),
PlaceholderType::MediaClip => write!(f, "MediaClip"),
PlaceholderType::Object => write!(f, "Object"),
PlaceholderType::Content => write!(f, "Content"),
PlaceholderType::Picture => write!(f, "Picture"),
PlaceholderType::SlideImage => write!(f, "SlideImage"),
PlaceholderType::VerticalTextTitle => write!(f, "VerticalTextTitle"),
PlaceholderType::VerticalTextBody => write!(f, "VerticalTextBody"),
PlaceholderType::NotesSlideImage => write!(f, "NotesSlideImage"),
PlaceholderType::NotesSlideText => write!(f, "NotesSlideText"),
PlaceholderType::Header => write!(f, "Header"),
PlaceholderType::Footer => write!(f, "Footer"),
PlaceholderType::SlideNumber => write!(f, "SlideNumber"),
PlaceholderType::DateAndTime => write!(f, "DateAndTime"),
PlaceholderType::VerticalObject => write!(f, "VerticalObject"),
PlaceholderType::Copyright => write!(f, "Copyright"),
PlaceholderType::Custom(id) => write!(f, "Custom({})", id),
}
}
}
#[derive(Debug, Clone)]
pub struct Placeholder {
container: ShapeContainer,
placeholder_type: PlaceholderType,
size: Option<u8>,
index: Option<u16>,
raw_placeholder_data: Option<Vec<u8>>,
}
impl Placeholder {
pub fn new(properties: ShapeProperties, raw_data: Vec<u8>) -> Self {
Self {
container: ShapeContainer::new(properties, raw_data),
placeholder_type: PlaceholderType::Body, size: None,
index: None,
raw_placeholder_data: None,
}
}
pub fn from_escher_record(record: &super::escher::EscherRecord) -> super::super::package::Result<Self> {
let properties = record.extract_shape_properties()?;
let placeholder_info = Self::extract_placeholder_info_from_record(record)?;
let (placeholder_type, size, index) = if let Some((poi_id, size_val, index_val)) = placeholder_info {
match Self::map_poi_placeholder_id_to_type(poi_id) {
Ok(placeholder_type) => (placeholder_type, Some(size_val), Some(index_val)),
Err(_) => (PlaceholderType::Body, None, None), }
} else {
(PlaceholderType::Body, None, None)
};
let container = ShapeContainer::new(properties, record.data.clone());
Ok(Self {
container,
placeholder_type,
size,
index,
raw_placeholder_data: Some(record.data.clone()),
})
}
pub fn from_container(container: ShapeContainer) -> Self {
let (placeholder_type, size, index) = if !container.raw_data.is_empty() {
if let Ok((escher_record, _)) = super::escher::EscherRecord::parse(&container.raw_data, 0) {
if let Ok(Some((poi_id, size_val, index_val))) = escher_record.extract_placeholder_info() {
let ph_type = Self::map_poi_placeholder_id_to_type(poi_id).unwrap_or(PlaceholderType::Body);
(ph_type, Some(size_val), Some(index_val))
} else {
(PlaceholderType::Body, None, None)
}
} else {
(PlaceholderType::Body, None, None)
}
} else {
(PlaceholderType::Body, None, None)
};
Self {
container,
placeholder_type,
size,
index,
raw_placeholder_data: None,
}
}
fn extract_placeholder_info_from_record(record: &super::escher::EscherRecord) -> super::super::package::Result<Option<(u16, u8, u16)>> {
record.extract_placeholder_info()
}
fn map_poi_placeholder_id_to_type(poi_id: u16) -> super::super::package::Result<PlaceholderType> {
let placeholder_type = match poi_id {
0 => PlaceholderType::None,
13 => PlaceholderType::Title, 14 => PlaceholderType::Body, 15 => PlaceholderType::CenterTitle, 16 => PlaceholderType::SubTitle, 7 => PlaceholderType::DateAndTime, 8 => PlaceholderType::SlideNumber, 9 => PlaceholderType::Footer, 10 => PlaceholderType::Header, 19 => PlaceholderType::Content, 20 => PlaceholderType::Chart, 21 => PlaceholderType::Table, 22 => PlaceholderType::ClipArt, 23 => PlaceholderType::Diagram, 24 => PlaceholderType::MediaClip, 11 => PlaceholderType::SlideImage, 26 => PlaceholderType::Picture, 25 => PlaceholderType::VerticalObject, 17 => PlaceholderType::VerticalTextTitle, 18 => PlaceholderType::VerticalTextBody, _ => {
return Err(super::super::package::PptError::Corrupted(
format!("Unknown placeholder type ID: {}", poi_id)
));
}
};
Ok(placeholder_type)
}
pub fn raw_placeholder_data(&self) -> Option<&[u8]> {
self.raw_placeholder_data.as_deref()
}
pub fn placeholder_type(&self) -> PlaceholderType {
self.placeholder_type
}
pub fn set_placeholder_type(&mut self, placeholder_type: PlaceholderType) {
self.placeholder_type = placeholder_type;
}
pub fn size(&self) -> Option<u8> {
self.size
}
pub fn set_size(&mut self, size: u8) {
self.size = Some(size);
}
pub fn index(&self) -> Option<u16> {
self.index
}
pub fn set_index(&mut self, index: u16) {
self.index = Some(index);
}
pub fn is_title(&self) -> bool {
matches!(
self.placeholder_type,
PlaceholderType::Title | PlaceholderType::CenterTitle | PlaceholderType::SubTitle
)
}
pub fn is_content(&self) -> bool {
matches!(self.placeholder_type, PlaceholderType::Body)
}
pub fn is_media(&self) -> bool {
matches!(
self.placeholder_type,
PlaceholderType::Picture
| PlaceholderType::Chart
| PlaceholderType::Table
| PlaceholderType::ClipArt
| PlaceholderType::Diagram
| PlaceholderType::MediaClip
| PlaceholderType::Object
| PlaceholderType::Content
| PlaceholderType::SlideImage
)
}
pub fn placeholder_size(&self) -> PlaceholderSize {
match self.size {
Some(1) => PlaceholderSize::Quarter,
Some(2) => PlaceholderSize::Half,
Some(3) => PlaceholderSize::Full,
_ => PlaceholderSize::Full, }
}
pub fn set_placeholder_size(&mut self, size: PlaceholderSize) {
self.size = match size {
PlaceholderSize::Quarter => Some(1),
PlaceholderSize::Half => Some(2),
PlaceholderSize::Full => Some(3),
};
}
}
impl Shape for Placeholder {
fn properties(&self) -> &ShapeProperties {
&self.container.properties
}
fn properties_mut(&mut self) -> &mut ShapeProperties {
&mut self.container.properties
}
fn text(&self) -> super::super::package::Result<String> {
Ok(String::new())
}
fn has_text(&self) -> bool {
false }
fn clone_box(&self) -> Box<dyn Shape> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::shape::ShapeType;
#[test]
#[allow(clippy::field_reassign_with_default)]
fn test_placeholder_creation() {
let mut props = ShapeProperties::default();
props.id = 2001;
props.shape_type = ShapeType::Placeholder;
props.x = 50;
props.y = 50;
props.width = 400;
props.height = 300;
let placeholder = Placeholder::new(props, vec![1, 2, 3]);
assert_eq!(placeholder.id(), 2001);
assert_eq!(placeholder.shape_type(), ShapeType::Placeholder);
assert_eq!(placeholder.placeholder_type(), PlaceholderType::Body);
assert!(placeholder.is_content());
assert!(!placeholder.is_title());
}
#[test]
#[allow(clippy::field_reassign_with_default)]
fn test_placeholder_type_operations() {
let mut props = ShapeProperties::default();
props.shape_type = ShapeType::Placeholder;
let mut placeholder = Placeholder::new(props, vec![]);
placeholder.set_placeholder_type(PlaceholderType::Title);
placeholder.set_size(1);
placeholder.set_index(0);
assert_eq!(placeholder.placeholder_type(), PlaceholderType::Title);
assert_eq!(placeholder.size(), Some(1));
assert_eq!(placeholder.index(), Some(0));
assert!(placeholder.is_title());
assert!(!placeholder.is_content());
}
#[test]
#[allow(clippy::field_reassign_with_default)]
fn test_placeholder_media_check() {
let mut props = ShapeProperties::default();
props.shape_type = ShapeType::Placeholder;
let mut placeholder = Placeholder::new(props, vec![]);
placeholder.set_placeholder_type(PlaceholderType::Picture);
assert!(placeholder.is_media());
assert!(!placeholder.is_title());
placeholder.set_placeholder_type(PlaceholderType::Title);
assert!(!placeholder.is_media());
assert!(placeholder.is_title());
}
#[test]
fn test_placeholder_type_conversion() {
assert_eq!(PlaceholderType::from(1), PlaceholderType::Title);
assert_eq!(PlaceholderType::from(2), PlaceholderType::Body);
assert_eq!(PlaceholderType::from(11), PlaceholderType::SlideImage);
assert_eq!(PlaceholderType::from(26), PlaceholderType::Picture);
assert_eq!(PlaceholderType::from(999), PlaceholderType::Custom(999));
}
}