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