1use deckcraft_color::{ColorScheme, ColorTransform, Rgba, SchemeSlot};
4use serde::{Deserialize, Serialize};
5
6use crate::MediaId;
7
8#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase", tag = "kind")]
10pub enum ColorBase {
11 Rgb {
12 rgb: Rgba,
13 },
14 Scheme {
15 slot: SchemeSlot,
16 },
17 Preset {
18 name: String,
19 },
20 System {
22 name: String,
23 last: Rgba,
24 },
25}
26
27#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct ColorRef {
31 pub base: ColorBase,
32 #[serde(default, skip_serializing_if = "Vec::is_empty")]
33 pub mods: Vec<ColorTransform>,
34}
35
36impl ColorRef {
37 pub fn rgb(c: Rgba) -> Self {
38 ColorRef {
39 base: ColorBase::Rgb { rgb: c },
40 mods: vec![],
41 }
42 }
43 pub fn scheme(slot: SchemeSlot) -> Self {
44 ColorRef {
45 base: ColorBase::Scheme { slot },
46 mods: vec![],
47 }
48 }
49 pub fn with(mut self, m: ColorTransform) -> Self {
50 self.mods.push(m);
51 self
52 }
53 pub fn resolve(&self, scheme: &ColorScheme, ph: Option<Rgba>) -> Rgba {
55 let base = match &self.base {
56 ColorBase::Rgb { rgb } => *rgb,
57 ColorBase::Scheme {
58 slot: SchemeSlot::PhClr,
59 } => ph.unwrap_or_else(|| scheme.get(SchemeSlot::Accent1)),
60 ColorBase::Scheme { slot } => scheme.get(*slot),
61 ColorBase::Preset { name } => deckcraft_color::preset(name).unwrap_or(Rgba::BLACK),
62 ColorBase::System { last, .. } => *last,
63 };
64 deckcraft_color::apply(base, &self.mods)
65 }
66 pub fn alpha(&self) -> f64 {
68 self.mods.iter().fold(1.0, |a, m| match m {
69 ColorTransform::Alpha(v) => (*v as f64 / 100_000.0).clamp(0.0, 1.0),
70 _ => a,
71 })
72 }
73}
74
75#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
76#[serde(rename_all = "camelCase")]
77pub struct GradientStop {
78 pub pos: f64,
80 pub color: ColorRef,
81}
82
83#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase", tag = "kind")]
85pub enum GradientShape {
86 Linear {
88 angle: f64,
89 #[serde(default)]
90 scaled: bool,
91 },
92 Path { path: String, focus: [f64; 4] },
94}
95
96#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct Gradient {
99 pub stops: Vec<GradientStop>,
100 pub shape: GradientShape,
101 #[serde(default = "crate::yes")]
102 pub rotate_with_shape: bool,
103}
104
105#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase", tag = "kind")]
108pub enum PictureMode {
109 Stretch {
110 #[serde(default)]
111 fill_rect: [f64; 4],
112 },
113 Tile {
114 tx: f64,
115 ty: f64,
116 sx: f64,
117 sy: f64,
118 #[serde(default)]
119 flip: String,
120 #[serde(default)]
121 align: String,
122 },
123}
124
125impl Default for PictureMode {
126 fn default() -> Self {
127 PictureMode::Stretch {
128 fill_rect: [0.0; 4],
129 }
130 }
131}
132
133#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
134#[serde(default, rename_all = "camelCase")]
135pub struct PictureFill {
136 pub media: MediaId,
137 pub crop: [f64; 4],
139 pub mode: PictureMode,
140 pub alpha: Option<f64>,
142 pub adjust: PictureAdjust,
143}
144
145#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
147#[serde(default, rename_all = "camelCase")]
148pub struct PictureAdjust {
149 pub brightness: f64,
151 pub contrast: f64,
153 pub saturation: Option<f64>,
155 pub grayscale: bool,
156 pub duotone: Option<(ColorRef, ColorRef)>,
158 pub clear_color: Option<Rgba>,
160 pub sharpen: f64,
161}
162
163#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct PatternFill {
166 pub preset: String,
167 pub fg: ColorRef,
168 pub bg: ColorRef,
169}
170
171#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase", tag = "kind")]
173pub enum Fill {
174 None,
175 Solid {
176 color: ColorRef,
177 },
178 Gradient(Gradient),
179 Picture(PictureFill),
180 Pattern(PatternFill),
181 Group,
183 Background,
185}
186
187impl Fill {
188 pub fn solid(c: ColorRef) -> Self {
189 Fill::Solid { color: c }
190 }
191 pub fn is_none(&self) -> bool {
192 matches!(self, Fill::None)
193 }
194}
195
196#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub enum LineCap {
199 #[default]
200 Flat,
201 Round,
202 Square,
203}
204
205#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub enum LineJoin {
208 #[default]
209 Round,
210 Bevel,
211 Miter,
212}
213
214#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub enum Compound {
217 #[default]
218 Single,
219 Double,
220 ThickThin,
221 ThinThick,
222 Triple,
223}
224
225impl Compound {
226 pub fn xml(self) -> &'static str {
227 match self {
228 Compound::Single => "sng",
229 Compound::Double => "dbl",
230 Compound::ThickThin => "thickThin",
231 Compound::ThinThick => "thinThick",
232 Compound::Triple => "tri",
233 }
234 }
235 pub fn from_xml(s: &str) -> Self {
236 match s {
237 "dbl" => Compound::Double,
238 "thickThin" => Compound::ThickThin,
239 "thinThick" => Compound::ThinThick,
240 "tri" => Compound::Triple,
241 _ => Compound::Single,
242 }
243 }
244}
245
246#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase", tag = "kind")]
251pub enum Dash {
252 Preset { name: String },
253 Custom { pattern: Vec<(f64, f64)> },
254}
255
256impl Dash {
257 pub const PRESETS: [&'static str; 11] = [
258 "solid",
259 "sysDot",
260 "sysDash",
261 "dash",
262 "dashDot",
263 "lgDash",
264 "lgDashDot",
265 "lgDashDotDot",
266 "dot",
267 "sysDashDot",
268 "sysDashDotDot",
269 ];
270 pub fn pattern(&self) -> Vec<f64> {
272 match self {
273 Dash::Custom { pattern } => pattern.iter().flat_map(|(d, s)| [*d, *s]).collect(),
274 Dash::Preset { name } => match name.as_str() {
275 "dot" => vec![1.0, 3.0],
276 "dash" => vec![4.0, 3.0],
277 "lgDash" => vec![8.0, 3.0],
278 "dashDot" => vec![4.0, 3.0, 1.0, 3.0],
279 "lgDashDot" => vec![8.0, 3.0, 1.0, 3.0],
280 "lgDashDotDot" => vec![8.0, 3.0, 1.0, 3.0, 1.0, 3.0],
281 "sysDash" => vec![3.0, 1.0],
282 "sysDot" => vec![1.0, 1.0],
283 "sysDashDot" => vec![3.0, 1.0, 1.0, 1.0],
284 "sysDashDotDot" => vec![3.0, 1.0, 1.0, 1.0, 1.0, 1.0],
285 _ => vec![],
286 },
287 }
288 }
289}
290
291#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
293#[serde(rename_all = "camelCase")]
294pub struct LineEnd {
295 pub kind: String,
297 #[serde(default = "med")]
299 pub w: String,
300 #[serde(default = "med")]
301 pub len: String,
302}
303
304fn med() -> String {
305 "med".into()
306}
307
308#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
309#[serde(default, rename_all = "camelCase")]
310pub struct Line {
311 pub fill: Option<Fill>,
312 pub width: Option<f64>,
314 pub dash: Option<Dash>,
315 pub cap: Option<LineCap>,
316 pub join: Option<LineJoin>,
317 pub compound: Option<Compound>,
318 pub head: Option<LineEnd>,
319 pub tail: Option<LineEnd>,
320}
321
322impl Line {
323 pub fn solid(c: ColorRef, width: f64) -> Self {
324 Line {
325 fill: Some(Fill::solid(c)),
326 width: Some(width),
327 ..Default::default()
328 }
329 }
330 pub fn none() -> Self {
331 Line {
332 fill: Some(Fill::None),
333 ..Default::default()
334 }
335 }
336 pub fn inherit(&mut self, parent: &Line) {
338 if self.fill.is_none() {
339 self.fill.clone_from(&parent.fill);
340 }
341 self.width = self.width.or(parent.width);
342 if self.dash.is_none() {
343 self.dash.clone_from(&parent.dash);
344 }
345 self.cap = self.cap.or(parent.cap);
346 self.join = self.join.or(parent.join);
347 self.compound = self.compound.or(parent.compound);
348 if self.head.is_none() {
349 self.head.clone_from(&parent.head);
350 }
351 if self.tail.is_none() {
352 self.tail.clone_from(&parent.tail);
353 }
354 }
355}
356
357#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
358#[serde(rename_all = "camelCase")]
359pub struct Shadow {
360 pub color: ColorRef,
361 pub blur: f64,
363 pub dist: f64,
365 pub dir: f64,
367 #[serde(default)]
368 pub inner: bool,
369 #[serde(default = "one")]
371 pub sx: f64,
372 #[serde(default = "one")]
373 pub sy: f64,
374 #[serde(default)]
375 pub kx: f64,
376 #[serde(default)]
377 pub ky: f64,
378 #[serde(default)]
379 pub align: String,
380 #[serde(default = "crate::yes")]
381 pub rotate_with_shape: bool,
382}
383
384fn one() -> f64 {
385 1.0
386}
387
388#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub struct Glow {
391 pub color: ColorRef,
392 pub radius: f64,
393}
394
395#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct Reflection {
398 pub blur: f64,
399 pub start_alpha: f64,
400 pub end_alpha: f64,
401 pub end_pos: f64,
402 pub dist: f64,
403}
404
405#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
406#[serde(default, rename_all = "camelCase")]
407pub struct Effects {
408 pub outer_shadow: Option<Shadow>,
409 pub inner_shadow: Option<Shadow>,
410 pub glow: Option<Glow>,
411 pub soft_edge: Option<f64>,
413 pub reflection: Option<Reflection>,
414 pub bevel: Option<String>,
416 pub raw3d: Option<String>,
418 pub fill_overlay: Option<FillOverlay>,
421}
422
423#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
424#[serde(rename_all = "camelCase")]
425pub struct FillOverlay {
426 pub blend: String,
427 pub fill: Fill,
428}
429
430impl Effects {
431 pub fn is_empty(&self) -> bool {
432 self.outer_shadow.is_none()
433 && self.inner_shadow.is_none()
434 && self.glow.is_none()
435 && self.soft_edge.is_none()
436 && self.reflection.is_none()
437 && self.bevel.is_none()
438 && self.fill_overlay.is_none()
439 }
440}
441
442#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446pub struct ShapeStyle {
447 pub line_ref: (u32, ColorRef),
448 pub fill_ref: (u32, ColorRef),
449 pub effect_ref: (u32, ColorRef),
450 pub font_ref: (String, Option<ColorRef>),
452}
453
454impl ShapeStyle {
455 pub fn accent(slot: SchemeSlot) -> Self {
457 ShapeStyle {
458 line_ref: (2, ColorRef::scheme(slot).with(ColorTransform::Shade(50000))),
459 fill_ref: (1, ColorRef::scheme(slot)),
460 effect_ref: (0, ColorRef::scheme(slot)),
461 font_ref: ("minor".into(), Some(ColorRef::scheme(SchemeSlot::Lt1))),
462 }
463 }
464 pub fn line(slot: SchemeSlot) -> Self {
466 ShapeStyle {
467 line_ref: (1, ColorRef::scheme(slot)),
468 fill_ref: (0, ColorRef::scheme(slot)),
469 effect_ref: (0, ColorRef::scheme(slot)),
470 font_ref: ("minor".into(), Some(ColorRef::scheme(SchemeSlot::Tx1))),
471 }
472 }
473}