use cairo::Context;
use pango::{Alignment, FontDescription, Weight};
use pangocairo::functions as pangocairo_fn;
#[derive(Debug, Clone, PartialEq)]
pub struct Style {
pub font_family: String,
pub font_weight: Weight,
pub size_pt: u32,
pub text_rgba: (f64, f64, f64, f64),
pub border_rgba: (f64, f64, f64, f64),
pub thickness: i32,
pub alignment: Alignment,
}
pub fn parse_hex(hex: &str) -> (f64, f64, f64, f64) {
let clean = hex.trim_start_matches('#');
let normalized = match clean.len() {
3 => {
let mut s = String::with_capacity(8);
for c in clean.chars() {
s.push(c);
s.push(c);
}
s.push_str("ff");
s
}
4 => {
let mut s = String::with_capacity(8);
for c in clean.chars() {
s.push(c);
s.push(c);
}
s
}
6 => format!("{clean}ff"),
8 => clean.to_string(),
_ => "ffffffff".to_string(),
};
let r = f64::from(u8::from_str_radix(&normalized[0..2], 16).unwrap_or(255)) / 255.0;
let g = f64::from(u8::from_str_radix(&normalized[2..4], 16).unwrap_or(255)) / 255.0;
let b = f64::from(u8::from_str_radix(&normalized[4..6], 16).unwrap_or(255)) / 255.0;
let a = f64::from(u8::from_str_radix(&normalized[6..8], 16).unwrap_or(255)) / 255.0;
(r, g, b, a)
}
pub fn parse_font_weight(weight: &str) -> Weight {
match weight.to_lowercase().as_str() {
"thin" => Weight::Thin,
"ultralight" | "ultra-light" => Weight::Ultralight,
"light" => Weight::Light,
"semilight" | "semi-light" => Weight::Semilight,
"book" => Weight::Book,
"normal" => Weight::Normal,
"medium" => Weight::Medium,
"semibold" | "semi-bold" => Weight::Semibold,
"bold" => Weight::Bold,
"ultrabold" | "ultra-bold" => Weight::Ultrabold,
"heavy" | "black" => Weight::Heavy,
"ultraheavy" | "ultra-heavy" => Weight::Ultraheavy,
_ => Weight::Heavy,
}
}
pub fn parse_alignment(align: &str) -> Alignment {
match align.to_lowercase().as_str() {
"left" => Alignment::Left,
"right" => Alignment::Right,
_ => Alignment::Center,
}
}
pub struct ClockRenderer;
impl ClockRenderer {
pub fn new() -> Self {
Self
}
pub fn draw(&self, cr: &Context, text: &str, style: &Style) {
if text.is_empty() {
return;
}
let layout = pangocairo::create_layout(cr);
layout.set_text(text);
let mut font_desc = FontDescription::new();
font_desc.set_family(&style.font_family);
font_desc.set_size(style.size_pt as i32 * pango::SCALE);
font_desc.set_weight(style.font_weight);
layout.set_font_description(Some(&font_desc));
layout.set_alignment(style.alignment);
cr.translate(f64::from(style.thickness), f64::from(style.thickness));
pangocairo_fn::layout_path(cr, &layout);
if style.thickness > 0 {
let (r, g, b, a) = style.border_rgba;
cr.set_source_rgba(r, g, b, a);
cr.set_line_width(f64::from(style.thickness) * 2.0);
cr.set_line_join(cairo::LineJoin::Round);
let _ = cr.stroke_preserve();
}
let (r, g, b, a) = style.text_rgba;
cr.set_source_rgba(r, g, b, a);
let _ = cr.fill();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_hex() {
let (r, g, b, a) = parse_hex("#00b7ff");
assert!((r - 0.0).abs() < 1e-4);
assert!((g - (183.0 / 255.0)).abs() < 1e-4);
assert!((b - 1.0).abs() < 1e-4);
assert!((a - 1.0).abs() < 1e-4);
}
#[test]
fn test_parse_font_weight() {
assert_eq!(parse_font_weight("bold"), Weight::Bold);
assert_eq!(parse_font_weight("light"), Weight::Light);
assert_eq!(parse_font_weight("invalid"), Weight::Heavy);
}
#[test]
fn test_parse_alignment() {
assert_eq!(parse_alignment("left"), Alignment::Left);
assert_eq!(parse_alignment("right"), Alignment::Right);
assert_eq!(parse_alignment("center"), Alignment::Center);
}
}