Files
piglet/src/parser/gradient.rs
Sebastian Krüger 24f2dae24f Fix formatting: move #[allow(dead_code)] to separate line
Cargo fmt requires attribute macros to be on their own line.
This fixes the CI lint failure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 03:27:10 +01:00

123 lines
3.6 KiB
Rust

use crate::parser::color::Color;
use anyhow::{bail, Result};
#[derive(Debug, Clone)]
pub struct ColorStop {
pub color: Color,
pub position: f64,
}
#[derive(Debug, Clone)]
pub struct Gradient {
pub stops: Vec<ColorStop>,
#[allow(dead_code)]
pub angle: f64,
}
impl Gradient {
pub fn new(stops: Vec<ColorStop>, angle: f64) -> Self {
Self { stops, angle }
}
pub fn parse(gradient_str: &str) -> Result<Self> {
let gradient_str = gradient_str.trim();
if !gradient_str.starts_with("linear-gradient(") {
bail!("Only linear-gradient is supported");
}
let content = gradient_str
.strip_prefix("linear-gradient(")
.and_then(|s| s.strip_suffix(")"))
.ok_or_else(|| anyhow::anyhow!("Invalid gradient syntax"))?;
let parts: Vec<&str> = content.split(',').map(|s| s.trim()).collect();
if parts.is_empty() {
bail!("Gradient must have at least one color");
}
let mut angle = 180.0;
let mut color_parts = parts.as_slice();
if let Some(first) = parts.first() {
if first.ends_with("deg") {
angle = first
.trim_end_matches("deg")
.trim()
.parse()
.unwrap_or(180.0);
color_parts = &parts[1..];
} else if first.starts_with("to ") {
angle = match first.trim() {
"to right" => 90.0,
"to left" => 270.0,
"to top" => 0.0,
"to bottom" => 180.0,
_ => 180.0,
};
color_parts = &parts[1..];
}
}
let mut stops = Vec::new();
let count = color_parts.len();
for (i, part) in color_parts.iter().enumerate() {
let part = part.trim();
let mut color_str = part;
let mut position = i as f64 / (count - 1).max(1) as f64;
// Check if there's a percentage (e.g., "#FF5733 50%" or "red 50%")
if let Some(percent_pos) = part.rfind('%') {
// Find the last space before the percentage
if let Some(space_pos) = part[..percent_pos].rfind(|c: char| c.is_whitespace()) {
color_str = part[..space_pos].trim();
let percent_str = part[space_pos + 1..percent_pos].trim();
if let Ok(p) = percent_str.parse::<f64>() {
position = p / 100.0;
}
}
}
let color = Color::parse(color_str)?;
stops.push(ColorStop { color, position });
}
Ok(Self::new(stops, angle))
}
pub fn color_at(&self, t: f64) -> Color {
if self.stops.is_empty() {
return Color::new(255, 255, 255);
}
if self.stops.len() == 1 {
return self.stops[0].color;
}
let t = t.clamp(0.0, 1.0);
for i in 0..self.stops.len() - 1 {
let stop1 = &self.stops[i];
let stop2 = &self.stops[i + 1];
if t >= stop1.position && t <= stop2.position {
let local_t = (t - stop1.position) / (stop2.position - stop1.position);
return stop1.color.interpolate(&stop2.color, local_t);
}
}
self.stops.last().unwrap().color
}
pub fn colors(&self, steps: usize) -> Vec<Color> {
(0..steps)
.map(|i| {
let t = i as f64 / (steps - 1).max(1) as f64;
self.color_at(t)
})
.collect()
}
}