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 #[serde(default)]
180 pub adjust_values: BTreeMap<String, f64>,
181 pub fill: Option<ShapeFill>,
182 pub outline: Option<ShapeOutline>,
183 pub text: Option<TextBody>,
184}
185
186#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct Picture {
189 #[serde(flatten)]
190 pub base: ShapeBase,
191 pub relationship_id: Option<String>,
192 pub media_part_path: Option<String>,
193 pub crop: PictureCrop,
194 pub fill: Option<ShapeFill>,
195 pub outline: Option<ShapeOutline>,
196}
197
198#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
199pub struct PictureCrop {
200 pub left: i32,
201 pub top: i32,
202 pub right: i32,
203 pub bottom: i32,
204}
205
206#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
207#[serde(rename_all = "camelCase")]
208pub struct GraphicFrame {
209 #[serde(flatten)]
210 pub base: ShapeBase,
211 pub data: GraphicFrameData,
212}
213
214#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
215#[serde(
216 tag = "type",
217 rename_all = "camelCase",
218 rename_all_fields = "camelCase"
219)]
220pub enum GraphicFrameData {
221 Table {
222 rows: Vec<Vec<TextBody>>,
223 },
224 Chart {
225 relationship_id: String,
226 part_path: Option<String>,
227 },
228 Diagram {
229 relationship_ids: Vec<String>,
230 },
231 Unknown {
232 uri: Option<String>,
233 },
234}
235
236#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct GroupShape {
239 #[serde(flatten)]
240 pub base: ShapeBase,
241 pub children: Vec<ShapeNode>,
242}
243
244#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
245#[serde(rename_all = "camelCase")]
246pub struct TextBody {
247 pub anchor: Option<String>,
248 pub vertical: Option<String>,
249 pub autofit: Option<TextAutofit>,
250 pub inset_left: Option<i64>,
251 pub inset_top: Option<i64>,
252 pub inset_right: Option<i64>,
253 pub inset_bottom: Option<i64>,
254 pub paragraphs: Vec<TextParagraph>,
255}
256
257#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
258#[serde(
259 tag = "type",
260 rename_all = "camelCase",
261 rename_all_fields = "camelCase"
262)]
263pub enum TextAutofit {
264 None,
265 Shape,
266 Normal {
267 font_scale: Option<f64>,
268 line_space_reduction: Option<f64>,
269 },
270}
271
272#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct TextParagraph {
275 pub properties: ParagraphProperties,
276 pub runs: Vec<TextRun>,
277 pub end_properties: Option<RunProperties>,
278}
279
280#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
281#[serde(rename_all = "camelCase")]
282pub struct ParagraphProperties {
283 pub alignment: Option<String>,
284 pub level: u32,
285 pub margin_left: Option<i64>,
286 pub indent: Option<i64>,
287 pub bullet: Option<Bullet>,
288 pub default_run: Option<RunProperties>,
289}
290
291#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
292#[serde(
293 tag = "type",
294 rename_all = "camelCase",
295 rename_all_fields = "camelCase"
296)]
297pub enum Bullet {
298 Character { value: String },
299 AutoNumber { scheme: String, start_at: u32 },
300 None,
301}
302
303#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct TextRun {
306 pub text: String,
307 pub properties: RunProperties,
308 pub field_id: Option<String>,
309 pub field_type: Option<String>,
310 pub line_break: bool,
311}
312
313#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
314#[serde(rename_all = "camelCase")]
315pub struct RunProperties {
316 pub font_size_pt: Option<f64>,
317 pub bold: Option<bool>,
318 pub italic: Option<bool>,
319 pub underline: Option<String>,
320 pub font_family: Option<String>,
321 pub color: Option<ColorValue>,
322 pub language: Option<String>,
323 pub hyperlink_relationship_id: Option<String>,
324}
325
326#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
327#[serde(rename_all = "camelCase")]
328pub struct TextStyleSet {
329 pub title: Vec<ParagraphProperties>,
330 pub body: Vec<ParagraphProperties>,
331 pub other: Vec<ParagraphProperties>,
332}