use super::super::package::Result;
use super::super::shapes::ShapeEnum;
use super::super::records::PptRecord;
use super::slide_factory::SlideData;
use once_cell::unsync::OnceCell;
pub struct Slide<'doc> {
persist_id: u32,
slide_number: usize,
record: PptRecord,
#[allow(dead_code)]
doc_data: &'doc [u8],
shapes: OnceCell<Vec<ShapeEnum>>,
text_cache: OnceCell<String>,
}
impl<'doc> Slide<'doc> {
pub fn from_slide_data(data: SlideData<'doc>, slide_number: usize) -> Self {
let doc_data_ref = data.doc_data();
Self {
persist_id: data.persist_id,
slide_number,
doc_data: doc_data_ref,
record: data.record,
shapes: OnceCell::new(),
text_cache: OnceCell::new(),
}
}
#[inline]
pub fn slide_number(&self) -> usize {
self.slide_number
}
#[inline]
pub fn persist_id(&self) -> u32 {
self.persist_id
}
pub fn shapes(&self) -> Result<&[ShapeEnum]> {
self.shapes
.get_or_try_init(|| self.parse_shapes())
.map(|v| v.as_slice())
}
pub fn shape_count(&self) -> Result<usize> {
Ok(self.shapes()?.len())
}
pub fn text(&self) -> Result<&str> {
self.text_cache
.get_or_try_init(|| self.extract_all_text())
.map(|s| s.as_str())
}
fn parse_shapes(&self) -> Result<Vec<ShapeEnum>> {
let ppdrawing = match self.record.find_child(crate::ole::consts::PptRecordType::PPDrawing) {
Some(record) => record,
None => return Ok(Vec::new()),
};
let escher_shapes = super::super::escher::EscherShapeFactory::extract_shapes_from_ppdrawing(&ppdrawing.data)?;
let shapes: Vec<ShapeEnum> = escher_shapes.iter()
.filter_map(|escher_shape| {
Self::convert_escher_to_shape_enum(escher_shape)
})
.collect();
Ok(shapes)
}
fn convert_escher_to_shape_enum(escher_shape: &super::super::escher::EscherShape<'_>) -> Option<ShapeEnum> {
use super::super::escher::EscherShapeType;
use super::super::shapes::*;
let shape_id = escher_shape.shape_id().unwrap_or(0);
let anchor = escher_shape.anchor();
match escher_shape.shape_type() {
EscherShapeType::TextBox => {
let mut properties = shape::ShapeProperties::default();
properties.id = shape_id;
properties.shape_type = shape::ShapeType::TextBox;
if let Some(a) = anchor {
properties.x = a.left;
properties.y = a.top;
properties.width = a.width();
properties.height = a.height();
}
let text = escher_shape.text().unwrap_or_default();
let mut textbox = TextBox::new(properties, Vec::new());
if !text.is_empty() {
textbox.set_text(text);
}
Some(ShapeEnum::TextBox(textbox))
}
EscherShapeType::Picture => {
let mut picture = shape_enum::PictureShape::new(shape_id);
if let Some(a) = anchor {
picture.set_bounds(a.left, a.top, a.width(), a.height());
}
use super::super::escher::EscherPropertyId;
if let Some(blip_id) = escher_shape.properties().get_int(EscherPropertyId::PictureId) {
picture.set_blip_id(blip_id as u32);
}
Some(ShapeEnum::Picture(picture))
}
EscherShapeType::Line => {
if let Some(a) = anchor {
let mut line = shape_enum::LineShape::new(
shape_id,
a.left,
a.top,
a.right,
a.bottom
);
use super::super::escher::EscherPropertyId;
if let Some(width) = escher_shape.properties().get_int(EscherPropertyId::LineWidth) {
line.set_width(width);
}
if let Some(color) = escher_shape.properties().get_color(EscherPropertyId::LineColor) {
line.set_color(color);
}
Some(ShapeEnum::Line(line))
} else {
None
}
}
EscherShapeType::Group => {
let mut group = shape_enum::GroupShape::new(shape_id);
if let Some(a) = anchor {
group.set_bounds(a.left, a.top, a.width(), a.height());
}
Some(ShapeEnum::Group(group))
}
EscherShapeType::Rectangle | EscherShapeType::Ellipse | EscherShapeType::AutoShape => {
let mut properties = shape::ShapeProperties::default();
properties.id = shape_id;
properties.shape_type = shape::ShapeType::AutoShape;
if let Some(a) = anchor {
properties.x = a.left;
properties.y = a.top;
properties.width = a.width();
properties.height = a.height();
}
let autoshape = AutoShape::new(properties, Vec::new());
Some(ShapeEnum::AutoShape(autoshape))
}
_ => None,
}
}
fn extract_all_text(&self) -> Result<String> {
let mut text_parts = Vec::new();
Self::extract_text_recursive(&self.record, &mut text_parts);
if let Some(ppdrawing) = self.record.find_child(crate::ole::consts::PptRecordType::PPDrawing) {
if let Ok(escher_text) = super::super::escher::extract_text_from_escher(&ppdrawing.data) {
if !escher_text.trim().is_empty() {
text_parts.push(escher_text);
}
}
}
Ok(if text_parts.is_empty() {
String::new()
} else {
text_parts.join("\n")
})
}
fn extract_text_recursive(record: &crate::ole::ppt::records::PptRecord, text_parts: &mut Vec<String>) {
if let Ok(record_text) = record.extract_text() {
let trimmed = record_text.trim();
if !trimmed.is_empty() {
text_parts.push(trimmed.to_string());
}
}
for child in &record.children {
Self::extract_text_recursive(child, text_parts);
}
}
#[inline]
pub fn has_drawing(&self) -> bool {
self.record.find_child(crate::ole::consts::PptRecordType::PPDrawing).is_some()
}
#[inline]
pub fn record(&self) -> &PptRecord {
&self.record
}
}
#[cfg(test)]
mod tests {
use super::*;
}