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    #[serde(default)]
180    pub adjust_values: BTreeMap<String, f64>,
181    pub fill: Option<ShapeFill>,
182    pub outline: Option<ShapeOutline>,
183    pub text: Option<TextBody>,
184}
185
186#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct Picture {
189    #[serde(flatten)]
190    pub base: ShapeBase,
191    pub relationship_id: Option<String>,
192    pub media_part_path: Option<String>,
193    pub crop: PictureCrop,
194    pub fill: Option<ShapeFill>,
195    pub outline: Option<ShapeOutline>,
196}
197
198#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
199pub struct PictureCrop {
200    pub left: i32,
201    pub top: i32,
202    pub right: i32,
203    pub bottom: i32,
204}
205
206#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
207#[serde(rename_all = "camelCase")]
208pub struct GraphicFrame {
209    #[serde(flatten)]
210    pub base: ShapeBase,
211    pub data: GraphicFrameData,
212}
213
214#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
215#[serde(
216    tag = "type",
217    rename_all = "camelCase",
218    rename_all_fields = "camelCase"
219)]
220pub enum GraphicFrameData {
221    Table {
222        rows: Vec<Vec<TextBody>>,
223    },
224    Chart {
225        relationship_id: String,
226        part_path: Option<String>,
227    },
228    Diagram {
229        relationship_ids: Vec<String>,
230    },
231    Unknown {
232        uri: Option<String>,
233    },
234}
235
236#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct GroupShape {
239    #[serde(flatten)]
240    pub base: ShapeBase,
241    pub children: Vec<ShapeNode>,
242}
243
244#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
245#[serde(rename_all = "camelCase")]
246pub struct TextBody {
247    pub anchor: Option<String>,
248    pub vertical: Option<String>,
249    pub autofit: Option<TextAutofit>,
250    pub inset_left: Option<i64>,
251    pub inset_top: Option<i64>,
252    pub inset_right: Option<i64>,
253    pub inset_bottom: Option<i64>,
254    pub paragraphs: Vec<TextParagraph>,
255}
256
257#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
258#[serde(
259    tag = "type",
260    rename_all = "camelCase",
261    rename_all_fields = "camelCase"
262)]
263pub enum TextAutofit {
264    None,
265    Shape,
266    Normal {
267        font_scale: Option<f64>,
268        line_space_reduction: Option<f64>,
269    },
270}
271
272#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct TextParagraph {
275    pub properties: ParagraphProperties,
276    pub runs: Vec<TextRun>,
277    pub end_properties: Option<RunProperties>,
278}
279
280#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
281#[serde(rename_all = "camelCase")]
282pub struct ParagraphProperties {
283    pub alignment: Option<String>,
284    pub level: u32,
285    pub margin_left: Option<i64>,
286    pub indent: Option<i64>,
287    pub bullet: Option<Bullet>,
288    pub default_run: Option<RunProperties>,
289}
290
291#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
292#[serde(
293    tag = "type",
294    rename_all = "camelCase",
295    rename_all_fields = "camelCase"
296)]
297pub enum Bullet {
298    Character { value: String },
299    AutoNumber { scheme: String, start_at: u32 },
300    None,
301}
302
303#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct TextRun {
306    pub text: String,
307    pub properties: RunProperties,
308    pub field_id: Option<String>,
309    pub field_type: Option<String>,
310    pub line_break: bool,
311}
312
313#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
314#[serde(rename_all = "camelCase")]
315pub struct RunProperties {
316    pub font_size_pt: Option<f64>,
317    pub bold: Option<bool>,
318    pub italic: Option<bool>,
319    pub underline: Option<String>,
320    pub font_family: Option<String>,
321    pub color: Option<ColorValue>,
322    pub language: Option<String>,
323    pub hyperlink_relationship_id: Option<String>,
324}
325
326#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
327#[serde(rename_all = "camelCase")]
328pub struct TextStyleSet {
329    pub title: Vec<ParagraphProperties>,
330    pub body: Vec<ParagraphProperties>,
331    pub other: Vec<ParagraphProperties>,
332}