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 shape_svg: Option<String>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 paint_bounds: Option<rust_office_art::PaintBounds>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 source_xml: Option<String>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 fill: Option<FillStyle>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 line: Option<LineStyle>,
121 paragraphs: Vec<TextParagraph>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 vertical_alignment: Option<VerticalAlignment>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 text_insets: Option<TextInsets>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 autofit: Option<TextAutofit>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 text_rotation: Option<f64>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 vertical_text: Option<String>,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 horizontal_overflow: Option<String>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 vertical_overflow: Option<String>,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 text_wrap: Option<String>,
138 #[serde(skip_serializing_if = "Option::is_none")]
139 column_count: Option<usize>,
140 #[serde(skip_serializing_if = "Option::is_none")]
141 column_spacing: Option<i64>,
142 #[serde(skip_serializing_if = "Option::is_none")]
143 right_to_left_columns: Option<bool>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 space_first_last_paragraph: Option<bool>,
146 },
147 Image {
148 id: String,
149 name: String,
150 transform: Transform,
151 asset_id: String,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 crop: Option<ImageCrop>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 opacity: Option<f64>,
156 preserve_aspect_ratio: bool,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 effects: Option<ImageEffects>,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 picture_svg: Option<String>,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 paint_bounds: Option<rust_office_art::PaintBounds>,
163 #[serde(skip_serializing_if = "Option::is_none")]
164 source_xml: Option<String>,
165 },
166 Group {
167 id: String,
168 name: String,
169 transform: Transform,
170 children: Vec<SlideNode>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 child_transform: Option<Transform>,
173 },
174 Table {
175 id: String,
176 name: String,
177 transform: Transform,
178 rows: Vec<Vec<TableCell>>,
179 #[serde(skip_serializing_if = "Vec::is_empty")]
180 column_widths: Vec<i64>,
181 #[serde(skip_serializing_if = "Vec::is_empty")]
182 row_heights: Vec<i64>,
183 },
184 Chart {
185 id: String,
186 name: String,
187 transform: Transform,
188 chart_type: String,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 title: Option<String>,
191 series: Vec<ChartSeries>,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 has_legend: Option<bool>,
194 #[serde(skip_serializing_if = "Option::is_none")]
197 chart_xml: Option<String>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 chart_svg: Option<String>,
201 #[serde(skip_serializing_if = "Option::is_none")]
203 chart_style_xml: Option<String>,
204 #[serde(skip_serializing_if = "Option::is_none")]
206 chart_colors_xml: Option<String>,
207 },
208 Unknown {
209 id: String,
210 name: String,
211 transform: Transform,
212 feature: String,
213 },
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct TableCell {
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub row_span: Option<usize>,
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub col_span: Option<usize>,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub fill: Option<FillStyle>,
225 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
226 pub borders: BTreeMap<String, LineStyle>,
227 pub paragraphs: Vec<TextParagraph>,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub text_insets: Option<TextInsets>,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub vertical_alignment: Option<VerticalAlignment>,
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub text_rotation: Option<f64>,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct TextInsets {
239 pub top: i64,
240 pub right: i64,
241 pub bottom: i64,
242 pub left: i64,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct ImageCrop {
248 pub top: f64,
249 pub right: f64,
250 pub bottom: f64,
251 pub left: f64,
252}
253
254#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
256#[serde(rename_all = "camelCase")]
257pub struct ImageEffects {
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub bi_level_threshold: Option<f64>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub grayscale: Option<bool>,
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub duotone: Option<Vec<ColorValue>>,
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub brightness: Option<f64>,
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub contrast: Option<f64>,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
276#[serde(rename_all = "camelCase")]
277pub struct ChartSeries {
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub name: Option<String>,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 pub categories: Option<Vec<serde_json::Value>>,
282 pub values: Vec<Option<f64>>,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub color: Option<ColorValue>,
285}
286
287#[derive(Debug, Clone, Default, Serialize, Deserialize)]
288pub struct ShapeGeometry {
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub preset: Option<String>,
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub path: Option<String>,
294}
295
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297#[serde(rename_all = "camelCase")]
298pub struct ColorValue {
299 pub value: String,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 pub alpha: Option<f64>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct GradientStop {
307 pub position: f64,
308 pub color: ColorValue,
309}
310
311#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
312#[serde(rename_all = "lowercase")]
313pub enum FillImageMode {
314 Stretch,
315 Tile,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
319#[serde(
320 tag = "type",
321 rename_all = "lowercase",
322 rename_all_fields = "camelCase"
323)]
324pub enum FillStyle {
325 None,
326 Solid {
327 color: ColorValue,
328 },
329 Gradient {
330 #[serde(skip_serializing_if = "Option::is_none")]
331 angle: Option<f64>,
332 stops: Vec<GradientStop>,
333 },
334 Pattern {
335 preset: String,
336 foreground: ColorValue,
337 background: ColorValue,
338 },
339 Image {
340 asset_id: String,
341 mode: FillImageMode,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 crop: Option<ImageCrop>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 opacity: Option<f64>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 effects: Option<ImageEffects>,
348 },
349}
350
351#[derive(Debug, Clone, Default, Serialize, Deserialize)]
352#[serde(rename_all = "camelCase")]
353pub struct LineStyle {
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub color: Option<ColorValue>,
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub width: Option<f64>,
358 #[serde(skip_serializing_if = "Option::is_none")]
359 pub dash: Option<String>,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub start_arrow: Option<String>,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 pub end_arrow: Option<String>,
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
367#[serde(rename_all = "lowercase")]
368pub enum VerticalAlignment {
369 Top,
370 Middle,
371 Bottom,
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
375#[serde(rename_all = "lowercase")]
376pub enum TextAutoFitMode {
377 None,
378 Normal,
379 Shape,
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub struct TextAutofit {
385 pub mode: TextAutoFitMode,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 pub font_scale: Option<f64>,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 pub line_spacing_reduction: Option<f64>,
390}
391
392#[derive(Debug, Clone, Default, Serialize, Deserialize)]
393#[serde(rename_all = "camelCase")]
394pub struct TextParagraph {
395 pub runs: Vec<TextRun>,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub alignment: Option<String>,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub level: Option<usize>,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 pub bullet: Option<TextBullet>,
402 #[serde(skip_serializing_if = "Option::is_none")]
403 pub line_spacing: Option<TextSpacing>,
404 #[serde(skip_serializing_if = "Option::is_none")]
405 pub space_before: Option<TextSpacing>,
406 #[serde(skip_serializing_if = "Option::is_none")]
407 pub space_after: Option<TextSpacing>,
408 #[serde(skip_serializing_if = "Option::is_none")]
409 pub rtl: Option<bool>,
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub margin_left_emu: Option<i64>,
412 #[serde(skip_serializing_if = "Option::is_none")]
413 pub indent_emu: Option<i64>,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 pub default_tab_size_emu: Option<i64>,
416 #[serde(skip_serializing_if = "Option::is_none")]
417 pub tab_stops: Option<Vec<TextTabStop>>,
418 #[serde(skip_serializing_if = "Option::is_none")]
419 pub east_asian_line_break: Option<bool>,
420 #[serde(skip_serializing_if = "Option::is_none")]
421 pub latin_line_break: Option<bool>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 pub hanging_punctuation: Option<bool>,
424}
425
426#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
427#[serde(rename_all = "lowercase")]
428pub enum TextTabAlignment {
429 Left,
430 Center,
431 Right,
432 Decimal,
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
436#[serde(rename_all = "camelCase")]
437pub struct TextTabStop {
438 pub position_emu: i64,
439 pub alignment: TextTabAlignment,
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
443#[serde(rename_all = "camelCase")]
444pub struct TextSpacing {
445 pub value: f64,
446 pub unit: TextSpacingUnit,
447}
448
449#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
450#[serde(rename_all = "lowercase")]
451pub enum TextSpacingUnit {
452 Points,
453 Percent,
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize)]
457#[serde(rename_all = "camelCase")]
458pub struct TextBullet {
459 pub kind: String,
460 #[serde(skip_serializing_if = "Option::is_none")]
461 pub value: Option<String>,
462 #[serde(skip_serializing_if = "Option::is_none")]
463 pub font_family: Option<String>,
464 #[serde(skip_serializing_if = "Option::is_none")]
465 pub font_size_pt: Option<f64>,
466 #[serde(skip_serializing_if = "Option::is_none")]
467 pub size_percent: Option<f64>,
468 #[serde(skip_serializing_if = "Option::is_none")]
469 pub start_at: Option<usize>,
470}
471
472#[derive(Debug, Clone, Default, Serialize, Deserialize)]
473#[serde(rename_all = "camelCase")]
474pub struct TextRun {
475 pub text: String,
476 #[serde(skip_serializing_if = "Option::is_none")]
477 pub font_family: Option<String>,
478 #[serde(skip_serializing_if = "Option::is_none")]
479 pub east_asian_font_family: Option<String>,
480 #[serde(skip_serializing_if = "Option::is_none")]
481 pub complex_script_font_family: Option<String>,
482 #[serde(skip_serializing_if = "Option::is_none")]
483 pub symbol_font_family: Option<String>,
484 #[serde(skip_serializing_if = "Option::is_none")]
485 pub font_size_pt: Option<f64>,
486 #[serde(skip_serializing_if = "Option::is_none")]
487 pub bold: Option<bool>,
488 #[serde(skip_serializing_if = "Option::is_none")]
489 pub italic: Option<bool>,
490 #[serde(skip_serializing_if = "Option::is_none")]
491 pub underline: Option<bool>,
492 #[serde(skip_serializing_if = "Option::is_none")]
493 pub strike: Option<bool>,
494 #[serde(skip_serializing_if = "Option::is_none")]
495 pub color: Option<ColorValue>,
496 #[serde(skip_serializing_if = "Option::is_none")]
497 pub baseline: Option<f64>,
498 #[serde(skip_serializing_if = "Option::is_none")]
499 pub language: Option<String>,
500 #[serde(skip_serializing_if = "Option::is_none")]
501 pub alternative_language: Option<String>,
502 #[serde(skip_serializing_if = "Option::is_none")]
503 pub right_to_left: Option<bool>,
504 #[serde(skip_serializing_if = "Option::is_none")]
505 pub hyperlink: Option<String>,
506 #[serde(skip_serializing_if = "Option::is_none")]
507 pub character_spacing_pt: Option<f64>,
508 #[serde(skip_serializing_if = "Option::is_none")]
509 pub kerning_threshold_pt: Option<f64>,
510}
511
512#[derive(Debug, Clone, Serialize, Deserialize)]
513#[serde(rename_all = "camelCase")]
514pub struct PresentationTheme {
515 pub id: String,
516 #[serde(skip_serializing_if = "Option::is_none")]
517 pub name: Option<String>,
518 pub colors: BTreeMap<String, String>,
519 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
520 pub major_fonts: BTreeMap<String, String>,
521 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
522 pub minor_fonts: BTreeMap<String, String>,
523 #[serde(skip_serializing_if = "Option::is_none")]
524 pub format_scheme_xml: Option<String>,
525}
526
527#[derive(Debug, Clone, Serialize, Deserialize)]
528#[serde(rename_all = "camelCase")]
529pub struct SlideMaster {
530 pub id: String,
531 #[serde(skip_serializing_if = "Option::is_none")]
532 pub name: Option<String>,
533 #[serde(skip_serializing_if = "Option::is_none")]
534 pub theme_id: Option<String>,
535 pub nodes: Vec<SlideNode>,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize)]
539#[serde(rename_all = "camelCase")]
540pub struct SlideLayout {
541 pub id: String,
542 #[serde(skip_serializing_if = "Option::is_none")]
543 pub name: Option<String>,
544 #[serde(skip_serializing_if = "Option::is_none")]
545 pub master_id: Option<String>,
546 pub nodes: Vec<SlideNode>,
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize)]
550#[serde(rename_all = "camelCase")]
551pub struct PresentationSlide {
552 pub id: String,
553 pub index: usize,
554 #[serde(skip_serializing_if = "Option::is_none")]
555 pub name: Option<String>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 pub hidden: Option<bool>,
558 #[serde(skip_serializing_if = "Option::is_none")]
559 pub master_id: Option<String>,
560 #[serde(skip_serializing_if = "Option::is_none")]
561 pub layout_id: Option<String>,
562 #[serde(skip_serializing_if = "Option::is_none")]
563 pub background: Option<FillStyle>,
564 pub nodes: Vec<SlideNode>,
565 #[serde(skip_serializing_if = "Vec::is_empty")]
566 pub notes: Vec<SlideNote>,
567 #[serde(skip_serializing_if = "Vec::is_empty")]
568 pub comments: Vec<SlideComment>,
569 #[serde(skip_serializing_if = "Option::is_none")]
570 pub source_part: Option<String>,
571 pub warnings: Vec<PresentationWarning>,
572}
573
574#[derive(Debug, Clone, Serialize, Deserialize)]
575pub struct SlideNote {
576 pub text: String,
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize)]
580#[serde(rename_all = "camelCase")]
581pub struct SlideComment {
582 pub id: String,
583 pub text: String,
584 #[serde(skip_serializing_if = "Option::is_none")]
585 pub author: Option<String>,
586 #[serde(skip_serializing_if = "Option::is_none")]
587 pub created_at: Option<String>,
588 #[serde(skip_serializing_if = "Option::is_none")]
589 pub x: Option<f64>,
590 #[serde(skip_serializing_if = "Option::is_none")]
591 pub y: Option<f64>,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize)]
595#[serde(rename_all = "camelCase")]
596pub struct PresentationDocument {
597 pub format: PresentationFormat,
598 pub size: PresentationSize,
599 pub slides: Vec<PresentationSlide>,
600 pub masters: Vec<SlideMaster>,
601 pub layouts: Vec<SlideLayout>,
602 pub themes: Vec<PresentationTheme>,
603 pub assets: BTreeMap<String, PresentationAsset>,
604 #[serde(default, skip_serializing_if = "Vec::is_empty")]
605 pub embedded_fonts: Vec<PresentationEmbeddedFont>,
606 pub warnings: Vec<PresentationWarning>,
607 #[serde(skip_serializing_if = "Option::is_none")]
608 pub metadata: Option<PresentationMetadata>,
609}