#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum GradientType {
Linear,
Radial,
Rectangular,
Path,
}
impl GradientType {
pub fn xml_value(&self) -> &'static str {
match self {
GradientType::Linear => "lin",
GradientType::Radial => "circle",
GradientType::Rectangular => "rect",
GradientType::Path => "path",
}
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum GradientDirection {
Horizontal,
Vertical,
DiagonalDown,
DiagonalUp,
Custom(u32),
}
impl GradientDirection {
pub fn angle(&self) -> u32 {
match self {
GradientDirection::Horizontal => 0,
GradientDirection::Vertical => 5400000,
GradientDirection::DiagonalDown => 2700000,
GradientDirection::DiagonalUp => 18900000,
GradientDirection::Custom(deg) => deg * 60000,
}
}
}
#[derive(Clone, Debug)]
pub struct GradientStop {
pub position: u32,
pub color: String,
pub transparency: Option<u32>,
}
impl GradientStop {
pub fn new(position: u32, color: &str) -> Self {
GradientStop {
position: position.min(100000),
color: color.trim_start_matches('#').to_uppercase(),
transparency: None,
}
}
pub fn start(color: &str) -> Self {
Self::new(0, color)
}
pub fn middle(color: &str) -> Self {
Self::new(50000, color)
}
pub fn end(color: &str) -> Self {
Self::new(100000, color)
}
pub fn with_transparency(mut self, percent: u32) -> Self {
self.transparency = Some(percent.min(100) * 1000);
self
}
}
#[derive(Clone, Debug)]
pub struct GradientFill {
pub gradient_type: GradientType,
pub direction: GradientDirection,
pub stops: Vec<GradientStop>,
pub rotate_with_shape: bool,
}
impl GradientFill {
pub fn new(gradient_type: GradientType) -> Self {
GradientFill {
gradient_type,
direction: GradientDirection::Vertical,
stops: Vec::new(),
rotate_with_shape: true,
}
}
pub fn linear(direction: GradientDirection) -> Self {
let mut fill = Self::new(GradientType::Linear);
fill.direction = direction;
fill
}
pub fn radial() -> Self {
Self::new(GradientType::Radial)
}
pub fn two_color(start_color: &str, end_color: &str) -> Self {
Self::linear(GradientDirection::Vertical)
.add_stop(GradientStop::start(start_color))
.add_stop(GradientStop::end(end_color))
}
pub fn three_color(start_color: &str, middle_color: &str, end_color: &str) -> Self {
Self::linear(GradientDirection::Vertical)
.add_stop(GradientStop::start(start_color))
.add_stop(GradientStop::middle(middle_color))
.add_stop(GradientStop::end(end_color))
}
pub fn add_stop(mut self, stop: GradientStop) -> Self {
self.stops.push(stop);
self
}
pub fn with_direction(mut self, direction: GradientDirection) -> Self {
self.direction = direction;
self
}
pub fn with_rotate(mut self, rotate: bool) -> Self {
self.rotate_with_shape = rotate;
self
}
pub fn sorted(mut self) -> Self {
self.stops.sort_by_key(|s| s.position);
self
}
}
pub struct PresetGradients;
impl PresetGradients {
pub fn blue() -> GradientFill {
GradientFill::two_color("0066CC", "003366")
}
pub fn green() -> GradientFill {
GradientFill::two_color("00CC66", "006633")
}
pub fn red() -> GradientFill {
GradientFill::two_color("CC0000", "660000")
}
pub fn orange() -> GradientFill {
GradientFill::two_color("FF9900", "CC6600")
}
pub fn purple() -> GradientFill {
GradientFill::two_color("9933CC", "660099")
}
pub fn gray() -> GradientFill {
GradientFill::two_color("999999", "333333")
}
pub fn sunrise() -> GradientFill {
GradientFill::three_color("FF6600", "FFCC00", "FFFF66")
}
pub fn ocean() -> GradientFill {
GradientFill::three_color("003366", "0066CC", "66CCFF")
}
pub fn forest() -> GradientFill {
GradientFill::three_color("003300", "006600", "66CC66")
}
pub fn rainbow() -> GradientFill {
GradientFill::linear(GradientDirection::Horizontal)
.add_stop(GradientStop::new(0, "FF0000"))
.add_stop(GradientStop::new(17000, "FF9900"))
.add_stop(GradientStop::new(33000, "FFFF00"))
.add_stop(GradientStop::new(50000, "00FF00"))
.add_stop(GradientStop::new(67000, "0000FF"))
.add_stop(GradientStop::new(83000, "9900FF"))
.add_stop(GradientStop::new(100000, "FF00FF"))
}
}
pub fn generate_gradient_fill_xml(gradient: &GradientFill) -> String {
let mut xml = String::from(r#"<a:gradFill rotWithShape=""#);
xml.push_str(if gradient.rotate_with_shape { "1" } else { "0" });
xml.push_str(r#"">"#);
xml.push_str("<a:gsLst>");
for stop in &gradient.stops {
xml.push_str(&format!(
r#"<a:gs pos="{}"><a:srgbClr val="{}""#,
stop.position, stop.color
));
if let Some(alpha) = stop.transparency {
xml.push_str(&format!(r#"><a:alpha val="{}"/></a:srgbClr>"#, 100000 - alpha));
} else {
xml.push_str("/>");
}
xml.push_str("</a:gs>");
}
xml.push_str("</a:gsLst>");
match gradient.gradient_type {
GradientType::Linear => {
xml.push_str(&format!(
r#"<a:lin ang="{}" scaled="1"/>"#,
gradient.direction.angle()
));
}
GradientType::Radial | GradientType::Rectangular | GradientType::Path => {
xml.push_str(&format!(
r#"<a:path path="{}"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>"#,
gradient.gradient_type.xml_value()
));
}
}
xml.push_str("</a:gradFill>");
xml
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gradient_type_xml() {
assert_eq!(GradientType::Linear.xml_value(), "lin");
assert_eq!(GradientType::Radial.xml_value(), "circle");
}
#[test]
fn test_gradient_direction_angle() {
assert_eq!(GradientDirection::Horizontal.angle(), 0);
assert_eq!(GradientDirection::Vertical.angle(), 5400000);
assert_eq!(GradientDirection::Custom(45).angle(), 2700000);
}
#[test]
fn test_gradient_stop() {
let stop = GradientStop::new(50000, "#FF0000");
assert_eq!(stop.position, 50000);
assert_eq!(stop.color, "FF0000");
}
#[test]
fn test_gradient_stop_with_transparency() {
let stop = GradientStop::new(0, "000000").with_transparency(50);
assert_eq!(stop.transparency, Some(50000));
}
#[test]
fn test_two_color_gradient() {
let gradient = GradientFill::two_color("FF0000", "0000FF");
assert_eq!(gradient.stops.len(), 2);
assert_eq!(gradient.stops[0].color, "FF0000");
assert_eq!(gradient.stops[1].color, "0000FF");
}
#[test]
fn test_three_color_gradient() {
let gradient = GradientFill::three_color("FF0000", "00FF00", "0000FF");
assert_eq!(gradient.stops.len(), 3);
}
#[test]
fn test_preset_gradients() {
let blue = PresetGradients::blue();
assert_eq!(blue.stops.len(), 2);
let rainbow = PresetGradients::rainbow();
assert_eq!(rainbow.stops.len(), 7);
}
#[test]
fn test_generate_gradient_xml() {
let gradient = GradientFill::two_color("FF0000", "0000FF");
let xml = generate_gradient_fill_xml(&gradient);
assert!(xml.contains("gradFill"));
assert!(xml.contains("gsLst"));
assert!(xml.contains("FF0000"));
assert!(xml.contains("0000FF"));
}
#[test]
fn test_radial_gradient_xml() {
let gradient = GradientFill::radial()
.add_stop(GradientStop::start("FFFFFF"))
.add_stop(GradientStop::end("000000"));
let xml = generate_gradient_fill_xml(&gradient);
assert!(xml.contains("path"));
assert!(xml.contains("circle"));
}
}