1use std::collections::BTreeMap;
2
3pub use ooxml_drawingml::ShapeStyle;
4use ooxml_drawingml::{
5 ColorValue, GeometryPathCommand, ShapeEffects, ShapeFill, ShapeOutline, TableStyleList, Theme,
6 ThemeFormatScheme,
7};
8use serde::{Deserialize, Serialize};
9
10use crate::comments::{Comment, CommentAuthor, CommentFlavor};
11use crate::relationships::Relationship;
12
13pub use ooxml_drawingml::chart::{
14 ChartAxes, ChartAxis, ChartDataLabels, ChartLegend, ChartMarker, ChartPlotGroup, ChartPoint,
15 ChartPointLabel, ChartSeries, ChartSpace, ChartTextProperties,
16};
17
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub(crate) enum ShapeElements {
22 WithConnectors,
23 #[default]
24 WithoutConnectors,
25}
26
27impl ShapeElements {
28 fn is_legacy(&self) -> bool {
29 *self == Self::WithoutConnectors
30 }
31
32 pub(crate) fn contains(self, local: &str) -> bool {
33 matches!(local, "sp" | "pic" | "graphicFrame" | "grpSp")
34 || (self == Self::WithConnectors && local == "cxnSp")
35 }
36}
37
38#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase")]
40pub struct PptxPackage {
41 pub presentation: Presentation,
42 pub slides: Vec<Slide>,
43 pub layouts: Vec<SlideLayout>,
44 pub masters: Vec<SlideMaster>,
45 pub themes: Vec<ThemePart>,
46 #[serde(default)]
48 pub charts: Vec<ChartPart>,
49 pub media: Vec<MediaPart>,
50 #[serde(default, skip_serializing_if = "TableStyleList::is_empty")]
52 pub table_styles: TableStyleList,
53 #[serde(default, skip_serializing_if = "Vec::is_empty")]
54 pub comment_authors: Vec<CommentAuthor>,
55 #[serde(default, skip_serializing_if = "Vec::is_empty")]
56 pub comments: Vec<Comment>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub comment_flavor: Option<CommentFlavor>,
59 pub relationships: BTreeMap<String, Vec<Relationship>>,
60 #[serde(skip)]
61 pub(crate) parts: Vec<PackagePart>,
62 #[serde(default, skip_serializing_if = "ShapeElements::is_legacy")]
63 pub(crate) shape_elements: ShapeElements,
64}
65
66impl PptxPackage {
67 pub fn part_bytes(&self, path: &str) -> Option<&[u8]> {
68 self.parts
69 .iter()
70 .find(|part| part.path == path)
71 .map(|part| part.bytes.as_slice())
72 }
73
74 pub fn has_parts(&self) -> bool {
77 !self.parts.is_empty()
78 }
79
80 pub fn models_connectors(&self) -> bool {
82 self.shape_elements == ShapeElements::WithConnectors
83 }
84
85 pub fn replace_part(&mut self, path: &str, bytes: Vec<u8>) -> bool {
86 let Some(part) = self.parts.iter_mut().find(|part| part.path == path) else {
87 return false;
88 };
89 part.bytes = bytes;
90 true
91 }
92}
93
94#[derive(Clone, Debug, PartialEq)]
95pub(crate) struct PackagePart {
96 pub path: String,
97 pub bytes: Vec<u8>,
98}
99
100#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct Presentation {
103 pub part_path: String,
104 pub width_emu: i64,
105 pub height_emu: i64,
106 #[serde(
108 default = "default_first_slide_num",
109 skip_serializing_if = "is_default_first_slide_num"
110 )]
111 pub first_slide_num: i32,
112 pub slides: Vec<SlideReference>,
113 pub master_part_paths: Vec<String>,
114}
115
116fn default_first_slide_num() -> i32 {
117 1
118}
119
120fn is_default_first_slide_num(value: &i32) -> bool {
121 *value == default_first_slide_num()
122}
123
124#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct SlideReference {
127 pub id: u32,
128 pub relationship_id: String,
129 pub part_path: String,
130}
131
132#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct Slide {
135 pub part_path: String,
136 pub name: Option<String>,
137 pub layout_part_path: Option<String>,
138 pub show_master_shapes: bool,
139 pub background: Option<ShapeFill>,
140 pub shapes: Vec<ShapeNode>,
141 #[serde(default, skip_serializing_if = "String::is_empty")]
142 pub notes: String,
143}
144
145#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub struct SlideLayout {
148 pub part_path: String,
149 pub name: Option<String>,
150 pub layout_type: Option<String>,
151 pub master_part_path: Option<String>,
152 pub show_master_shapes: bool,
153 pub background: Option<ShapeFill>,
154 pub shapes: Vec<ShapeNode>,
155}
156
157#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct SlideMaster {
160 pub part_path: String,
161 pub name: Option<String>,
162 pub theme_part_path: Option<String>,
163 pub layout_part_paths: Vec<String>,
164 pub background: Option<ShapeFill>,
165 pub shapes: Vec<ShapeNode>,
166 pub text_styles: TextStyleSet,
167}
168
169#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct ThemePart {
172 pub part_path: String,
173 pub theme: Theme,
174 #[serde(default, skip_serializing_if = "ThemeFormatScheme::is_empty")]
176 pub format_scheme: ThemeFormatScheme,
177}
178
179#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct ChartPart {
183 pub part_path: String,
184 pub theme_part_path: Option<String>,
185 pub chart: ChartSpace,
186}
187
188#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct MediaPart {
191 pub part_path: String,
192 pub content_type: String,
193 pub bytes: Vec<u8>,
194}
195
196#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
197#[serde(
198 tag = "kind",
199 rename_all = "camelCase",
200 rename_all_fields = "camelCase"
201)]
202pub enum ShapeNode {
203 Shape(Shape),
204 Picture(Picture),
205 GraphicFrame(GraphicFrame),
206 Group(GroupShape),
207}
208
209impl ShapeNode {
210 pub fn id(&self) -> u32 {
211 match self {
212 Self::Shape(shape) => shape.base.id,
213 Self::Picture(picture) => picture.base.id,
214 Self::GraphicFrame(frame) => frame.base.id,
215 Self::Group(group) => group.base.id,
216 }
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct ShapeBase {
223 pub id: u32,
224 pub name: String,
225 pub description: Option<String>,
226 pub hidden: bool,
227 pub placeholder: Option<Placeholder>,
228 pub transform: ShapeTransform,
229}
230
231#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
232#[serde(rename_all = "camelCase")]
233pub struct ShapeTransform {
234 pub x: i64,
235 pub y: i64,
236 pub width: i64,
237 pub height: i64,
238 pub rotation_deg: f64,
239 pub flip_h: bool,
240 pub flip_v: bool,
241 pub child_x: Option<i64>,
242 pub child_y: Option<i64>,
243 pub child_width: Option<i64>,
244 pub child_height: Option<i64>,
245}
246
247#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase")]
249pub struct Placeholder {
250 pub placeholder_type: Option<String>,
251 pub index: Option<u32>,
252 pub orientation: Option<String>,
253 pub size: Option<String>,
254}
255
256#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct Shape {
259 #[serde(flatten)]
260 pub base: ShapeBase,
261 pub geometry: String,
262 #[serde(
263 default = "has_preset_geometry_default",
264 skip_serializing_if = "is_preset_geometry"
265 )]
266 pub has_preset_geometry: bool,
267 #[serde(default, skip_serializing_if = "Vec::is_empty")]
269 pub paths: Vec<CustomGeometryPath>,
270 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub style: Option<Box<ShapeStyle>>,
273 #[serde(default)]
274 pub adjust_values: BTreeMap<String, f64>,
275 pub fill: Option<ShapeFill>,
276 #[serde(default, skip_serializing_if = "Option::is_none")]
278 pub picture_fill: Option<Box<PictureFill>>,
279 pub outline: Option<ShapeOutline>,
280 #[serde(default, skip_serializing_if = "Option::is_none")]
281 pub effects: Option<ShapeEffects>,
282 pub text: Option<TextBody>,
283}
284
285#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct PictureFill {
289 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub relationship_id: Option<String>,
291 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub media_part_path: Option<String>,
293 #[serde(default, skip_serializing_if = "PictureCrop::is_whole")]
294 pub crop: PictureCrop,
295 #[serde(default, skip_serializing_if = "PictureCrop::is_whole")]
297 pub fill_rect: PictureCrop,
298}
299
300#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct CustomGeometryPath {
303 pub commands: Vec<GeometryPathCommand>,
304 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
305 pub no_fill: bool,
306 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
307 pub no_stroke: bool,
308}
309
310#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
311#[serde(rename_all = "camelCase")]
312pub struct Picture {
313 #[serde(flatten)]
314 pub base: ShapeBase,
315 pub relationship_id: Option<String>,
316 pub media_part_path: Option<String>,
317 pub crop: PictureCrop,
318 #[serde(default, skip_serializing_if = "Vec::is_empty")]
320 pub effects: Vec<BlipEffect>,
321 #[serde(default = "rect_geometry", skip_serializing_if = "is_rect")]
323 pub geometry: String,
324 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
325 pub adjust_values: BTreeMap<String, f64>,
326 pub fill: Option<ShapeFill>,
327 pub outline: Option<ShapeOutline>,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub shape_effects: Option<ShapeEffects>,
330 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub style: Option<Box<ShapeStyle>>,
332}
333
334fn rect_geometry() -> String {
335 "rect".to_owned()
336}
337
338fn is_rect(geometry: &str) -> bool {
339 geometry == "rect"
340}
341
342fn has_preset_geometry_default() -> bool {
343 true
344}
345
346fn is_preset_geometry(value: &bool) -> bool {
347 *value
348}
349
350#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
352#[serde(tag = "type", rename_all = "camelCase")]
353pub enum BlipEffect {
354 BiLevel { threshold: f64 },
356 Grayscale,
358 Luminance { brightness: f64, contrast: f64 },
360 Duotone {
362 shadow: Option<ColorValue>,
363 highlight: Option<ColorValue>,
364 },
365 ColorChange {
367 from: Option<ColorValue>,
368 to: Option<ColorValue>,
369 #[serde(
370 default = "default_use_alpha",
371 skip_serializing_if = "use_alpha_is_default",
372 rename = "useAlpha"
373 )]
374 use_alpha: bool,
375 },
376}
377
378fn default_use_alpha() -> bool {
379 true
380}
381
382fn use_alpha_is_default(value: &bool) -> bool {
383 *value
384}
385
386#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
387pub struct PictureCrop {
388 pub left: i32,
389 pub top: i32,
390 pub right: i32,
391 pub bottom: i32,
392}
393
394impl PictureCrop {
395 pub fn is_whole(&self) -> bool {
396 *self == Self::default()
397 }
398}
399
400#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
401#[serde(rename_all = "camelCase")]
402pub struct GraphicFrame {
403 #[serde(flatten)]
404 pub base: ShapeBase,
405 pub data: GraphicFrameData,
406}
407
408#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
409#[serde(
410 tag = "type",
411 rename_all = "camelCase",
412 rename_all_fields = "camelCase"
413)]
414pub enum GraphicFrameData {
415 Table(Table),
416 Chart {
417 relationship_id: String,
418 part_path: Option<String>,
419 },
420 Diagram {
421 relationship_ids: Vec<String>,
422 },
423 Unknown {
424 uri: Option<String>,
425 #[serde(default, skip_serializing_if = "Option::is_none")]
426 picture: Option<Box<Picture>>,
427 },
428}
429
430#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
431#[serde(rename_all = "camelCase")]
432pub struct GroupShape {
433 #[serde(flatten)]
434 pub base: ShapeBase,
435 pub children: Vec<ShapeNode>,
436}
437
438#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct Table {
442 #[serde(default, skip_serializing_if = "Vec::is_empty")]
444 pub grid: Vec<i64>,
445 #[serde(default, skip_serializing_if = "TableProperties::is_default")]
446 pub properties: TableProperties,
447 #[serde(
448 default,
449 serialize_with = "serialize_table_rows",
450 deserialize_with = "deserialize_table_rows"
451 )]
452 pub rows: Vec<TableRow>,
453}
454
455#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
457#[serde(rename_all = "camelCase", default)]
458pub struct TableProperties {
459 pub first_row: bool,
460 pub last_row: bool,
461 pub first_col: bool,
462 pub last_col: bool,
463 pub band_row: bool,
464 pub band_col: bool,
465 #[serde(skip_serializing_if = "Option::is_none")]
466 pub style_id: Option<String>,
467}
468
469impl TableProperties {
470 pub fn is_default(&self) -> bool {
471 *self == Self::default()
472 }
473}
474
475#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
476#[serde(rename_all = "camelCase")]
477pub struct TableRow {
478 #[serde(default)]
480 pub height: i64,
481 pub cells: Vec<TableCell>,
482}
483
484impl TableRow {
485 fn is_text_only(&self) -> bool {
486 self.height == 0 && self.cells.iter().all(TableCell::is_text_only)
487 }
488}
489
490#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct TableCell {
494 pub text: TextBody,
495 #[serde(default = "unit_span", skip_serializing_if = "is_unit_span")]
496 pub grid_span: u32,
497 #[serde(default = "unit_span", skip_serializing_if = "is_unit_span")]
498 pub row_span: u32,
499 #[serde(default, skip_serializing_if = "is_false")]
501 pub merged: bool,
502 #[serde(default, skip_serializing_if = "Option::is_none")]
503 pub fill: Option<ShapeFill>,
504 #[serde(default, skip_serializing_if = "TableCellBorders::is_empty")]
505 pub borders: TableCellBorders,
506}
507
508impl TableCell {
509 pub fn from_text(text: TextBody) -> Self {
510 Self {
511 text,
512 ..Self::default()
513 }
514 }
515
516 fn is_text_only(&self) -> bool {
519 *self == Self::from_text(self.text.clone())
520 }
521}
522
523impl Default for TableCell {
524 fn default() -> Self {
525 Self {
526 text: TextBody::default(),
527 grid_span: 1,
528 row_span: 1,
529 merged: false,
530 fill: None,
531 borders: TableCellBorders::default(),
532 }
533 }
534}
535
536#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
538#[serde(rename_all = "camelCase", default)]
539pub struct TableCellBorders {
540 #[serde(skip_serializing_if = "Option::is_none")]
541 pub left: Option<ShapeOutline>,
542 #[serde(skip_serializing_if = "Option::is_none")]
543 pub top: Option<ShapeOutline>,
544 #[serde(skip_serializing_if = "Option::is_none")]
545 pub right: Option<ShapeOutline>,
546 #[serde(skip_serializing_if = "Option::is_none")]
547 pub bottom: Option<ShapeOutline>,
548}
549
550impl TableCellBorders {
551 pub fn is_empty(&self) -> bool {
552 *self == Self::default()
553 }
554}
555
556fn unit_span() -> u32 {
557 1
558}
559
560fn is_unit_span(span: &u32) -> bool {
561 *span == 1
562}
563
564fn is_false(value: &bool) -> bool {
565 !*value
566}
567
568fn serialize_table_rows<S>(rows: &[TableRow], serializer: S) -> Result<S::Ok, S::Error>
571where
572 S: serde::Serializer,
573{
574 use serde::ser::SerializeSeq;
575
576 if !rows.iter().all(TableRow::is_text_only) {
577 return rows.serialize(serializer);
578 }
579 let mut sequence = serializer.serialize_seq(Some(rows.len()))?;
580 for row in rows {
581 let cells: Vec<_> = row.cells.iter().map(|cell| &cell.text).collect();
582 sequence.serialize_element(&cells)?;
583 }
584 sequence.end()
585}
586
587fn deserialize_table_rows<'de, D>(deserializer: D) -> Result<Vec<TableRow>, D::Error>
589where
590 D: serde::Deserializer<'de>,
591{
592 #[derive(Deserialize)]
593 #[serde(untagged)]
594 enum Row {
595 Modern(TableRow),
596 Legacy(Vec<TextBody>),
597 }
598
599 Ok(Vec::<Row>::deserialize(deserializer)?
600 .into_iter()
601 .map(|row| match row {
602 Row::Modern(row) => row,
603 Row::Legacy(cells) => TableRow {
604 height: 0,
605 cells: cells.into_iter().map(TableCell::from_text).collect(),
606 },
607 })
608 .collect())
609}
610
611#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
612#[serde(rename_all = "camelCase")]
613pub struct TextBody {
614 pub anchor: Option<String>,
615 pub vertical: Option<String>,
616 #[serde(default, skip_serializing_if = "Option::is_none")]
618 pub compat_line_spacing: Option<bool>,
619 pub autofit: Option<TextAutofit>,
620 #[serde(default, skip_serializing_if = "Option::is_none")]
621 pub vertical_overflow: Option<TextOverflow>,
622 #[serde(default, skip_serializing_if = "Option::is_none")]
623 pub horizontal_overflow: Option<TextOverflow>,
624 pub inset_left: Option<i64>,
625 pub inset_top: Option<i64>,
626 pub inset_right: Option<i64>,
627 pub inset_bottom: Option<i64>,
628 #[serde(default, skip_serializing_if = "Vec::is_empty")]
630 pub list_style: Vec<ParagraphProperties>,
631 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub default_list_style: Option<Box<ParagraphProperties>>,
633 pub paragraphs: Vec<TextParagraph>,
634}
635
636#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
637#[serde(rename_all = "camelCase")]
638pub enum TextOverflow {
639 Overflow,
640 Clip,
641 Ellipsis,
642}
643
644#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
645#[serde(
646 tag = "type",
647 rename_all = "camelCase",
648 rename_all_fields = "camelCase"
649)]
650pub enum TextAutofit {
651 None,
652 Shape,
653 Normal {
654 font_scale: Option<f64>,
655 line_space_reduction: Option<f64>,
656 },
657}
658
659#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
660#[serde(rename_all = "camelCase")]
661pub struct TextParagraph {
662 pub properties: ParagraphProperties,
663 pub runs: Vec<TextRun>,
664 pub end_properties: Option<RunProperties>,
665}
666
667#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
669#[serde(
670 tag = "type",
671 rename_all = "camelCase",
672 rename_all_fields = "camelCase"
673)]
674pub enum LineSpacing {
675 Percent { value: f64 },
676 Points { value: f64 },
677}
678
679#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
680#[serde(rename_all = "camelCase")]
681pub struct ParagraphProperties {
682 pub alignment: Option<String>,
683 pub level: u32,
684 pub margin_left: Option<i64>,
685 #[serde(default, skip_serializing_if = "Option::is_none")]
686 pub margin_right: Option<i64>,
687 pub indent: Option<i64>,
688 pub bullet: Option<Bullet>,
689 #[serde(default, skip_serializing_if = "Option::is_none")]
690 pub line_spacing: Option<LineSpacing>,
691 #[serde(default, skip_serializing_if = "Option::is_none")]
692 pub space_before: Option<LineSpacing>,
693 #[serde(default, skip_serializing_if = "Option::is_none")]
694 pub space_after: Option<LineSpacing>,
695 #[serde(default, skip_serializing_if = "Option::is_none")]
696 pub bullet_font: Option<BulletFont>,
697 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub bullet_color: Option<BulletColor>,
699 #[serde(default, skip_serializing_if = "Option::is_none")]
700 pub bullet_size: Option<BulletSize>,
701 pub default_run: Option<RunProperties>,
702}
703
704#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
705#[serde(tag = "type", content = "value", rename_all = "camelCase")]
706pub enum BulletFont {
707 FollowText,
708 Typeface(String),
709}
710
711#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
712#[serde(tag = "type", content = "value", rename_all = "camelCase")]
713pub enum BulletColor {
714 FollowText,
715 Color(ColorValue),
716}
717
718#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
719#[serde(tag = "type", content = "value", rename_all = "camelCase")]
720pub enum BulletSize {
721 FollowText,
722 Percent(f64),
723 Points(f64),
724}
725
726#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
727#[serde(
728 tag = "type",
729 rename_all = "camelCase",
730 rename_all_fields = "camelCase"
731)]
732pub enum Bullet {
733 Character {
734 value: String,
735 },
736 AutoNumber {
737 scheme: String,
738 start_at: u32,
739 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
740 restart: bool,
741 },
742 None,
743}
744
745#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
746#[serde(rename_all = "camelCase")]
747pub struct TextRun {
748 pub text: String,
749 pub properties: RunProperties,
750 pub field_id: Option<String>,
751 pub field_type: Option<String>,
752 pub line_break: bool,
753}
754
755#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
756#[serde(rename_all = "camelCase")]
757pub struct RunProperties {
758 pub font_size_pt: Option<f64>,
759 #[serde(default, skip_serializing_if = "Option::is_none")]
760 pub spacing_pt: Option<f64>,
761 #[serde(default, skip_serializing_if = "Option::is_none")]
763 pub baseline_pct: Option<f64>,
764 pub bold: Option<bool>,
765 pub italic: Option<bool>,
766 pub underline: Option<String>,
767 pub font_family: Option<String>,
768 pub color: Option<ColorValue>,
769 pub language: Option<String>,
770 pub hyperlink_relationship_id: Option<String>,
771}
772
773#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
774#[serde(rename_all = "camelCase")]
775pub struct TextStyleSet {
776 pub title: Vec<ParagraphProperties>,
777 pub body: Vec<ParagraphProperties>,
778 pub other: Vec<ParagraphProperties>,
779}