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#[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#[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 #[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 #[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}