pub type Emu = i64;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Transform {
pub x: Emu,
pub y: Emu,
pub cx: Emu,
pub cy: Emu,
pub rot: i64,
pub flip_h: bool,
pub flip_v: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChildSpace {
pub x: Emu,
pub y: Emu,
pub cx: Emu,
pub cy: Emu,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Placeholder {
pub kind: PlaceholderKind,
pub idx: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PlaceholderKind {
Title,
Body,
CenterTitle,
Subtitle,
DateTime,
SlideNumber,
Footer,
Header,
#[default]
Object,
Chart,
Table,
ClipArt,
Diagram,
Media,
Picture,
SlideImage,
Other,
}
impl PlaceholderKind {
pub fn parse(value: &[u8]) -> PlaceholderKind {
match value {
b"title" => PlaceholderKind::Title,
b"body" => PlaceholderKind::Body,
b"ctrTitle" => PlaceholderKind::CenterTitle,
b"subTitle" => PlaceholderKind::Subtitle,
b"dt" => PlaceholderKind::DateTime,
b"sldNum" => PlaceholderKind::SlideNumber,
b"ftr" => PlaceholderKind::Footer,
b"hdr" => PlaceholderKind::Header,
b"obj" => PlaceholderKind::Object,
b"chart" => PlaceholderKind::Chart,
b"tbl" => PlaceholderKind::Table,
b"clipArt" => PlaceholderKind::ClipArt,
b"dgm" => PlaceholderKind::Diagram,
b"media" => PlaceholderKind::Media,
b"pic" => PlaceholderKind::Picture,
b"sldImg" => PlaceholderKind::SlideImage,
_ => PlaceholderKind::Other,
}
}
pub fn is_title(self) -> bool {
matches!(self, PlaceholderKind::Title | PlaceholderKind::CenterTitle)
}
pub fn is_furniture(self) -> bool {
matches!(
self,
PlaceholderKind::DateTime
| PlaceholderKind::SlideNumber
| PlaceholderKind::Footer
| PlaceholderKind::Header
)
}
pub fn as_str(self) -> &'static str {
match self {
PlaceholderKind::Title => "title",
PlaceholderKind::Body => "body",
PlaceholderKind::CenterTitle => "ctrTitle",
PlaceholderKind::Subtitle => "subTitle",
PlaceholderKind::DateTime => "dt",
PlaceholderKind::SlideNumber => "sldNum",
PlaceholderKind::Footer => "ftr",
PlaceholderKind::Header => "hdr",
PlaceholderKind::Object => "obj",
PlaceholderKind::Chart => "chart",
PlaceholderKind::Table => "tbl",
PlaceholderKind::ClipArt => "clipArt",
PlaceholderKind::Diagram => "dgm",
PlaceholderKind::Media => "media",
PlaceholderKind::Picture => "pic",
PlaceholderKind::SlideImage => "sldImg",
PlaceholderKind::Other => "other",
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Bullet {
#[default]
Inherited,
None,
Char(String),
AutoNumber {
scheme: String,
start_at: u32,
},
Picture,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RunProps {
pub bold: Option<bool>,
pub italic: Option<bool>,
pub underline: Option<bool>,
pub strike: Option<bool>,
pub size: Option<u32>,
pub hyperlink: Option<String>,
pub lang: Option<String>,
pub typeface: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RunKind {
Text,
LineBreak,
Field(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Run {
pub kind: RunKind,
pub text: String,
pub props: RunProps,
}
impl Run {
pub fn text(text: impl Into<String>) -> Self {
Self {
kind: RunKind::Text,
text: text.into(),
props: RunProps::default(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Paragraph {
pub level: u8,
pub bullet: Bullet,
pub runs: Vec<Run>,
}
impl Paragraph {
pub fn text(&self) -> String {
let mut out = String::new();
self.write_text(&mut out);
out
}
pub fn write_text(&self, out: &mut String) {
for run in &self.runs {
match run.kind {
RunKind::LineBreak => out.push('\n'),
_ => out.push_str(&run.text),
}
}
}
pub fn is_empty(&self) -> bool {
self.runs
.iter()
.all(|run| run.text.is_empty() && run.kind != RunKind::LineBreak)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TextBody {
pub paragraphs: Vec<Paragraph>,
}
impl TextBody {
pub fn text(&self) -> String {
let mut out = String::new();
self.write_text(&mut out);
out
}
pub fn write_text(&self, out: &mut String) {
for (i, paragraph) in self.paragraphs.iter().enumerate() {
if i > 0 {
out.push('\n');
}
paragraph.write_text(out);
}
}
pub fn is_empty(&self) -> bool {
self.paragraphs.iter().all(Paragraph::is_empty)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Cell {
pub body: TextBody,
pub grid_span: u32,
pub row_span: u32,
pub h_merge: bool,
pub v_merge: bool,
}
impl Cell {
pub fn is_origin(&self) -> bool {
!self.h_merge && !self.v_merge
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Row {
pub height: Emu,
pub cells: Vec<Cell>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Table {
pub column_widths: Vec<Emu>,
pub rows: Vec<Row>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Picture {
pub embed: Option<String>,
pub link: Option<String>,
pub media: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OleObject {
pub prog_id: Option<String>,
pub rel_id: Option<String>,
pub preview: Option<Picture>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Content {
Text(TextBody),
Picture(Picture),
Group(Vec<Shape>, Option<ChildSpace>),
Table(Table),
Chart(Option<String>),
Diagram(Option<String>),
Ole(OleObject),
Connector,
ContentPart(Option<String>),
UnknownGraphic(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Shape {
pub id: u32,
pub name: String,
pub hidden: bool,
pub description: Option<String>,
pub hyperlink: Option<String>,
pub placeholder: Option<Placeholder>,
pub transform: Option<Transform>,
pub text_box: bool,
pub content: Content,
}
impl Shape {
pub fn text_body(&self) -> Option<&TextBody> {
match &self.content {
Content::Text(body) => Some(body),
_ => None,
}
}
pub fn is_title(&self) -> bool {
self.placeholder
.as_ref()
.is_some_and(|ph| ph.kind.is_title())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SlideKind {
Slide,
Layout,
Master,
Notes,
NotesMaster,
HandoutMaster,
}
impl SlideKind {
pub fn root_name(self) -> &'static str {
match self {
SlideKind::Slide => "sld",
SlideKind::Layout => "sldLayout",
SlideKind::Master => "sldMaster",
SlideKind::Notes => "notes",
SlideKind::NotesMaster => "notesMaster",
SlideKind::HandoutMaster => "handoutMaster",
}
}
pub fn from_root(local: &[u8]) -> Option<SlideKind> {
match local {
b"sld" => Some(SlideKind::Slide),
b"sldLayout" => Some(SlideKind::Layout),
b"sldMaster" => Some(SlideKind::Master),
b"notes" => Some(SlideKind::Notes),
b"notesMaster" => Some(SlideKind::NotesMaster),
b"handoutMaster" => Some(SlideKind::HandoutMaster),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SlideContent {
pub kind: SlideKind,
pub name: Option<String>,
pub show: bool,
pub shapes: Vec<Shape>,
}
impl SlideContent {
pub fn walk(&self) -> impl Iterator<Item = &Shape> {
let mut stack: Vec<&Shape> = self.shapes.iter().rev().collect();
std::iter::from_fn(move || {
let shape = stack.pop()?;
if let Content::Group(children, _) = &shape.content {
stack.extend(children.iter().rev());
}
Some(shape)
})
}
pub fn title(&self) -> Option<String> {
self.walk()
.find(|shape| shape.is_title())
.and_then(Shape::text_body)
.map(TextBody::text)
.filter(|text| !text.trim().is_empty())
}
}