Skip to main content

rust_pptx/
model.rs

1use std::collections::BTreeMap;
2
3use crate::{SlideTiming, SlideTransition};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct PictureArtwork {
9    pub svg: String,
10    #[serde(default, skip_serializing_if = "Option::is_none")]
11    pub paint_bounds: Option<rust_office_art::PaintBounds>,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum PresentationFormat {
17    Pptx,
18    Ppt,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct PresentationSize {
24    pub width_emu: u64,
25    pub height_emu: u64,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct PresentationWarning {
31    pub code: String,
32    pub message: String,
33    pub severity: String,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub slide_index: Option<usize>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub node_id: Option<String>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub part_name: Option<String>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub feature: Option<String>,
42}
43
44impl PresentationWarning {
45    pub fn warning(code: &str, message: impl Into<String>) -> Self {
46        Self {
47            code: code.into(),
48            message: message.into(),
49            severity: "warning".into(),
50            slide_index: None,
51            node_id: None,
52            part_name: None,
53            feature: None,
54        }
55    }
56}
57
58#[derive(Debug, Clone, Default, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct PresentationMetadata {
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub title: Option<String>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub subject: Option<String>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub creator: Option<String>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub company: Option<String>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct PresentationAsset {
74    pub id: String,
75    pub content_type: String,
76    pub byte_length: usize,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub file_name: Option<String>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub data: Option<serde_bytes::ByteBuf>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct PresentationEmbeddedFont {
86    pub family: String,
87    pub asset_id: String,
88    pub style: String,
89    pub weight: String,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct Transform {
95    pub x: i64,
96    pub y: i64,
97    pub width: i64,
98    pub height: i64,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub rotation: Option<f64>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub flip_horizontal: Option<bool>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub flip_vertical: Option<bool>,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(
109    tag = "type",
110    rename_all = "kebab-case",
111    rename_all_fields = "camelCase"
112)]
113#[allow(clippy::large_enum_variant)]
114pub enum SlideNode {
115    Shape {
116        #[serde(flatten, default)]
117        metadata: Option<Box<NodeMetadata>>,
118        id: String,
119        name: String,
120        transform: Transform,
121        geometry: ShapeGeometry,
122        #[serde(skip_serializing_if = "Option::is_none")]
123        shape_svg: Option<String>,
124        #[serde(skip_serializing_if = "Option::is_none")]
125        paint_bounds: Option<rust_office_art::PaintBounds>,
126        #[serde(skip_serializing_if = "Option::is_none")]
127        source_xml: Option<String>,
128        #[serde(skip_serializing_if = "Option::is_none")]
129        fill: Option<FillStyle>,
130        #[serde(skip_serializing_if = "Option::is_none")]
131        line: Option<LineStyle>,
132        paragraphs: Vec<TextParagraph>,
133        #[serde(skip_serializing_if = "Option::is_none")]
134        vertical_alignment: Option<VerticalAlignment>,
135        #[serde(skip_serializing_if = "Option::is_none")]
136        text_insets: Option<TextInsets>,
137        #[serde(skip_serializing_if = "Option::is_none")]
138        autofit: Option<TextAutofit>,
139        #[serde(skip_serializing_if = "Option::is_none")]
140        text_rotation: Option<f64>,
141        #[serde(default, skip_serializing_if = "Option::is_none")]
142        text_transform: Option<Transform>,
143        #[serde(skip_serializing_if = "Option::is_none")]
144        vertical_text: Option<String>,
145        #[serde(skip_serializing_if = "Option::is_none")]
146        horizontal_overflow: Option<String>,
147        #[serde(skip_serializing_if = "Option::is_none")]
148        vertical_overflow: Option<String>,
149        #[serde(skip_serializing_if = "Option::is_none")]
150        text_wrap: Option<String>,
151        #[serde(skip_serializing_if = "Option::is_none")]
152        column_count: Option<usize>,
153        #[serde(skip_serializing_if = "Option::is_none")]
154        column_spacing: Option<i64>,
155        #[serde(skip_serializing_if = "Option::is_none")]
156        right_to_left_columns: Option<bool>,
157        #[serde(skip_serializing_if = "Option::is_none")]
158        space_first_last_paragraph: Option<bool>,
159    },
160    Image {
161        #[serde(flatten, default)]
162        metadata: Option<Box<NodeMetadata>>,
163        #[serde(default, rename = "model3d", skip_serializing_if = "Option::is_none")]
164        model_3d: Option<Box<rust_office_art::model3d::Model3D>>,
165        #[serde(default, skip_serializing_if = "Option::is_none")]
166        ink: Option<Box<InkContent>>,
167        id: String,
168        name: String,
169        transform: Transform,
170        asset_id: String,
171        #[serde(default, skip_serializing_if = "Option::is_none")]
172        fallback_asset_id: Option<String>,
173        #[serde(skip_serializing_if = "Option::is_none")]
174        crop: Option<ImageCrop>,
175        #[serde(skip_serializing_if = "Option::is_none")]
176        opacity: Option<f64>,
177        preserve_aspect_ratio: bool,
178        #[serde(skip_serializing_if = "Option::is_none")]
179        effects: Option<ImageEffects>,
180        #[serde(skip_serializing_if = "Option::is_none")]
181        picture_svg: Option<String>,
182        #[serde(default, skip_serializing_if = "Option::is_none")]
183        fallback_picture: Option<Box<PictureArtwork>>,
184        #[serde(skip_serializing_if = "Option::is_none")]
185        paint_bounds: Option<rust_office_art::PaintBounds>,
186        #[serde(skip_serializing_if = "Option::is_none")]
187        source_xml: Option<String>,
188    },
189    Group {
190        #[serde(flatten, default)]
191        metadata: Option<Box<NodeMetadata>>,
192        id: String,
193        name: String,
194        transform: Transform,
195        children: Vec<SlideNode>,
196        #[serde(skip_serializing_if = "Option::is_none")]
197        child_transform: Option<Transform>,
198    },
199    Table {
200        #[serde(flatten, default)]
201        metadata: Option<Box<NodeMetadata>>,
202        id: String,
203        name: String,
204        transform: Transform,
205        rows: Vec<Vec<TableCell>>,
206        #[serde(skip_serializing_if = "Vec::is_empty")]
207        column_widths: Vec<i64>,
208        #[serde(skip_serializing_if = "Vec::is_empty")]
209        row_heights: Vec<i64>,
210    },
211    Chart {
212        #[serde(flatten, default)]
213        metadata: Option<Box<NodeMetadata>>,
214        id: String,
215        name: String,
216        transform: Transform,
217        chart_type: String,
218        #[serde(skip_serializing_if = "Option::is_none")]
219        title: Option<String>,
220        series: Vec<ChartSeries>,
221        #[serde(skip_serializing_if = "Option::is_none")]
222        has_legend: Option<bool>,
223        /// Raw DrawingML chart part XML (`c:chartSpace` or `cx:chartSpace`) so
224        /// viewers can render with full PowerPoint fidelity.
225        #[serde(skip_serializing_if = "Option::is_none")]
226        chart_xml: Option<String>,
227        /// Chart artwork at the dimensions of the slide frame.
228        #[serde(skip_serializing_if = "Option::is_none")]
229        chart_svg: Option<String>,
230        /// Optional companion chart style definition.
231        #[serde(skip_serializing_if = "Option::is_none")]
232        chart_style_xml: Option<String>,
233        /// Optional companion chart color style definition.
234        #[serde(skip_serializing_if = "Option::is_none")]
235        chart_colors_xml: Option<String>,
236    },
237    Media {
238        #[serde(flatten, default)]
239        metadata: Option<Box<NodeMetadata>>,
240        id: String,
241        name: String,
242        transform: Transform,
243        media_type: MediaType,
244        #[serde(skip_serializing_if = "Option::is_none")]
245        asset_id: Option<String>,
246        #[serde(skip_serializing_if = "Option::is_none")]
247        source_url: Option<String>,
248        #[serde(skip_serializing_if = "Option::is_none")]
249        poster_asset_id: Option<String>,
250        #[serde(skip_serializing_if = "Option::is_none")]
251        poster: Option<Box<SlideNode>>,
252        #[serde(skip_serializing_if = "Option::is_none")]
253        playback: Option<MediaPlayback>,
254        #[serde(skip_serializing_if = "Option::is_none")]
255        embedded_object: Option<EmbeddedObject>,
256        #[serde(skip_serializing_if = "Option::is_none")]
257        source_xml: Option<String>,
258    },
259    Unknown {
260        #[serde(flatten, default)]
261        metadata: Option<Box<NodeMetadata>>,
262        id: String,
263        name: String,
264        transform: Transform,
265        feature: String,
266    },
267}
268
269impl SlideNode {
270    pub fn metadata_mut(&mut self) -> &mut NodeMetadata {
271        match self {
272            Self::Shape { metadata, .. }
273            | Self::Image { metadata, .. }
274            | Self::Group { metadata, .. }
275            | Self::Table { metadata, .. }
276            | Self::Chart { metadata, .. }
277            | Self::Media { metadata, .. }
278            | Self::Unknown { metadata, .. } => metadata.get_or_insert_with(Default::default),
279        }
280    }
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub struct InkContent {
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub asset_id: Option<String>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub source_xml: Option<String>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub drawing: Option<Box<rust_office_art::InkDrawing>>,
292}
293
294#[derive(Debug, Clone, Default, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub struct NodeMetadata {
297    #[serde(default, skip_serializing_if = "Option::is_none")]
298    pub editing: Option<Box<ObjectEditing>>,
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub source_id: Option<String>,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub hidden: Option<bool>,
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub alt_text: Option<String>,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub accessibility_title: Option<String>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub description: Option<String>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub decorative: Option<bool>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub hyperlink: Option<String>,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub click_action: Option<Box<ObjectAction>>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub hover_action: Option<Box<ObjectAction>>,
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub source_part: Option<String>,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
322#[serde(rename_all = "camelCase")]
323pub struct ObjectEditing {
324    pub kind: ObjectEditingKind,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub locks: Option<ObjectLocks>,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub connector: Option<ConnectorConnections>,
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub text_box: Option<bool>,
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub prefer_relative_resize: Option<bool>,
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub is_comment: Option<bool>,
335    pub source_xml: String,
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub enum ObjectEditingKind {
341    Shape,
342    Connector,
343    Picture,
344    Group,
345    GraphicFrame,
346    ContentPart,
347}
348
349#[derive(Debug, Clone, Default, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct ObjectLocks {
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub no_grouping: Option<bool>,
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub no_ungrouping: Option<bool>,
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub no_selection: Option<bool>,
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub no_rotation: Option<bool>,
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub no_change_aspect: Option<bool>,
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub no_move: Option<bool>,
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub no_resize: Option<bool>,
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub no_edit_points: Option<bool>,
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub no_adjust_handles: Option<bool>,
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub no_change_arrowheads: Option<bool>,
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub no_change_shape_type: Option<bool>,
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub no_text_edit: Option<bool>,
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub no_crop: Option<bool>,
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub no_drilldown: Option<bool>,
380}
381
382#[derive(Debug, Clone, Default, Serialize, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub struct ConnectorConnections {
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub start: Option<ConnectorEndpoint>,
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub end: Option<ConnectorEndpoint>,
389}
390
391#[derive(Debug, Clone, Serialize, Deserialize)]
392#[serde(rename_all = "camelCase")]
393pub struct ConnectorEndpoint {
394    pub shape_id: u32,
395    pub connection_site: u32,
396}
397
398#[derive(Debug, Clone, Default, Serialize, Deserialize)]
399#[serde(rename_all = "camelCase")]
400pub struct ObjectAction {
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub external: Option<bool>,
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub relationship_id: Option<String>,
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub target: Option<String>,
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub action: Option<String>,
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub tooltip: Option<String>,
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub target_frame: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub invalid_url: Option<String>,
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub history: Option<bool>,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub highlight_click: Option<bool>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub end_sound: Option<bool>,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub sound_asset_id: Option<String>,
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub sound_name: Option<String>,
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub sound_builtin: Option<bool>,
427}
428
429#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
430#[serde(rename_all = "lowercase")]
431pub enum MediaType {
432    Audio,
433    Video,
434    Ole,
435    Control,
436    Unknown,
437}
438
439#[derive(Debug, Clone, Default, Serialize, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct MediaPlayback {
442    #[serde(skip_serializing_if = "Option::is_none")]
443    pub trim_start_ms: Option<f64>,
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub trim_end_ms: Option<f64>,
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub fade_in_ms: Option<f64>,
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub fade_out_ms: Option<f64>,
450    #[serde(default, skip_serializing_if = "Vec::is_empty")]
451    pub bookmarks: Vec<MediaBookmark>,
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize)]
455#[serde(rename_all = "camelCase")]
456pub struct MediaBookmark {
457    pub name: String,
458    pub time_ms: f64,
459}
460
461#[derive(Debug, Clone, Default, Serialize, Deserialize)]
462#[serde(rename_all = "camelCase")]
463pub struct EmbeddedObject {
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub control: Option<Box<EmbeddedControl>>,
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub program_id: Option<String>,
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub name: Option<String>,
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub relationship_id: Option<String>,
472    pub linked: bool,
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub show_as_icon: Option<bool>,
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub preview_width_emu: Option<u64>,
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub preview_height_emu: Option<u64>,
479    #[serde(skip_serializing_if = "Option::is_none")]
480    pub follow_color_scheme: Option<String>,
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub update_automatic: Option<bool>,
483}
484
485#[derive(Debug, Clone, Default, Serialize, Deserialize)]
486#[serde(rename_all = "camelCase")]
487pub struct EmbeddedControl {
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub class_id: Option<String>,
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub persistence: Option<String>,
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub source_xml: Option<String>,
494    #[serde(default, skip_serializing_if = "Vec::is_empty")]
495    pub binary_asset_ids: Vec<String>,
496    #[serde(default, skip_serializing_if = "Vec::is_empty")]
497    pub properties: Vec<ControlProperty>,
498}
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
501#[serde(rename_all = "camelCase")]
502pub struct ControlProperty {
503    pub name: String,
504    #[serde(skip_serializing_if = "Option::is_none")]
505    pub value: Option<String>,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize)]
509#[serde(rename_all = "camelCase")]
510pub struct TableCell {
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub merge: Option<Box<TableCellMerge>>,
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub row_span: Option<usize>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub col_span: Option<usize>,
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub fill: Option<FillStyle>,
519    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
520    pub borders: BTreeMap<String, LineStyle>,
521    pub paragraphs: Vec<TextParagraph>,
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub text_insets: Option<TextInsets>,
524    #[serde(skip_serializing_if = "Option::is_none")]
525    pub vertical_alignment: Option<VerticalAlignment>,
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub text_rotation: Option<f64>,
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
531#[serde(rename_all = "camelCase")]
532pub struct TableCellMerge {
533    pub horizontal: bool,
534    pub vertical: bool,
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub origin: Option<TableCellPosition>,
537}
538
539#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
540pub struct TableCellPosition {
541    pub row: usize,
542    pub column: usize,
543}
544
545#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
546#[serde(rename_all = "camelCase")]
547pub struct TextInsets {
548    pub top: i64,
549    pub right: i64,
550    pub bottom: i64,
551    pub left: i64,
552}
553
554#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
555#[serde(rename_all = "camelCase")]
556pub struct ImageCrop {
557    pub top: f64,
558    pub right: f64,
559    pub bottom: f64,
560    pub left: f64,
561}
562
563/// DrawingML picture color effects applied to an `a:blip`.
564#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
565#[serde(rename_all = "camelCase")]
566pub struct ImageEffects {
567    /// `a:biLevel` black/white threshold, normalized to 0..1.
568    #[serde(skip_serializing_if = "Option::is_none")]
569    pub bi_level_threshold: Option<f64>,
570    /// `a:grayscl` grayscale recolor.
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub grayscale: Option<bool>,
573    /// `a:duotone` two-color ramp, dark then light.
574    #[serde(skip_serializing_if = "Option::is_none")]
575    pub duotone: Option<Vec<ColorValue>>,
576    /// `a:lum` brightness, normalized to -1..1.
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub brightness: Option<f64>,
579    /// `a:lum` contrast, normalized to -1..1.
580    #[serde(skip_serializing_if = "Option::is_none")]
581    pub contrast: Option<f64>,
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize)]
585#[serde(rename_all = "camelCase")]
586pub struct ChartSeries {
587    #[serde(skip_serializing_if = "Option::is_none")]
588    pub name: Option<String>,
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub categories: Option<Vec<serde_json::Value>>,
591    pub values: Vec<Option<f64>>,
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub color: Option<ColorValue>,
594}
595
596#[derive(Debug, Clone, Default, Serialize, Deserialize)]
597pub struct ShapeGeometry {
598    #[serde(skip_serializing_if = "Option::is_none")]
599    pub preset: Option<String>,
600    /// SVG path data normalized to a 0..1 view box for DrawingML custom geometry.
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub path: Option<String>,
603}
604
605#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
606#[serde(rename_all = "camelCase")]
607pub struct ColorValue {
608    pub value: String,
609    #[serde(skip_serializing_if = "Option::is_none")]
610    pub alpha: Option<f64>,
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize)]
614#[serde(rename_all = "camelCase")]
615pub struct GradientStop {
616    pub position: f64,
617    pub color: ColorValue,
618}
619
620#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
621#[serde(rename_all = "lowercase")]
622pub enum FillImageMode {
623    Stretch,
624    Tile,
625}
626
627#[derive(Debug, Clone, Serialize, Deserialize)]
628#[serde(
629    tag = "type",
630    rename_all = "lowercase",
631    rename_all_fields = "camelCase"
632)]
633pub enum FillStyle {
634    None,
635    Solid {
636        color: ColorValue,
637    },
638    Gradient {
639        #[serde(skip_serializing_if = "Option::is_none")]
640        angle: Option<f64>,
641        stops: Vec<GradientStop>,
642    },
643    Pattern {
644        preset: String,
645        foreground: ColorValue,
646        background: ColorValue,
647    },
648    Image {
649        asset_id: String,
650        mode: FillImageMode,
651        #[serde(skip_serializing_if = "Option::is_none")]
652        crop: Option<ImageCrop>,
653        #[serde(skip_serializing_if = "Option::is_none")]
654        opacity: Option<f64>,
655        #[serde(skip_serializing_if = "Option::is_none")]
656        effects: Option<ImageEffects>,
657    },
658}
659
660#[derive(Debug, Clone, Default, Serialize, Deserialize)]
661#[serde(rename_all = "camelCase")]
662pub struct LineStyle {
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub color: Option<ColorValue>,
665    #[serde(skip_serializing_if = "Option::is_none")]
666    pub width: Option<f64>,
667    #[serde(skip_serializing_if = "Option::is_none")]
668    pub dash: Option<String>,
669    #[serde(skip_serializing_if = "Option::is_none")]
670    pub start_arrow: Option<String>,
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub end_arrow: Option<String>,
673}
674
675#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
676#[serde(rename_all = "lowercase")]
677pub enum VerticalAlignment {
678    Top,
679    Middle,
680    Bottom,
681}
682
683#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
684#[serde(rename_all = "lowercase")]
685pub enum TextAutoFitMode {
686    None,
687    Normal,
688    Shape,
689}
690
691#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
692#[serde(rename_all = "camelCase")]
693pub struct TextAutofit {
694    pub mode: TextAutoFitMode,
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub font_scale: Option<f64>,
697    #[serde(skip_serializing_if = "Option::is_none")]
698    pub line_spacing_reduction: Option<f64>,
699}
700
701#[derive(Debug, Clone, Default, Serialize, Deserialize)]
702#[serde(rename_all = "camelCase")]
703pub struct TextParagraph {
704    pub runs: Vec<TextRun>,
705    #[serde(skip_serializing_if = "Option::is_none")]
706    pub alignment: Option<String>,
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub level: Option<usize>,
709    #[serde(skip_serializing_if = "Option::is_none")]
710    pub bullet: Option<TextBullet>,
711    #[serde(skip_serializing_if = "Option::is_none")]
712    pub line_spacing: Option<TextSpacing>,
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub space_before: Option<TextSpacing>,
715    #[serde(skip_serializing_if = "Option::is_none")]
716    pub space_after: Option<TextSpacing>,
717    #[serde(skip_serializing_if = "Option::is_none")]
718    pub rtl: Option<bool>,
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub margin_left_emu: Option<i64>,
721    #[serde(skip_serializing_if = "Option::is_none")]
722    pub indent_emu: Option<i64>,
723    #[serde(skip_serializing_if = "Option::is_none")]
724    pub default_tab_size_emu: Option<i64>,
725    #[serde(skip_serializing_if = "Option::is_none")]
726    pub tab_stops: Option<Vec<TextTabStop>>,
727    #[serde(skip_serializing_if = "Option::is_none")]
728    pub east_asian_line_break: Option<bool>,
729    #[serde(skip_serializing_if = "Option::is_none")]
730    pub latin_line_break: Option<bool>,
731    #[serde(skip_serializing_if = "Option::is_none")]
732    pub hanging_punctuation: Option<bool>,
733}
734
735#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
736#[serde(rename_all = "lowercase")]
737pub enum TextTabAlignment {
738    Left,
739    Center,
740    Right,
741    Decimal,
742}
743
744#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
745#[serde(rename_all = "camelCase")]
746pub struct TextTabStop {
747    pub position_emu: i64,
748    pub alignment: TextTabAlignment,
749}
750
751#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
752#[serde(rename_all = "camelCase")]
753pub struct TextSpacing {
754    pub value: f64,
755    pub unit: TextSpacingUnit,
756}
757
758#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
759#[serde(rename_all = "lowercase")]
760pub enum TextSpacingUnit {
761    Points,
762    Percent,
763}
764
765#[derive(Debug, Clone, Serialize, Deserialize)]
766#[serde(rename_all = "camelCase")]
767pub struct TextBullet {
768    pub kind: String,
769    #[serde(skip_serializing_if = "Option::is_none")]
770    pub value: Option<String>,
771    #[serde(skip_serializing_if = "Option::is_none")]
772    pub font_family: Option<String>,
773    #[serde(skip_serializing_if = "Option::is_none")]
774    pub font_size_pt: Option<f64>,
775    #[serde(skip_serializing_if = "Option::is_none")]
776    pub size_percent: Option<f64>,
777    #[serde(skip_serializing_if = "Option::is_none")]
778    pub start_at: Option<usize>,
779}
780
781#[derive(Debug, Clone, Default, Serialize, Deserialize)]
782#[serde(rename_all = "camelCase")]
783pub struct TextRunProperties {
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub font_family: Option<String>,
786    #[serde(skip_serializing_if = "Option::is_none")]
787    pub east_asian_font_family: Option<String>,
788    #[serde(skip_serializing_if = "Option::is_none")]
789    pub complex_script_font_family: Option<String>,
790    #[serde(skip_serializing_if = "Option::is_none")]
791    pub symbol_font_family: Option<String>,
792    #[serde(skip_serializing_if = "Option::is_none")]
793    pub font_size_pt: Option<f64>,
794    #[serde(skip_serializing_if = "Option::is_none")]
795    pub bold: Option<bool>,
796    #[serde(skip_serializing_if = "Option::is_none")]
797    pub italic: Option<bool>,
798    #[serde(skip_serializing_if = "Option::is_none")]
799    pub underline: Option<bool>,
800    #[serde(skip_serializing_if = "Option::is_none")]
801    pub strike: Option<bool>,
802    #[serde(skip_serializing_if = "Option::is_none")]
803    pub color: Option<ColorValue>,
804    #[serde(skip_serializing_if = "Option::is_none")]
805    pub baseline: Option<f64>,
806    #[serde(skip_serializing_if = "Option::is_none")]
807    pub language: Option<String>,
808    #[serde(skip_serializing_if = "Option::is_none")]
809    pub alternative_language: Option<String>,
810    #[serde(skip_serializing_if = "Option::is_none")]
811    pub right_to_left: Option<bool>,
812    #[serde(skip_serializing_if = "Option::is_none")]
813    pub hyperlink: Option<String>,
814    #[serde(skip_serializing_if = "Option::is_none")]
815    pub character_spacing_pt: Option<f64>,
816    #[serde(skip_serializing_if = "Option::is_none")]
817    pub kerning_threshold_pt: Option<f64>,
818}
819
820#[derive(Debug, Clone, Default, Serialize, Deserialize)]
821#[serde(rename_all = "camelCase")]
822pub struct TextFieldParagraphProperties {
823    #[serde(skip_serializing_if = "Option::is_none")]
824    pub alignment: Option<String>,
825    #[serde(skip_serializing_if = "Option::is_none")]
826    pub level: Option<usize>,
827    #[serde(skip_serializing_if = "Option::is_none")]
828    pub bullet: Option<TextBullet>,
829    #[serde(skip_serializing_if = "Option::is_none")]
830    pub line_spacing: Option<TextSpacing>,
831    #[serde(skip_serializing_if = "Option::is_none")]
832    pub space_before: Option<TextSpacing>,
833    #[serde(skip_serializing_if = "Option::is_none")]
834    pub space_after: Option<TextSpacing>,
835    #[serde(skip_serializing_if = "Option::is_none")]
836    pub rtl: Option<bool>,
837    #[serde(skip_serializing_if = "Option::is_none")]
838    pub margin_left_emu: Option<i64>,
839    #[serde(skip_serializing_if = "Option::is_none")]
840    pub indent_emu: Option<i64>,
841    #[serde(skip_serializing_if = "Option::is_none")]
842    pub default_tab_size_emu: Option<i64>,
843    #[serde(skip_serializing_if = "Option::is_none")]
844    pub tab_stops: Option<Vec<TextTabStop>>,
845    #[serde(skip_serializing_if = "Option::is_none")]
846    pub east_asian_line_break: Option<bool>,
847    #[serde(skip_serializing_if = "Option::is_none")]
848    pub latin_line_break: Option<bool>,
849    #[serde(skip_serializing_if = "Option::is_none")]
850    pub hanging_punctuation: Option<bool>,
851    #[serde(skip_serializing_if = "Option::is_none")]
852    pub margin_right_emu: Option<i64>,
853    #[serde(skip_serializing_if = "Option::is_none")]
854    pub font_alignment: Option<String>,
855    #[serde(skip_serializing_if = "Option::is_none")]
856    pub bullet_none: Option<bool>,
857    #[serde(skip_serializing_if = "Option::is_none")]
858    pub bullet_color: Option<ColorValue>,
859    #[serde(skip_serializing_if = "Option::is_none")]
860    pub bullet_color_follow_text: Option<bool>,
861    #[serde(skip_serializing_if = "Option::is_none")]
862    pub bullet_font_follow_text: Option<bool>,
863    #[serde(skip_serializing_if = "Option::is_none")]
864    pub bullet_size_follow_text: Option<bool>,
865    #[serde(skip_serializing_if = "Option::is_none")]
866    pub bullet_image_asset_id: Option<String>,
867    #[serde(skip_serializing_if = "Option::is_none")]
868    pub default_run_properties: Option<TextRunProperties>,
869}
870
871#[derive(Debug, Clone, Serialize, Deserialize)]
872#[serde(rename_all = "camelCase")]
873pub struct TextField {
874    #[serde(skip_serializing_if = "Option::is_none")]
875    pub id: Option<String>,
876    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
877    pub field_type: Option<String>,
878    pub has_cached_text: bool,
879    #[serde(skip_serializing_if = "Option::is_none")]
880    pub paragraph_properties: Option<Box<TextFieldParagraphProperties>>,
881    #[serde(skip_serializing_if = "Option::is_none")]
882    pub source_part: Option<String>,
883    pub source_xml: String,
884}
885
886#[derive(Debug, Clone, Default, Serialize, Deserialize)]
887#[serde(rename_all = "camelCase")]
888pub struct TextRun {
889    pub text: String,
890    #[serde(default, skip_serializing_if = "Option::is_none")]
891    pub field: Option<Box<TextField>>,
892    #[serde(default, skip_serializing_if = "Option::is_none")]
893    pub math: Option<Box<rust_office_art::OfficeMath>>,
894    #[serde(skip_serializing_if = "Option::is_none")]
895    pub font_family: Option<String>,
896    #[serde(skip_serializing_if = "Option::is_none")]
897    pub east_asian_font_family: Option<String>,
898    #[serde(skip_serializing_if = "Option::is_none")]
899    pub complex_script_font_family: Option<String>,
900    #[serde(skip_serializing_if = "Option::is_none")]
901    pub symbol_font_family: Option<String>,
902    #[serde(skip_serializing_if = "Option::is_none")]
903    pub font_size_pt: Option<f64>,
904    #[serde(skip_serializing_if = "Option::is_none")]
905    pub bold: Option<bool>,
906    #[serde(skip_serializing_if = "Option::is_none")]
907    pub italic: Option<bool>,
908    #[serde(skip_serializing_if = "Option::is_none")]
909    pub underline: Option<bool>,
910    #[serde(skip_serializing_if = "Option::is_none")]
911    pub strike: Option<bool>,
912    #[serde(skip_serializing_if = "Option::is_none")]
913    pub color: Option<ColorValue>,
914    #[serde(skip_serializing_if = "Option::is_none")]
915    pub baseline: Option<f64>,
916    #[serde(skip_serializing_if = "Option::is_none")]
917    pub language: Option<String>,
918    #[serde(skip_serializing_if = "Option::is_none")]
919    pub alternative_language: Option<String>,
920    #[serde(skip_serializing_if = "Option::is_none")]
921    pub right_to_left: Option<bool>,
922    #[serde(skip_serializing_if = "Option::is_none")]
923    pub hyperlink: Option<String>,
924    #[serde(skip_serializing_if = "Option::is_none")]
925    pub character_spacing_pt: Option<f64>,
926    #[serde(skip_serializing_if = "Option::is_none")]
927    pub kerning_threshold_pt: Option<f64>,
928}
929
930#[derive(Debug, Clone, Serialize, Deserialize)]
931#[serde(rename_all = "camelCase")]
932pub struct PresentationTheme {
933    pub id: String,
934    #[serde(skip_serializing_if = "Option::is_none")]
935    pub name: Option<String>,
936    pub colors: BTreeMap<String, String>,
937    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
938    pub major_fonts: BTreeMap<String, String>,
939    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
940    pub minor_fonts: BTreeMap<String, String>,
941    #[serde(skip_serializing_if = "Option::is_none")]
942    pub format_scheme_xml: Option<String>,
943}
944
945#[derive(Debug, Clone, Serialize, Deserialize)]
946#[serde(rename_all = "camelCase")]
947pub struct SlideMaster {
948    pub id: String,
949    #[serde(skip_serializing_if = "Option::is_none")]
950    pub name: Option<String>,
951    #[serde(skip_serializing_if = "Option::is_none")]
952    pub theme_id: Option<String>,
953    #[serde(skip_serializing_if = "Option::is_none")]
954    pub transition: Option<Box<SlideTransition>>,
955    #[serde(skip_serializing_if = "Option::is_none")]
956    pub timing: Option<Box<SlideTiming>>,
957    pub nodes: Vec<SlideNode>,
958}
959
960#[derive(Debug, Clone, Serialize, Deserialize)]
961#[serde(rename_all = "camelCase")]
962pub struct SlideLayout {
963    pub id: String,
964    #[serde(skip_serializing_if = "Option::is_none")]
965    pub name: Option<String>,
966    #[serde(skip_serializing_if = "Option::is_none")]
967    pub master_id: Option<String>,
968    #[serde(skip_serializing_if = "Option::is_none")]
969    pub transition: Option<Box<SlideTransition>>,
970    #[serde(skip_serializing_if = "Option::is_none")]
971    pub timing: Option<Box<SlideTiming>>,
972    pub nodes: Vec<SlideNode>,
973}
974
975#[derive(Debug, Clone, Serialize, Deserialize)]
976#[serde(rename_all = "camelCase")]
977pub struct PresentationSlide {
978    pub id: String,
979    pub index: usize,
980    #[serde(skip_serializing_if = "Option::is_none")]
981    pub name: Option<String>,
982    #[serde(skip_serializing_if = "Option::is_none")]
983    pub hidden: Option<bool>,
984    #[serde(skip_serializing_if = "Option::is_none")]
985    pub master_id: Option<String>,
986    #[serde(skip_serializing_if = "Option::is_none")]
987    pub layout_id: Option<String>,
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub background: Option<FillStyle>,
990    #[serde(skip_serializing_if = "Option::is_none")]
991    pub transition: Option<Box<SlideTransition>>,
992    #[serde(skip_serializing_if = "Option::is_none")]
993    pub timing: Option<Box<SlideTiming>>,
994    pub nodes: Vec<SlideNode>,
995    #[serde(skip_serializing_if = "Vec::is_empty")]
996    pub notes: Vec<SlideNote>,
997    #[serde(skip_serializing_if = "Vec::is_empty")]
998    pub comments: Vec<SlideComment>,
999    #[serde(skip_serializing_if = "Option::is_none")]
1000    pub source_part: Option<String>,
1001    pub warnings: Vec<PresentationWarning>,
1002}
1003
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1005pub struct SlideNote {
1006    pub text: String,
1007    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1008    pub paragraphs: Vec<TextParagraph>,
1009}
1010
1011#[derive(Debug, Clone, Serialize, Deserialize)]
1012#[serde(rename_all = "camelCase")]
1013pub struct SlideComment {
1014    pub id: String,
1015    pub text: String,
1016    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1017    pub paragraphs: Vec<TextParagraph>,
1018    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1019    pub replies: Vec<SlideComment>,
1020    #[serde(skip_serializing_if = "Option::is_none")]
1021    pub modern: Option<Box<ModernComment>>,
1022    #[serde(skip_serializing_if = "Option::is_none")]
1023    pub author: Option<String>,
1024    #[serde(skip_serializing_if = "Option::is_none")]
1025    pub author_id: Option<String>,
1026    #[serde(skip_serializing_if = "Option::is_none")]
1027    pub author_initials: Option<String>,
1028    #[serde(skip_serializing_if = "Option::is_none")]
1029    pub source_index: Option<u32>,
1030    #[serde(skip_serializing_if = "Option::is_none")]
1031    pub source_part: Option<String>,
1032    #[serde(skip_serializing_if = "Option::is_none")]
1033    pub created_at: Option<String>,
1034    #[serde(skip_serializing_if = "Option::is_none")]
1035    pub modified_at: Option<String>,
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    pub position: Option<SlideCommentPosition>,
1038    #[serde(skip_serializing_if = "Option::is_none")]
1039    pub x: Option<f64>,
1040    #[serde(skip_serializing_if = "Option::is_none")]
1041    pub y: Option<f64>,
1042}
1043
1044#[derive(Debug, Clone, Serialize, Deserialize)]
1045#[serde(rename_all = "camelCase")]
1046pub struct SlideCommentPosition {
1047    pub x: i64,
1048    pub y: i64,
1049    pub unit: SlideCommentPositionUnit,
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize)]
1053#[serde(rename_all = "camelCase")]
1054pub enum SlideCommentPositionUnit {
1055    EighthPoint,
1056}
1057
1058#[derive(Debug, Clone, Serialize, Deserialize)]
1059#[serde(rename_all = "camelCase")]
1060pub struct ModernComment {
1061    pub status: String,
1062    #[serde(skip_serializing_if = "Option::is_none")]
1063    pub author_user_id: Option<String>,
1064    #[serde(skip_serializing_if = "Option::is_none")]
1065    pub author_provider_id: Option<String>,
1066    #[serde(skip_serializing_if = "Option::is_none")]
1067    pub author_extensions_xml: Option<String>,
1068    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1069    pub anchors: Vec<CommentAnchor>,
1070    #[serde(skip_serializing_if = "Option::is_none")]
1071    pub position_emu: Option<CommentAnchorPosition>,
1072    #[serde(skip_serializing_if = "Option::is_none")]
1073    pub task: Option<CommentTask>,
1074    #[serde(skip_serializing_if = "Option::is_none")]
1075    pub extensions_xml: Option<String>,
1076}
1077
1078#[derive(Debug, Clone, Serialize, Deserialize)]
1079#[serde(rename_all = "camelCase")]
1080pub struct CommentTask {
1081    #[serde(skip_serializing_if = "Option::is_none")]
1082    pub start_date: Option<String>,
1083    #[serde(skip_serializing_if = "Option::is_none")]
1084    pub due_date: Option<String>,
1085    pub assigned_to: Vec<String>,
1086    pub title: String,
1087    pub completion: String,
1088    #[serde(skip_serializing_if = "Option::is_none")]
1089    pub completion_fraction: Option<f64>,
1090}
1091
1092#[derive(Debug, Clone, Serialize, Deserialize)]
1093#[serde(rename_all = "camelCase")]
1094pub struct CommentAnchorPosition {
1095    pub x: f64,
1096    pub y: f64,
1097    pub source_x: String,
1098    pub source_y: String,
1099}
1100
1101#[derive(Debug, Clone, Serialize, Deserialize)]
1102#[serde(rename_all = "camelCase")]
1103pub struct CommentAnchor {
1104    pub kind: CommentAnchorKind,
1105    pub monikers: Vec<CommentMoniker>,
1106}
1107
1108#[derive(Debug, Clone, Serialize, Deserialize)]
1109#[serde(rename_all = "camelCase")]
1110pub enum CommentAnchorKind {
1111    Slide,
1112    DrawingElement,
1113    TextRange,
1114    Unknown,
1115}
1116
1117#[derive(Debug, Clone, Serialize, Deserialize)]
1118pub struct CommentMoniker {
1119    pub namespace: String,
1120    pub name: String,
1121    pub attributes: BTreeMap<String, String>,
1122    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1123    pub children: Vec<CommentMoniker>,
1124    #[serde(default, skip_serializing_if = "String::is_empty")]
1125    pub text: String,
1126}
1127
1128#[derive(Debug, Clone, Serialize, Deserialize)]
1129#[serde(rename_all = "camelCase")]
1130pub struct PresentationDocument {
1131    pub format: PresentationFormat,
1132    pub size: PresentationSize,
1133    pub slides: Vec<PresentationSlide>,
1134    pub masters: Vec<SlideMaster>,
1135    pub layouts: Vec<SlideLayout>,
1136    pub themes: Vec<PresentationTheme>,
1137    pub assets: BTreeMap<String, PresentationAsset>,
1138    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1139    pub embedded_fonts: Vec<PresentationEmbeddedFont>,
1140    pub warnings: Vec<PresentationWarning>,
1141    #[serde(skip_serializing_if = "Option::is_none")]
1142    pub metadata: Option<PresentationMetadata>,
1143}