1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum PresentationFormat {
8 Pptx,
9 Ppt,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct PresentationSize {
15 pub width_emu: u64,
16 pub height_emu: u64,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct PresentationWarning {
22 pub code: String,
23 pub message: String,
24 pub severity: String,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub slide_index: Option<usize>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub node_id: Option<String>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub part_name: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub feature: Option<String>,
33}
34
35impl PresentationWarning {
36 pub fn warning(code: &str, message: impl Into<String>) -> Self {
37 Self {
38 code: code.into(),
39 message: message.into(),
40 severity: "warning".into(),
41 slide_index: None,
42 node_id: None,
43 part_name: None,
44 feature: None,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Default, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct PresentationMetadata {
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub title: Option<String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub subject: Option<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub creator: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub company: Option<String>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct PresentationAsset {
65 pub id: String,
66 pub content_type: String,
67 pub byte_length: usize,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub file_name: Option<String>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub data: Option<serde_bytes::ByteBuf>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct PresentationEmbeddedFont {
77 pub family: String,
78 pub asset_id: String,
79 pub style: String,
80 pub weight: String,
81}
82
83#[derive(Debug, Clone, Default, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct Transform {
86 pub x: i64,
87 pub y: i64,
88 pub width: i64,
89 pub height: i64,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub rotation: Option<f64>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub flip_horizontal: Option<bool>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub flip_vertical: Option<bool>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99#[serde(
100 tag = "type",
101 rename_all = "kebab-case",
102 rename_all_fields = "camelCase"
103)]
104#[allow(clippy::large_enum_variant)]
105pub enum SlideNode {
106 Shape {
107 id: String,
108 name: String,
109 transform: Transform,
110 geometry: ShapeGeometry,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 fill: Option<FillStyle>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 line: Option<LineStyle>,
115 paragraphs: Vec<TextParagraph>,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 vertical_alignment: Option<VerticalAlignment>,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 text_insets: Option<TextInsets>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 autofit: Option<TextAutofit>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 text_rotation: Option<f64>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 vertical_text: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 horizontal_overflow: Option<String>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 vertical_overflow: Option<String>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 text_wrap: Option<String>,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 column_count: Option<usize>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 column_spacing: Option<i64>,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 right_to_left_columns: Option<bool>,
138 #[serde(skip_serializing_if = "Option::is_none")]
139 space_first_last_paragraph: Option<bool>,
140 },
141 Image {
142 id: String,
143 name: String,
144 transform: Transform,
145 asset_id: String,
146 #[serde(skip_serializing_if = "Option::is_none")]
147 crop: Option<ImageCrop>,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 opacity: Option<f64>,
150 preserve_aspect_ratio: bool,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 effects: Option<ImageEffects>,
153 },
154 Group {
155 id: String,
156 name: String,
157 transform: Transform,
158 children: Vec<SlideNode>,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 child_transform: Option<Transform>,
161 },
162 Table {
163 id: String,
164 name: String,
165 transform: Transform,
166 rows: Vec<Vec<TableCell>>,
167 #[serde(skip_serializing_if = "Vec::is_empty")]
168 column_widths: Vec<i64>,
169 #[serde(skip_serializing_if = "Vec::is_empty")]
170 row_heights: Vec<i64>,
171 },
172 Chart {
173 id: String,
174 name: String,
175 transform: Transform,
176 chart_type: String,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 title: Option<String>,
179 series: Vec<ChartSeries>,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 has_legend: Option<bool>,
182 #[serde(skip_serializing_if = "Option::is_none")]
185 chart_xml: Option<String>,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 chart_svg: Option<String>,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 chart_style_xml: Option<String>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 chart_colors_xml: Option<String>,
195 },
196 Unknown {
197 id: String,
198 name: String,
199 transform: Transform,
200 feature: String,
201 },
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206pub struct TableCell {
207 #[serde(skip_serializing_if = "Option::is_none")]
208 pub row_span: Option<usize>,
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub col_span: Option<usize>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub fill: Option<FillStyle>,
213 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
214 pub borders: BTreeMap<String, LineStyle>,
215 pub paragraphs: Vec<TextParagraph>,
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub text_insets: Option<TextInsets>,
218 #[serde(skip_serializing_if = "Option::is_none")]
219 pub vertical_alignment: Option<VerticalAlignment>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 pub text_rotation: Option<f64>,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct TextInsets {
227 pub top: i64,
228 pub right: i64,
229 pub bottom: i64,
230 pub left: i64,
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
234#[serde(rename_all = "camelCase")]
235pub struct ImageCrop {
236 pub top: f64,
237 pub right: f64,
238 pub bottom: f64,
239 pub left: f64,
240}
241
242#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct ImageEffects {
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub bi_level_threshold: Option<f64>,
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub grayscale: Option<bool>,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub duotone: Option<Vec<ColorValue>>,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub brightness: Option<f64>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub contrast: Option<f64>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
264#[serde(rename_all = "camelCase")]
265pub struct ChartSeries {
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub name: Option<String>,
268 #[serde(skip_serializing_if = "Option::is_none")]
269 pub categories: Option<Vec<serde_json::Value>>,
270 pub values: Vec<Option<f64>>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 pub color: Option<ColorValue>,
273}
274
275#[derive(Debug, Clone, Default, Serialize, Deserialize)]
276pub struct ShapeGeometry {
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub preset: Option<String>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub path: Option<String>,
282}
283
284#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
285#[serde(rename_all = "camelCase")]
286pub struct ColorValue {
287 pub value: String,
288 #[serde(skip_serializing_if = "Option::is_none")]
289 pub alpha: Option<f64>,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
293#[serde(rename_all = "camelCase")]
294pub struct GradientStop {
295 pub position: f64,
296 pub color: ColorValue,
297}
298
299#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
300#[serde(rename_all = "lowercase")]
301pub enum FillImageMode {
302 Stretch,
303 Tile,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
307#[serde(
308 tag = "type",
309 rename_all = "lowercase",
310 rename_all_fields = "camelCase"
311)]
312pub enum FillStyle {
313 None,
314 Solid {
315 color: ColorValue,
316 },
317 Gradient {
318 #[serde(skip_serializing_if = "Option::is_none")]
319 angle: Option<f64>,
320 stops: Vec<GradientStop>,
321 },
322 Pattern {
323 preset: String,
324 foreground: ColorValue,
325 background: ColorValue,
326 },
327 Image {
328 asset_id: String,
329 mode: FillImageMode,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 crop: Option<ImageCrop>,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 opacity: Option<f64>,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 effects: Option<ImageEffects>,
336 },
337}
338
339#[derive(Debug, Clone, Default, Serialize, Deserialize)]
340#[serde(rename_all = "camelCase")]
341pub struct LineStyle {
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub color: Option<ColorValue>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub width: Option<f64>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub dash: Option<String>,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub start_arrow: Option<String>,
350 #[serde(skip_serializing_if = "Option::is_none")]
351 pub end_arrow: Option<String>,
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
355#[serde(rename_all = "lowercase")]
356pub enum VerticalAlignment {
357 Top,
358 Middle,
359 Bottom,
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
363#[serde(rename_all = "lowercase")]
364pub enum TextAutoFitMode {
365 None,
366 Normal,
367 Shape,
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
371#[serde(rename_all = "camelCase")]
372pub struct TextAutofit {
373 pub mode: TextAutoFitMode,
374 #[serde(skip_serializing_if = "Option::is_none")]
375 pub font_scale: Option<f64>,
376 #[serde(skip_serializing_if = "Option::is_none")]
377 pub line_spacing_reduction: Option<f64>,
378}
379
380#[derive(Debug, Clone, Default, Serialize, Deserialize)]
381#[serde(rename_all = "camelCase")]
382pub struct TextParagraph {
383 pub runs: Vec<TextRun>,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 pub alignment: Option<String>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 pub level: Option<usize>,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 pub bullet: Option<TextBullet>,
390 #[serde(skip_serializing_if = "Option::is_none")]
391 pub line_spacing: Option<TextSpacing>,
392 #[serde(skip_serializing_if = "Option::is_none")]
393 pub space_before: Option<TextSpacing>,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub space_after: Option<TextSpacing>,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub rtl: Option<bool>,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub margin_left_emu: Option<i64>,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 pub indent_emu: Option<i64>,
402 #[serde(skip_serializing_if = "Option::is_none")]
403 pub default_tab_size_emu: Option<i64>,
404 #[serde(skip_serializing_if = "Option::is_none")]
405 pub tab_stops: Option<Vec<TextTabStop>>,
406 #[serde(skip_serializing_if = "Option::is_none")]
407 pub east_asian_line_break: Option<bool>,
408 #[serde(skip_serializing_if = "Option::is_none")]
409 pub latin_line_break: Option<bool>,
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub hanging_punctuation: Option<bool>,
412}
413
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
415#[serde(rename_all = "lowercase")]
416pub enum TextTabAlignment {
417 Left,
418 Center,
419 Right,
420 Decimal,
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
424#[serde(rename_all = "camelCase")]
425pub struct TextTabStop {
426 pub position_emu: i64,
427 pub alignment: TextTabAlignment,
428}
429
430#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
431#[serde(rename_all = "camelCase")]
432pub struct TextSpacing {
433 pub value: f64,
434 pub unit: TextSpacingUnit,
435}
436
437#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
438#[serde(rename_all = "lowercase")]
439pub enum TextSpacingUnit {
440 Points,
441 Percent,
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446pub struct TextBullet {
447 pub kind: String,
448 #[serde(skip_serializing_if = "Option::is_none")]
449 pub value: Option<String>,
450 #[serde(skip_serializing_if = "Option::is_none")]
451 pub font_family: Option<String>,
452 #[serde(skip_serializing_if = "Option::is_none")]
453 pub font_size_pt: Option<f64>,
454 #[serde(skip_serializing_if = "Option::is_none")]
455 pub size_percent: Option<f64>,
456 #[serde(skip_serializing_if = "Option::is_none")]
457 pub start_at: Option<usize>,
458}
459
460#[derive(Debug, Clone, Default, Serialize, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct TextRun {
463 pub text: String,
464 #[serde(skip_serializing_if = "Option::is_none")]
465 pub font_family: Option<String>,
466 #[serde(skip_serializing_if = "Option::is_none")]
467 pub east_asian_font_family: Option<String>,
468 #[serde(skip_serializing_if = "Option::is_none")]
469 pub complex_script_font_family: Option<String>,
470 #[serde(skip_serializing_if = "Option::is_none")]
471 pub symbol_font_family: Option<String>,
472 #[serde(skip_serializing_if = "Option::is_none")]
473 pub font_size_pt: Option<f64>,
474 #[serde(skip_serializing_if = "Option::is_none")]
475 pub bold: Option<bool>,
476 #[serde(skip_serializing_if = "Option::is_none")]
477 pub italic: Option<bool>,
478 #[serde(skip_serializing_if = "Option::is_none")]
479 pub underline: Option<bool>,
480 #[serde(skip_serializing_if = "Option::is_none")]
481 pub strike: Option<bool>,
482 #[serde(skip_serializing_if = "Option::is_none")]
483 pub color: Option<ColorValue>,
484 #[serde(skip_serializing_if = "Option::is_none")]
485 pub baseline: Option<f64>,
486 #[serde(skip_serializing_if = "Option::is_none")]
487 pub language: Option<String>,
488 #[serde(skip_serializing_if = "Option::is_none")]
489 pub alternative_language: Option<String>,
490 #[serde(skip_serializing_if = "Option::is_none")]
491 pub right_to_left: Option<bool>,
492 #[serde(skip_serializing_if = "Option::is_none")]
493 pub hyperlink: Option<String>,
494 #[serde(skip_serializing_if = "Option::is_none")]
495 pub character_spacing_pt: Option<f64>,
496 #[serde(skip_serializing_if = "Option::is_none")]
497 pub kerning_threshold_pt: Option<f64>,
498}
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
501#[serde(rename_all = "camelCase")]
502pub struct PresentationTheme {
503 pub id: String,
504 #[serde(skip_serializing_if = "Option::is_none")]
505 pub name: Option<String>,
506 pub colors: BTreeMap<String, String>,
507 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
508 pub major_fonts: BTreeMap<String, String>,
509 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
510 pub minor_fonts: BTreeMap<String, String>,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize)]
514#[serde(rename_all = "camelCase")]
515pub struct SlideMaster {
516 pub id: String,
517 #[serde(skip_serializing_if = "Option::is_none")]
518 pub name: Option<String>,
519 #[serde(skip_serializing_if = "Option::is_none")]
520 pub theme_id: Option<String>,
521 pub nodes: Vec<SlideNode>,
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
525#[serde(rename_all = "camelCase")]
526pub struct SlideLayout {
527 pub id: String,
528 #[serde(skip_serializing_if = "Option::is_none")]
529 pub name: Option<String>,
530 #[serde(skip_serializing_if = "Option::is_none")]
531 pub master_id: Option<String>,
532 pub nodes: Vec<SlideNode>,
533}
534
535#[derive(Debug, Clone, Serialize, Deserialize)]
536#[serde(rename_all = "camelCase")]
537pub struct PresentationSlide {
538 pub id: String,
539 pub index: usize,
540 #[serde(skip_serializing_if = "Option::is_none")]
541 pub name: Option<String>,
542 #[serde(skip_serializing_if = "Option::is_none")]
543 pub hidden: Option<bool>,
544 #[serde(skip_serializing_if = "Option::is_none")]
545 pub master_id: Option<String>,
546 #[serde(skip_serializing_if = "Option::is_none")]
547 pub layout_id: Option<String>,
548 #[serde(skip_serializing_if = "Option::is_none")]
549 pub background: Option<FillStyle>,
550 pub nodes: Vec<SlideNode>,
551 #[serde(skip_serializing_if = "Vec::is_empty")]
552 pub notes: Vec<SlideNote>,
553 #[serde(skip_serializing_if = "Vec::is_empty")]
554 pub comments: Vec<SlideComment>,
555 #[serde(skip_serializing_if = "Option::is_none")]
556 pub source_part: Option<String>,
557 pub warnings: Vec<PresentationWarning>,
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct SlideNote {
562 pub text: String,
563}
564
565#[derive(Debug, Clone, Serialize, Deserialize)]
566#[serde(rename_all = "camelCase")]
567pub struct SlideComment {
568 pub id: String,
569 pub text: String,
570 #[serde(skip_serializing_if = "Option::is_none")]
571 pub author: Option<String>,
572 #[serde(skip_serializing_if = "Option::is_none")]
573 pub created_at: Option<String>,
574 #[serde(skip_serializing_if = "Option::is_none")]
575 pub x: Option<f64>,
576 #[serde(skip_serializing_if = "Option::is_none")]
577 pub y: Option<f64>,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
581#[serde(rename_all = "camelCase")]
582pub struct PresentationDocument {
583 pub format: PresentationFormat,
584 pub size: PresentationSize,
585 pub slides: Vec<PresentationSlide>,
586 pub masters: Vec<SlideMaster>,
587 pub layouts: Vec<SlideLayout>,
588 pub themes: Vec<PresentationTheme>,
589 pub assets: BTreeMap<String, PresentationAsset>,
590 #[serde(default, skip_serializing_if = "Vec::is_empty")]
591 pub embedded_fonts: Vec<PresentationEmbeddedFont>,
592 pub warnings: Vec<PresentationWarning>,
593 #[serde(skip_serializing_if = "Option::is_none")]
594 pub metadata: Option<PresentationMetadata>,
595}