From dd53cce7f6052bd1b08a1fc65fdbf48a794fe0d1 Mon Sep 17 00:00:00 2001 From: Compositr Date: Sat, 2 Nov 2024 11:56:25 +1100 Subject: [PATCH] feat: render to png + edits --- src/main.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 120 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index e09c37b..3218dca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,13 @@ -use std::{ffi::OsString, fs, net::TcpListener, process}; +use std::{net::TcpListener, process, rc::Rc}; use ctrlc; use http::responses::{Body, Response}; +use resvg::{ + tiny_skia::{Color, Pixmap, Transform}, + usvg, +}; + mod handlers; mod http; mod icons; @@ -19,10 +24,10 @@ fn main() { }) .unwrap_or_else(|_| fatal("Failed to set termination signal handler")); - let icons = match icons::Icons::build() { + let icons_rc = Rc::new(match icons::Icons::build() { Ok(icons) => icons, Err(e) => fatal(e), - }; + }); let mut handlers = handlers::Handlers::new(); @@ -34,28 +39,123 @@ fn main() { Response::new(req, 200, Body::Static("OK - luciders is running")) }); - handlers.add_handler("/icons/[icon].svg", move |req| { - if req.method != "GET" { - return Response::new(req, 405, Body::Static("Method Not Allowed")); + handlers.add_handler("/icons/[icon].svg", { + let icons = Rc::clone(&icons_rc); + move |req| { + if req.method != "GET" { + return Response::new(req, 405, Body::Static("Method Not Allowed")); + } + + let icon_filename = match req.url.get_path_segment(1) { + Some(icon) => icon, + None => return Response::new(req, 404, Body::Static("Icon Segment Not Found")), + }; + + let icon_name = &icon_filename[..icon_filename.len() - icons::ICON_FILE_EXTENSION_LEN]; + + if !icons.has_iconname(icon_name) { + return Response::new(req, 404, Body::Static("Icon Not Found")); + } + + let icon = match icons.get_icon(icon_name) { + Some(icon) => icon, + None => return Response::new(req, 500, Body::Static("Failed to read icon")), + }; + + return Response::new(req, 200, Body::Bytes(icon, "image/svg+xml")); } + }); - let icon_filename = match req.url.get_path_segment(1) { - Some(icon) => icon, - None => return Response::new(req, 404, Body::Static("Icon Not Found")), - }; + handlers.add_handler("/icons/[icon].png", { + let icons = Rc::clone(&icons_rc); + move |req| { + if req.method != "GET" { + return Response::new(req, 405, Body::Static("Method Not Allowed")); + } - let icon_name = &icon_filename[..icon_filename.len() - icons::ICON_FILE_EXTENSION_LEN]; + let icon_filename = match req.url.get_path_segment(1) { + Some(icon) => icon, + None => return Response::new(req, 404, Body::Static("Icon Segment Not Found")), + }; - if !icons.has_iconname(icon_name) { - return Response::new(req, 404, Body::Static("Icon Not Found")); + let icon_name = &icon_filename[..icon_filename.len() - icons::ICON_FILE_EXTENSION_LEN]; + + if !icons.has_iconname(icon_name) { + return Response::new(req, 404, Body::Static("Icon Not Found")); + } + + let icon = match icons.get_icon(icon_name) { + Some(icon) => icon, + None => return Response::new(req, 500, Body::Static("Failed to read icon")), + }; + + let tree = match usvg::Tree::from_data(&icon, &usvg::Options::default()) { + Ok(tree) => tree, + Err(_) => { + return Response::new(req, 500, Body::Static("Failed to load icon into tree")) + } + }; + + // Options handling + let mut scale = 1; + match req.url.query.get("scale") { + Some(scale_str) => { + match scale_str.parse::() { + Ok(scale_val) => { + if scale_val <= 0 || scale_val > 100 { + return Response::new(req, 400, Body::Static("Invalid scale value. Scale value must be an integer > 0 and <= 100")); + } + scale = scale_val; + } + Err(_) => { + return Response::new(req, 400, Body::Static("Invalid scale value. Scale value must be an integer > 0 and <= 100")); + } + } + } + None => {} + } + + let mut padding = 0; + match req.url.query.get("padding") { + Some(padding_str) => { + match padding_str.parse::() { + Ok(padding_val) => { + if padding_val > 100 { + return Response::new(req, 400, Body::Static("Invalid padding value. Padding value must be an integer >= 0 and <= 100")); + } + + padding = padding_val; + } + Err(_) => { + return Response::new(req, 400, Body::Static("Invalid padding value. Padding value must be an integer >= 0 and <= 100")); + } + } + } + None => {} + } + + let length = 24 * scale + padding; + + let mut pixmap = match Pixmap::new(length, length) { + Some(pixmap) => pixmap, + None => return Response::new(req, 500, Body::Static("Failed to create pixmap")), + }; + + let mut pixmap_mut = pixmap.as_mut(); + + resvg::render( + &tree, + Transform::from_scale(scale as f32, scale as f32) + .post_concat(Transform::from_translate(length as f32 / 2.0 - 12.0 * scale as f32, length as f32 / 2.0 - 12.0 * scale as f32)), + &mut pixmap_mut, + ); + let png = match pixmap.encode_png() { + Ok(png) => png, + Err(_) => return Response::new(req, 500, Body::Static("Failed to encode PNG")), + }; + + return Response::new(req, 200, Body::Bytes(png, "image/png")); } - - let icon = match icons.get_icon(icon_name) { - Some(icon) => icon, - None => return Response::new(req, 500, Body::Static("Failed to read icon")), - }; - - return Response::new(req, 200, Body::Bytes(icon, "image/svg+xml")); }); handlers.bind(listener);