Skip to main content

ooxml_drawingml/
shape.rs

1use serde::{Deserialize, Serialize};
2
3use crate::ColorValue;
4
5#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct ShapeFill {
8    #[serde(rename = "type")]
9    pub fill_type: String,
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub color: Option<ColorValue>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub gradient: Option<GradientFill>,
14}
15
16impl ShapeFill {
17    pub fn named(fill_type: &str) -> Self {
18        Self {
19            fill_type: fill_type.to_owned(),
20            color: None,
21            gradient: None,
22        }
23    }
24}
25
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
27pub struct GradientFill {
28    #[serde(rename = "type")]
29    pub gradient_type: String,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub angle: Option<f64>,
32    pub stops: Vec<GradientStop>,
33}
34
35#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
36pub struct GradientStop {
37    pub position: f64,
38    pub color: ColorValue,
39}
40
41#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct ShapeOutline {
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub width: Option<f64>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub color: Option<ColorValue>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub gradient: Option<GradientFill>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub style: Option<String>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub cap: Option<String>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub join: Option<String>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub head_end: Option<LineEnd>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub tail_end: Option<LineEnd>,
60}
61
62/// Shape effects.
63#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct ShapeEffects {
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub outer_shadow: Option<OuterShadow>,
68}
69
70/// Outer shadow in EMUs and 60000ths of a degree.
71#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct OuterShadow {
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub color: Option<ColorValue>,
76    #[serde(default, skip_serializing_if = "is_zero_emu")]
77    pub blur_radius: i64,
78    #[serde(default, skip_serializing_if = "is_zero_emu")]
79    pub distance: i64,
80    #[serde(default, skip_serializing_if = "is_zero_emu")]
81    pub direction: i64,
82    #[serde(default = "default_true", skip_serializing_if = "is_true")]
83    pub rotate_with_shape: bool,
84    /// `sx`/`sy`: the shadow's own size, as a fraction of the shape's.
85    #[serde(default = "default_scale", skip_serializing_if = "is_unit_scale")]
86    pub scale_x: f64,
87    #[serde(default = "default_scale", skip_serializing_if = "is_unit_scale")]
88    pub scale_y: f64,
89    /// `algn`: the point of the shape's box a scaled shadow keeps.
90    #[serde(
91        default = "default_alignment",
92        skip_serializing_if = "is_default_alignment"
93    )]
94    pub alignment: String,
95}
96
97impl Default for OuterShadow {
98    fn default() -> Self {
99        Self {
100            color: None,
101            blur_radius: 0,
102            distance: 0,
103            direction: 0,
104            rotate_with_shape: true,
105            scale_x: 1.0,
106            scale_y: 1.0,
107            alignment: default_alignment(),
108        }
109    }
110}
111
112fn default_scale() -> f64 {
113    1.0
114}
115fn is_unit_scale(value: &f64) -> bool {
116    *value == 1.0
117}
118fn default_alignment() -> String {
119    "b".to_owned()
120}
121fn is_default_alignment(value: &str) -> bool {
122    value == "b"
123}
124
125fn is_zero_emu(value: &i64) -> bool {
126    *value == 0
127}
128fn default_true() -> bool {
129    true
130}
131fn is_true(value: &bool) -> bool {
132    *value
133}
134
135#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
136pub struct LineEnd {
137    #[serde(rename = "type")]
138    pub end_type: String,
139    pub width: Option<String>,
140    pub length: Option<String>,
141}
142
143#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
144#[serde(tag = "type", rename_all = "camelCase")]
145pub enum GeometryPathCommand {
146    Move {
147        x: f64,
148        y: f64,
149    },
150    Line {
151        x: f64,
152        y: f64,
153    },
154    Quad {
155        cpx: f64,
156        cpy: f64,
157        x: f64,
158        y: f64,
159    },
160    Cubic {
161        cp1x: f64,
162        cp1y: f64,
163        cp2x: f64,
164        cp2y: f64,
165        x: f64,
166        y: f64,
167    },
168    Close,
169}
170
171#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
172pub struct Size2D {
173    pub width: f64,
174    pub height: f64,
175}
176
177#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct Transform2D {
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub rotation: Option<f64>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub flip_h: Option<bool>,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub flip_v: Option<bool>,
186}
187
188#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
189pub struct Point2D {
190    pub x: f64,
191    pub y: f64,
192}
193
194#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
195pub struct TransformResult {
196    pub size: Size2D,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub transform: Option<Transform2D>,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub offset: Option<Point2D>,
201}
202
203pub fn resolve_shape_fill_color(fill_type: Option<&str>, color: Option<&str>) -> Option<String> {
204    if fill_type == Some("none") {
205        return None;
206    }
207    Some(color.unwrap_or("#ffffff").to_owned())
208}