use deckcraft_color::{ColorScheme, ColorTransform, Rgba, SchemeSlot};
use serde::{Deserialize, Serialize};
use crate::MediaId;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum ColorBase {
Rgb {
rgb: Rgba,
},
Scheme {
slot: SchemeSlot,
},
Preset {
name: String,
},
System {
name: String,
last: Rgba,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ColorRef {
pub base: ColorBase,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mods: Vec<ColorTransform>,
}
impl ColorRef {
pub fn rgb(c: Rgba) -> Self {
ColorRef {
base: ColorBase::Rgb { rgb: c },
mods: vec![],
}
}
pub fn scheme(slot: SchemeSlot) -> Self {
ColorRef {
base: ColorBase::Scheme { slot },
mods: vec![],
}
}
pub fn with(mut self, m: ColorTransform) -> Self {
self.mods.push(m);
self
}
pub fn resolve(&self, scheme: &ColorScheme, ph: Option<Rgba>) -> Rgba {
let base = match &self.base {
ColorBase::Rgb { rgb } => *rgb,
ColorBase::Scheme {
slot: SchemeSlot::PhClr,
} => ph.unwrap_or_else(|| scheme.get(SchemeSlot::Accent1)),
ColorBase::Scheme { slot } => scheme.get(*slot),
ColorBase::Preset { name } => deckcraft_color::preset(name).unwrap_or(Rgba::BLACK),
ColorBase::System { last, .. } => *last,
};
deckcraft_color::apply(base, &self.mods)
}
pub fn alpha(&self) -> f64 {
self.mods.iter().fold(1.0, |a, m| match m {
ColorTransform::Alpha(v) => (*v as f64 / 100_000.0).clamp(0.0, 1.0),
_ => a,
})
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GradientStop {
pub pos: f64,
pub color: ColorRef,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum GradientShape {
Linear {
angle: f64,
#[serde(default)]
scaled: bool,
},
Path { path: String, focus: [f64; 4] },
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Gradient {
pub stops: Vec<GradientStop>,
pub shape: GradientShape,
#[serde(default = "crate::yes")]
pub rotate_with_shape: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum PictureMode {
Stretch {
#[serde(default)]
fill_rect: [f64; 4],
},
Tile {
tx: f64,
ty: f64,
sx: f64,
sy: f64,
#[serde(default)]
flip: String,
#[serde(default)]
align: String,
},
}
impl Default for PictureMode {
fn default() -> Self {
PictureMode::Stretch {
fill_rect: [0.0; 4],
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct PictureFill {
pub media: MediaId,
pub crop: [f64; 4],
pub mode: PictureMode,
pub alpha: Option<f64>,
pub adjust: PictureAdjust,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct PictureAdjust {
pub brightness: f64,
pub contrast: f64,
pub saturation: Option<f64>,
pub grayscale: bool,
pub duotone: Option<(ColorRef, ColorRef)>,
pub clear_color: Option<Rgba>,
pub sharpen: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PatternFill {
pub preset: String,
pub fg: ColorRef,
pub bg: ColorRef,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum Fill {
None,
Solid {
color: ColorRef,
},
Gradient(Gradient),
Picture(PictureFill),
Pattern(PatternFill),
Group,
Background,
}
impl Fill {
pub fn solid(c: ColorRef) -> Self {
Fill::Solid { color: c }
}
pub fn is_none(&self) -> bool {
matches!(self, Fill::None)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum LineCap {
#[default]
Flat,
Round,
Square,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum LineJoin {
#[default]
Round,
Bevel,
Miter,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Compound {
#[default]
Single,
Double,
ThickThin,
ThinThick,
Triple,
}
impl Compound {
pub fn xml(self) -> &'static str {
match self {
Compound::Single => "sng",
Compound::Double => "dbl",
Compound::ThickThin => "thickThin",
Compound::ThinThick => "thinThick",
Compound::Triple => "tri",
}
}
pub fn from_xml(s: &str) -> Self {
match s {
"dbl" => Compound::Double,
"thickThin" => Compound::ThickThin,
"thinThick" => Compound::ThinThick,
"tri" => Compound::Triple,
_ => Compound::Single,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum Dash {
Preset { name: String },
Custom { pattern: Vec<(f64, f64)> },
}
impl Dash {
pub const PRESETS: [&'static str; 11] = [
"solid",
"sysDot",
"sysDash",
"dash",
"dashDot",
"lgDash",
"lgDashDot",
"lgDashDotDot",
"dot",
"sysDashDot",
"sysDashDotDot",
];
pub fn pattern(&self) -> Vec<f64> {
match self {
Dash::Custom { pattern } => pattern.iter().flat_map(|(d, s)| [*d, *s]).collect(),
Dash::Preset { name } => match name.as_str() {
"dot" => vec![1.0, 3.0],
"dash" => vec![4.0, 3.0],
"lgDash" => vec![8.0, 3.0],
"dashDot" => vec![4.0, 3.0, 1.0, 3.0],
"lgDashDot" => vec![8.0, 3.0, 1.0, 3.0],
"lgDashDotDot" => vec![8.0, 3.0, 1.0, 3.0, 1.0, 3.0],
"sysDash" => vec![3.0, 1.0],
"sysDot" => vec![1.0, 1.0],
"sysDashDot" => vec![3.0, 1.0, 1.0, 1.0],
"sysDashDotDot" => vec![3.0, 1.0, 1.0, 1.0, 1.0, 1.0],
_ => vec![],
},
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LineEnd {
pub kind: String,
#[serde(default = "med")]
pub w: String,
#[serde(default = "med")]
pub len: String,
}
fn med() -> String {
"med".into()
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Line {
pub fill: Option<Fill>,
pub width: Option<f64>,
pub dash: Option<Dash>,
pub cap: Option<LineCap>,
pub join: Option<LineJoin>,
pub compound: Option<Compound>,
pub head: Option<LineEnd>,
pub tail: Option<LineEnd>,
}
impl Line {
pub fn solid(c: ColorRef, width: f64) -> Self {
Line {
fill: Some(Fill::solid(c)),
width: Some(width),
..Default::default()
}
}
pub fn none() -> Self {
Line {
fill: Some(Fill::None),
..Default::default()
}
}
pub fn inherit(&mut self, parent: &Line) {
if self.fill.is_none() {
self.fill.clone_from(&parent.fill);
}
self.width = self.width.or(parent.width);
if self.dash.is_none() {
self.dash.clone_from(&parent.dash);
}
self.cap = self.cap.or(parent.cap);
self.join = self.join.or(parent.join);
self.compound = self.compound.or(parent.compound);
if self.head.is_none() {
self.head.clone_from(&parent.head);
}
if self.tail.is_none() {
self.tail.clone_from(&parent.tail);
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Shadow {
pub color: ColorRef,
pub blur: f64,
pub dist: f64,
pub dir: f64,
#[serde(default)]
pub inner: bool,
#[serde(default = "one")]
pub sx: f64,
#[serde(default = "one")]
pub sy: f64,
#[serde(default)]
pub kx: f64,
#[serde(default)]
pub ky: f64,
#[serde(default)]
pub align: String,
#[serde(default = "crate::yes")]
pub rotate_with_shape: bool,
}
fn one() -> f64 {
1.0
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Glow {
pub color: ColorRef,
pub radius: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Reflection {
pub blur: f64,
pub start_alpha: f64,
pub end_alpha: f64,
pub end_pos: f64,
pub dist: f64,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Effects {
pub outer_shadow: Option<Shadow>,
pub inner_shadow: Option<Shadow>,
pub glow: Option<Glow>,
pub soft_edge: Option<f64>,
pub reflection: Option<Reflection>,
pub bevel: Option<String>,
pub raw3d: Option<String>,
pub fill_overlay: Option<FillOverlay>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FillOverlay {
pub blend: String,
pub fill: Fill,
}
impl Effects {
pub fn is_empty(&self) -> bool {
self.outer_shadow.is_none()
&& self.inner_shadow.is_none()
&& self.glow.is_none()
&& self.soft_edge.is_none()
&& self.reflection.is_none()
&& self.bevel.is_none()
&& self.fill_overlay.is_none()
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShapeStyle {
pub line_ref: (u32, ColorRef),
pub fill_ref: (u32, ColorRef),
pub effect_ref: (u32, ColorRef),
pub font_ref: (String, Option<ColorRef>),
}
impl ShapeStyle {
pub fn accent(slot: SchemeSlot) -> Self {
ShapeStyle {
line_ref: (2, ColorRef::scheme(slot).with(ColorTransform::Shade(50000))),
fill_ref: (1, ColorRef::scheme(slot)),
effect_ref: (0, ColorRef::scheme(slot)),
font_ref: ("minor".into(), Some(ColorRef::scheme(SchemeSlot::Lt1))),
}
}
pub fn line(slot: SchemeSlot) -> Self {
ShapeStyle {
line_ref: (1, ColorRef::scheme(slot)),
fill_ref: (0, ColorRef::scheme(slot)),
effect_ref: (0, ColorRef::scheme(slot)),
font_ref: ("minor".into(), Some(ColorRef::scheme(SchemeSlot::Tx1))),
}
}
}