Skip to main content

pptx_parse/
model.rs

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