1use std::collections::BTreeMap;
2
3use ooxml_drawingml::{ColorValue, ShapeFill, ShapeOutline, Theme};
4use serde::{Deserialize, Serialize};
5
6use crate::relationships::Relationship;
7
8#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct PptxPackage {
11 pub presentation: Presentation,
12 pub slides: Vec<Slide>,
13 pub layouts: Vec<SlideLayout>,
14 pub masters: Vec<SlideMaster>,
15 pub themes: Vec<ThemePart>,
16 pub media: Vec<MediaPart>,
17 pub relationships: BTreeMap<String, Vec<Relationship>>,
18 #[serde(skip)]
19 pub(crate) parts: Vec<PackagePart>,
20}
21
22impl PptxPackage {
23 pub fn part_bytes(&self, path: &str) -> Option<&[u8]> {
24 self.parts
25 .iter()
26 .find(|part| part.path == path)
27 .map(|part| part.bytes.as_slice())
28 }
29
30 pub fn replace_part(&mut self, path: &str, bytes: Vec<u8>) -> bool {
31 let Some(part) = self.parts.iter_mut().find(|part| part.path == path) else {
32 return false;
33 };
34 part.bytes = bytes;
35 true
36 }
37}
38
39#[derive(Clone, Debug, PartialEq)]
40pub(crate) struct PackagePart {
41 pub path: String,
42 pub bytes: Vec<u8>,
43}
44
45#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct Presentation {
48 pub part_path: String,
49 pub width_emu: i64,
50 pub height_emu: i64,
51 pub slides: Vec<SlideReference>,
52 pub master_part_paths: Vec<String>,
53}
54
55#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct SlideReference {
58 pub id: u32,
59 pub relationship_id: String,
60 pub part_path: String,
61}
62
63#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct Slide {
66 pub part_path: String,
67 pub name: Option<String>,
68 pub layout_part_path: Option<String>,
69 pub show_master_shapes: bool,
70 pub background: Option<ShapeFill>,
71 pub shapes: Vec<ShapeNode>,
72}
73
74#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct SlideLayout {
77 pub part_path: String,
78 pub name: Option<String>,
79 pub layout_type: Option<String>,
80 pub master_part_path: Option<String>,
81 pub show_master_shapes: bool,
82 pub background: Option<ShapeFill>,
83 pub shapes: Vec<ShapeNode>,
84}
85
86#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct SlideMaster {
89 pub part_path: String,
90 pub name: Option<String>,
91 pub theme_part_path: Option<String>,
92 pub layout_part_paths: Vec<String>,
93 pub background: Option<ShapeFill>,
94 pub shapes: Vec<ShapeNode>,
95 pub text_styles: TextStyleSet,
96}
97
98#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct ThemePart {
101 pub part_path: String,
102 pub theme: Theme,
103}
104
105#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct MediaPart {
108 pub part_path: String,
109 pub content_type: String,
110 pub bytes: Vec<u8>,
111}
112
113#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
114#[serde(
115 tag = "kind",
116 rename_all = "camelCase",
117 rename_all_fields = "camelCase"
118)]
119pub enum ShapeNode {
120 Shape(Shape),
121 Picture(Picture),
122 GraphicFrame(GraphicFrame),
123 Group(GroupShape),
124}
125
126impl ShapeNode {
127 pub fn id(&self) -> u32 {
128 match self {
129 Self::Shape(shape) => shape.base.id,
130 Self::Picture(picture) => picture.base.id,
131 Self::GraphicFrame(frame) => frame.base.id,
132 Self::Group(group) => group.base.id,
133 }
134 }
135}
136
137#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct ShapeBase {
140 pub id: u32,
141 pub name: String,
142 pub description: Option<String>,
143 pub hidden: bool,
144 pub placeholder: Option<Placeholder>,
145 pub transform: ShapeTransform,
146}
147
148#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct ShapeTransform {
151 pub x: i64,
152 pub y: i64,
153 pub width: i64,
154 pub height: i64,
155 pub rotation_deg: f64,
156 pub flip_h: bool,
157 pub flip_v: bool,
158 pub child_x: Option<i64>,
159 pub child_y: Option<i64>,
160 pub child_width: Option<i64>,
161 pub child_height: Option<i64>,
162}
163
164#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct Placeholder {
167 pub placeholder_type: Option<String>,
168 pub index: Option<u32>,
169 pub orientation: Option<String>,
170 pub size: Option<String>,
171}
172
173#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct Shape {
176 #[serde(flatten)]
177 pub base: ShapeBase,
178 pub geometry: String,
179 pub fill: Option<ShapeFill>,
180 pub outline: Option<ShapeOutline>,
181 pub text: Option<TextBody>,
182}
183
184#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct Picture {
187 #[serde(flatten)]
188 pub base: ShapeBase,
189 pub relationship_id: Option<String>,
190 pub media_part_path: Option<String>,
191 pub crop: PictureCrop,
192 pub fill: Option<ShapeFill>,
193 pub outline: Option<ShapeOutline>,
194}
195
196#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
197pub struct PictureCrop {
198 pub left: i32,
199 pub top: i32,
200 pub right: i32,
201 pub bottom: i32,
202}
203
204#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206pub struct GraphicFrame {
207 #[serde(flatten)]
208 pub base: ShapeBase,
209 pub data: GraphicFrameData,
210}
211
212#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
213#[serde(
214 tag = "type",
215 rename_all = "camelCase",
216 rename_all_fields = "camelCase"
217)]
218pub enum GraphicFrameData {
219 Table {
220 rows: Vec<Vec<TextBody>>,
221 },
222 Chart {
223 relationship_id: String,
224 part_path: Option<String>,
225 },
226 Diagram {
227 relationship_ids: Vec<String>,
228 },
229 Unknown {
230 uri: Option<String>,
231 },
232}
233
234#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
235#[serde(rename_all = "camelCase")]
236pub struct GroupShape {
237 #[serde(flatten)]
238 pub base: ShapeBase,
239 pub children: Vec<ShapeNode>,
240}
241
242#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
243#[serde(rename_all = "camelCase")]
244pub struct TextBody {
245 pub anchor: Option<String>,
246 pub vertical: Option<String>,
247 pub autofit: Option<TextAutofit>,
248 pub inset_left: Option<i64>,
249 pub inset_top: Option<i64>,
250 pub inset_right: Option<i64>,
251 pub inset_bottom: Option<i64>,
252 pub paragraphs: Vec<TextParagraph>,
253}
254
255#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
256#[serde(
257 tag = "type",
258 rename_all = "camelCase",
259 rename_all_fields = "camelCase"
260)]
261pub enum TextAutofit {
262 None,
263 Shape,
264 Normal {
265 font_scale: Option<f64>,
266 line_space_reduction: Option<f64>,
267 },
268}
269
270#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct TextParagraph {
273 pub properties: ParagraphProperties,
274 pub runs: Vec<TextRun>,
275 pub end_properties: Option<RunProperties>,
276}
277
278#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct ParagraphProperties {
281 pub alignment: Option<String>,
282 pub level: u32,
283 pub margin_left: Option<i64>,
284 pub indent: Option<i64>,
285 pub bullet: Option<Bullet>,
286 pub default_run: Option<RunProperties>,
287}
288
289#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
290#[serde(
291 tag = "type",
292 rename_all = "camelCase",
293 rename_all_fields = "camelCase"
294)]
295pub enum Bullet {
296 Character { value: String },
297 AutoNumber { scheme: String, start_at: u32 },
298 None,
299}
300
301#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct TextRun {
304 pub text: String,
305 pub properties: RunProperties,
306 pub field_id: Option<String>,
307 pub field_type: Option<String>,
308 pub line_break: bool,
309}
310
311#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
312#[serde(rename_all = "camelCase")]
313pub struct RunProperties {
314 pub font_size_pt: Option<f64>,
315 pub bold: Option<bool>,
316 pub italic: Option<bool>,
317 pub underline: Option<String>,
318 pub font_family: Option<String>,
319 pub color: Option<ColorValue>,
320 pub language: Option<String>,
321 pub hyperlink_relationship_id: Option<String>,
322}
323
324#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
325#[serde(rename_all = "camelCase")]
326pub struct TextStyleSet {
327 pub title: Vec<ParagraphProperties>,
328 pub body: Vec<ParagraphProperties>,
329 pub other: Vec<ParagraphProperties>,
330}