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