From 6e3b3c8013e6d8814dbf70c854e55d062bedbdf4 Mon Sep 17 00:00:00 2001 From: memdmp Date: Mon, 13 Jan 2025 16:54:34 +0100 Subject: chore: initial commit --- src/main.rs | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fed8090 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,199 @@ +use std::{fs, io::Result, os::unix}; + +use serde::{Deserialize, Serialize}; +use tiny_skia::Pixmap; +use xcursor::{ImageInfo, XCursorEncoder}; + +mod xcursor; + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +struct Xy { + x: u32, + y: u32, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct BibataPlatformNames { + windows: Option, + xorg: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct BibataFrame { + idx: i32, + id: String, + url: String, + delay: u32, + sha512: String, + svg: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct BibataCursor { + animated: bool, + id: String, + name: String, + aliases: Vec, + hot: Xy, + frames: Vec, + frame_count: i32, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct BibataCursorStyle { + kind: String, + cursors: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct BibataColourScheme<'a> { + pub base: &'a str, + pub outline: &'a str, + pub watch_background: &'a str, + pub watch_color: Vec<&'a str>, + /** Value between 0 and 1 - Bibata defaults to 0.8 */ + pub watch_opacity: f32, +} + +fn vector_to_pixmap(svg_data: &[u8]) -> Pixmap { + let tree = { + let mut opt = usvg::Options::default(); + // Get file's absolute directory. + // opt.resources_dir = std::fs::canonicalize(&args[1]) + // .ok() + // .and_then(|p| p.parent().map(|p| p.to_path_buf())); + + opt.fontdb_mut().load_system_fonts(); + + usvg::Tree::from_data(svg_data, &opt).unwrap() + }; + + let pixmap_size = tree.size().to_int_size(); + let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap(); + resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut()); + pixmap +} +fn resize_cursvg(svg: &str, new_size: &u16) -> String { + svg.to_string().replace( + "width=\"256\" height=\"256\"", + format!("width=\"{new_size}\" height=\"{new_size}\"").as_str(), + ) +} +fn apply_colours(unpatched_svg: String, colours: &BibataColourScheme) -> String { + if colours.watch_color.len() != 4 { + panic!("Must specify 4 watch colours!"); + }; + unpatched_svg + .replace("#00FF00", colours.base) + .replace("#0000FF", colours.outline) + .replace("#FF0000", colours.watch_background) + .replace("#F05024", colours.watch_color[0]) + .replace("#FCB813", colours.watch_color[1]) + .replace("#7EBA41", colours.watch_color[2]) + .replace("#32A0DA", colours.watch_color[3]) + .replace( + "fill-opacity=\"0.8\"", + format!("fill-opacity=\"{}\"", colours.watch_opacity).as_str(), + ) +} +fn process_xcursor( + cursor: BibataCursor, + desired_colours: BibataColourScheme, + desired_sizes: Vec, + time_scale: u32, +) -> Vec { + let mut image_info_vec: Vec = Vec::new(); + for cursor_frame in cursor.frames { + for desired_size in &desired_sizes { + let svg_data = apply_colours( + resize_cursvg(&cursor_frame.svg, desired_size), + &desired_colours, + ); + let pixmap = vector_to_pixmap(svg_data.as_bytes()); + + let pixels = { + let pixels = pixmap.pixels(); + let mut otherpixels: Vec = Vec::new(); + for elem in pixels.iter() { + otherpixels.push(elem.red()); + otherpixels.push(elem.green()); + otherpixels.push(elem.blue()); + otherpixels.push(elem.alpha()); + } + otherpixels + }; + let image_info = ImageInfo { + r#type: *desired_size as u32, + subtype: *desired_size as u32, + width: pixmap.width().try_into().unwrap(), + height: pixmap.height().try_into().unwrap(), + xhot: (cursor.hot.x * pixmap.width() / 256).try_into().unwrap(), + yhot: (cursor.hot.y * pixmap.height() / 256).try_into().unwrap(), + data: pixels.try_into().unwrap(), + delay: (time_scale / cursor_frame.delay).try_into().unwrap(), + }; + image_info_vec.push(image_info); + } + } + let mut encoder = XCursorEncoder::new(image_info_vec); + encoder.pack() +} + +fn main() -> Result<()> { + let cursor_style: Vec = + serde_json::from_str(include_str!("../.cursors.min")).unwrap(); + + // TODO: proper arg parsing, handling shit cutely, not hardcoding everything, etc + if fs::exists("xcursor-out")? { + fs::remove_dir_all("xcursor-out")?; + } + fs::create_dir("xcursor-out")?; + for style in cursor_style { + fs::create_dir(format!("xcursor-out/{}", style.kind))?; + for cursor in style.cursors { + println!("xcursor-out/{}/{}", style.kind, cursor.name.clone()); + // shove these files in a dir named `cursors` inside the theme dir + // the theme dir should have these 2 files in it: + // + // cursor.theme: + // [Icon Theme] + // Name=Bibata + // Inherits="Bibata" + // + // index.theme: + // [Icon Theme] + // Name=Bibata + // Comment=Generated Bibata Theme + // Inherits="hicolor" + // + // the theme dir should be ~/.local/share/icons/Bibata + // + // to apply on gnome: gsettings set org.gnome.desktop.interface cursor-theme Bibata + fs::write( + format!("xcursor-out/{}/{}", style.kind, cursor.name.clone()), + process_xcursor( + cursor.clone(), + BibataColourScheme { + base: "#ffffff", + outline: "#000000", + watch_background: "#ffffff", + watch_color: vec!["#ff5e9c", "#65c7e0", "#f69a59", "#6789f0"], + watch_opacity: 1.0, + }, + vec![ + 4, 6, 8, 10, 12, 16, 18, 24, 30, 32, 36, 42, 48, 54, 60, 66, 72, 128, + ], + 750, + ), + )?; + for alias in cursor.aliases { + unix::fs::symlink( + cursor.name.clone(), + format!("xcursor-out/{}/{}", style.kind, alias), + )?; + } + } + } + + Ok(()) +} -- cgit v1.2.3