use crate::common::{Error, Result};
#[cfg(feature = "ole")]
use crate::ole;
#[cfg(feature = "ooxml")]
use crate::ooxml;
use std::fs::File;
use std::io::{Cursor, Read, Seek};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct PptxSlideData {
pub text: String,
pub name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct PptSlideData {
pub text: String,
pub slide_number: usize,
pub shape_count: usize,
}
enum PresentationImpl {
#[cfg(feature = "ole")]
Ppt(ole::ppt::Presentation),
#[cfg(feature = "ooxml")]
Pptx(Box<ooxml::pptx::Presentation<'static>>),
}
pub struct Presentation {
inner: PresentationImpl,
#[cfg(feature = "ooxml")]
_package: Option<Box<ooxml::pptx::Package>>,
}
impl Presentation {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let mut file = File::open(path)?;
let format = detect_presentation_format(&mut file)?;
match format {
#[cfg(feature = "ole")]
PresentationFormat::Ppt => {
let mut package = ole::ppt::Package::open(path)
.map_err(Error::from)?;
let pres = package.presentation()
.map_err(Error::from)?;
Ok(Self {
inner: PresentationImpl::Ppt(pres),
#[cfg(feature = "ooxml")]
_package: None,
})
}
#[cfg(not(feature = "ole"))]
PresentationFormat::Ppt => {
Err(Error::FeatureDisabled("ole".to_string()))
}
#[cfg(feature = "ooxml")]
PresentationFormat::Pptx => {
let package = Box::new(ooxml::pptx::Package::open(path)
.map_err(Error::from)?);
let pres_ref = unsafe {
let pkg_ptr = &*package as *const ooxml::pptx::Package;
let pres = (*pkg_ptr).presentation()
.map_err(Error::from)?;
std::mem::transmute::<ooxml::pptx::Presentation<'_>, ooxml::pptx::Presentation<'static>>(pres)
};
Ok(Self {
inner: PresentationImpl::Pptx(Box::new(pres_ref)),
_package: Some(package),
})
}
#[cfg(not(feature = "ooxml"))]
PresentationFormat::Pptx => {
Err(Error::FeatureDisabled("ooxml".to_string()))
}
}
}
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
let format = detect_presentation_format_from_bytes(&bytes)?;
match format {
#[cfg(feature = "ole")]
PresentationFormat::Ppt => {
let cursor = Cursor::new(bytes);
let mut package = ole::ppt::Package::from_reader(cursor)
.map_err(Error::from)?;
let pres = package.presentation()
.map_err(Error::from)?;
Ok(Self {
inner: PresentationImpl::Ppt(pres),
#[cfg(feature = "ooxml")]
_package: None,
})
}
#[cfg(not(feature = "ole"))]
PresentationFormat::Ppt => {
Err(Error::FeatureDisabled("ole".to_string()))
}
#[cfg(feature = "ooxml")]
PresentationFormat::Pptx => {
let cursor = Cursor::new(bytes);
let package = Box::new(ooxml::pptx::Package::from_reader(cursor)
.map_err(Error::from)?);
let pres_ref = unsafe {
let pkg_ptr = &*package as *const ooxml::pptx::Package;
let pres = (*pkg_ptr).presentation()
.map_err(Error::from)?;
std::mem::transmute::<ooxml::pptx::Presentation<'_>, ooxml::pptx::Presentation<'static>>(pres)
};
Ok(Self {
inner: PresentationImpl::Pptx(Box::new(pres_ref)),
_package: Some(package),
})
}
#[cfg(not(feature = "ooxml"))]
PresentationFormat::Pptx => {
Err(Error::FeatureDisabled("ooxml".to_string()))
}
}
}
pub fn text(&self) -> Result<String> {
match &self.inner {
#[cfg(feature = "ole")]
PresentationImpl::Ppt(pres) => {
pres.text().map_err(Error::from)
}
#[cfg(feature = "ooxml")]
PresentationImpl::Pptx(pres) => {
let slides = pres.slides().map_err(Error::from)?;
let mut texts = Vec::new();
for slide in slides {
if let Ok(text) = slide.text() && !text.is_empty() {
texts.push(text);
}
}
Ok(texts.join("\n\n"))
}
}
}
pub fn slide_count(&self) -> Result<usize> {
match &self.inner {
#[cfg(feature = "ole")]
PresentationImpl::Ppt(pres) => {
Ok(pres.slide_count())
}
#[cfg(feature = "ooxml")]
PresentationImpl::Pptx(pres) => {
pres.slide_count().map_err(Error::from)
}
}
}
pub fn slides(&self) -> Result<Vec<Slide>> {
match &self.inner {
#[cfg(feature = "ole")]
PresentationImpl::Ppt(pres) => {
let ppt_slides = pres.slides().map_err(Error::from)?;
ppt_slides.iter()
.map(|s| {
let text = s.text().map_err(Error::from)?.to_string();
let slide_number = s.slide_number();
let shape_count = s.shape_count().unwrap_or(0);
Ok(Slide::Ppt(PptSlideData {
text,
slide_number,
shape_count,
}))
})
.collect()
}
#[cfg(feature = "ooxml")]
PresentationImpl::Pptx(pres) => {
let slides = pres.slides()
.map_err(Error::from)?;
slides.iter()
.map(|s| {
let text = s.text().map_err(Error::from)?;
let name = s.name().ok();
Ok(Slide::Pptx(PptxSlideData { text, name }))
})
.collect()
}
}
}
pub fn slide_width(&self) -> Result<Option<i64>> {
match &self.inner {
#[cfg(feature = "ole")]
PresentationImpl::Ppt(_) => Ok(None),
#[cfg(feature = "ooxml")]
PresentationImpl::Pptx(pres) => {
pres.slide_width().map_err(Error::from)
}
}
}
pub fn slide_height(&self) -> Result<Option<i64>> {
match &self.inner {
#[cfg(feature = "ole")]
PresentationImpl::Ppt(_) => Ok(None),
#[cfg(feature = "ooxml")]
PresentationImpl::Pptx(pres) => {
pres.slide_height().map_err(Error::from)
}
}
}
}
pub enum Slide {
Ppt(PptSlideData),
Pptx(PptxSlideData),
}
impl Slide {
pub fn text(&self) -> Result<String> {
match self {
Slide::Ppt(data) => Ok(data.text.clone()),
Slide::Pptx(data) => Ok(data.text.clone()),
}
}
pub fn number(&self) -> Option<usize> {
match self {
Slide::Ppt(data) => Some(data.slide_number),
Slide::Pptx(_) => None,
}
}
pub fn shape_count(&self) -> Option<usize> {
match self {
Slide::Ppt(data) => Some(data.shape_count),
Slide::Pptx(_) => None,
}
}
pub fn name(&self) -> Result<Option<String>> {
match self {
Slide::Ppt(_) => Ok(None),
Slide::Pptx(data) => Ok(data.name.clone()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PresentationFormat {
Ppt,
Pptx,
}
fn detect_presentation_format<R: Read + Seek>(reader: &mut R) -> Result<PresentationFormat> {
use std::io::SeekFrom;
let mut header = [0u8; 8];
reader.read_exact(&mut header)?;
reader.seek(SeekFrom::Start(0))?;
detect_presentation_format_from_signature(&header)
}
#[inline]
fn detect_presentation_format_from_bytes(bytes: &[u8]) -> Result<PresentationFormat> {
if bytes.len() < 4 {
return Err(Error::InvalidFormat("File too small to determine format".to_string()));
}
detect_presentation_format_from_signature(&bytes[0..8.min(bytes.len())])
}
#[inline]
fn detect_presentation_format_from_signature(header: &[u8]) -> Result<PresentationFormat> {
if header.len() >= 4 && header[0..4] == [0xD0, 0xCF, 0x11, 0xE0] {
return Ok(PresentationFormat::Ppt);
}
if header.len() >= 4 && header[0..4] == [0x50, 0x4B, 0x03, 0x04] {
return Ok(PresentationFormat::Pptx);
}
Err(Error::NotOfficeFile)
}