diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d449a4b..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "artwork"] - path = artwork - url = https://git.makerlab-murnau.de/MakerLab/artwork.git diff --git a/Cargo.lock b/Cargo.lock index cc54d06..b1dfbe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "adler32" version = "1.2.0" @@ -17,6 +23,37 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "async-compression" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + [[package]] name = "async-trait" version = "0.1.66" @@ -25,7 +62,7 @@ checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -84,6 +121,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-macros" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb524613be645939e280b7279f7b017f98cf7f5ef084ec374df373530e73277" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "bitflags" version = "1.3.2" @@ -99,6 +154,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "1.4.0" @@ -129,6 +205,7 @@ checksum = "a8af54f5d48af1226928adc1f57edd22f5df1349e7da1fc96ae15cf43db0e871" dependencies = [ "bitflags", "cairo-sys-rs", + "freetype-rs", "libc", "once_cell", "thiserror", @@ -144,6 +221,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + [[package]] name = "cfg-expr" version = "0.11.0" @@ -168,6 +254,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -198,6 +293,16 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -213,6 +318,27 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "freetype-rs" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59c337e64822dd56a3a83ed75a662a470736bdb3a9fabfb588dff276b94a4e0" +dependencies = [ + "bitflags", + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643148ca6cbad6bec384b52fbe1968547d578c4efe83109e035c43a71734ff88" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "futures-channel" version = "0.3.27" @@ -228,6 +354,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + [[package]] name = "futures-task" version = "0.3.27" @@ -244,6 +376,7 @@ dependencies = [ "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -367,12 +500,31 @@ dependencies = [ "adler32", ] +[[package]] +name = "iri-string" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "itoa" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "libc" version = "0.2.140" @@ -403,6 +555,7 @@ name = "logo-generator" version = "0.1.0" dependencies = [ "axum", + "axum-macros", "cairo-rs", "mime_guess", "png", @@ -411,6 +564,8 @@ dependencies = [ "serde", "serde_json", "tokio", + "tower-http", + "tracing", ] [[package]] @@ -441,6 +596,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.6" @@ -545,7 +709,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -586,18 +750,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -678,7 +842,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 1.0.109", "walkdir", ] @@ -737,7 +901,7 @@ checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -792,6 +956,15 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -819,6 +992,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -855,7 +1039,7 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -886,7 +1070,20 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] @@ -920,6 +1117,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" dependencies = [ + "async-compression", + "base64", "bitflags", "bytes", "futures-core", @@ -927,10 +1126,19 @@ dependencies = [ "http", "http-body", "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", + "tracing", + "uuid", ] [[package]] @@ -954,9 +1162,21 @@ dependencies = [ "cfg-if", "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tracing-core" version = "0.1.30" @@ -993,6 +1213,15 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "uuid" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" +dependencies = [ + "getrandom", +] + [[package]] name = "version-compare" version = "0.1.1" @@ -1128,3 +1357,33 @@ name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 0995bc1..814fde9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] png = "0.11.0" -cairo-rs = { version = "0.17.0", default-features = false, features = ["png"] } +cairo-rs = { version = "0.17.0", default-features = false, features = ["png", "svg", "freetype"] } axum = "0.6.10" tokio = { version = "1.0", features = ["full"] } rand = "0.8.5" @@ -15,3 +15,6 @@ serde = { version = "1.0.152", features = ["derive"] } rust-embed = { version = "6.6.0", features = ["include-exclude"] } mime_guess = "2.0.4" serde_json = "1.0.94" +axum-macros = "0.3.7" +tower-http = { version = "0.4.0", features = ["fs", "full"] } +tracing = "0.1.37" diff --git a/artwork b/artwork deleted file mode 160000 index 13b39b2..0000000 --- a/artwork +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 13b39b2537350a24fe69a309e7f676a3c25f7bd0 diff --git a/fonts/Nunito-ExtraLight.ttf b/fonts/Nunito-ExtraLight.ttf new file mode 100644 index 0000000..430ae0f Binary files /dev/null and b/fonts/Nunito-ExtraLight.ttf differ diff --git a/fonts/Nunito-Light.ttf b/fonts/Nunito-Light.ttf new file mode 100644 index 0000000..42857b7 Binary files /dev/null and b/fonts/Nunito-Light.ttf differ diff --git a/fonts/Nunito-Regular.ttf b/fonts/Nunito-Regular.ttf new file mode 100644 index 0000000..dfd0fcb Binary files /dev/null and b/fonts/Nunito-Regular.ttf differ diff --git a/src/main.rs b/src/main.rs index 7a385d3..f4ba9be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,39 @@ -use axum::Router; -use std::net::SocketAddr; +use axum::{http::Request, response::Response, routing::get, Router}; +use std::{net::SocketAddr, time::Duration}; +use text::EmbeddedFonts; +use tower_http::{catch_panic::CatchPanicLayer, trace::TraceLayer}; +use tracing::Span; mod color; mod polygon; mod routes; +mod text; #[derive(Clone)] -pub struct SharedState {} +pub struct SharedState { + fonts: EmbeddedFonts, +} #[tokio::main] async fn main() { - let state = SharedState {}; + let state = SharedState { + fonts: EmbeddedFonts::load(), + }; let app = Router::new() .nest("/", routes::static_files::routes()) - .nest("/logo", routes::logo::routes()) + .route("/logo.png", get(routes::logo::png)) + .route("/logo.svg", get(routes::logo::svg)) .nest("/favicon.ico", routes::favicon::routes()) + .layer( + TraceLayer::new_for_http() + .on_request(|request: &Request<_>, _span: &Span| { + println!("Request {} {}", request.method(), request.uri()); + }) + .on_response(|response: &Response, latency: Duration, _span: &Span| { + println!("Response {}, {}ms", response.status(), latency.as_millis()); + }), + ) + .layer(CatchPanicLayer::new()) .with_state(state); // run it diff --git a/src/routes/logo.rs b/src/routes/logo.rs index 903c566..6c64dfc 100644 --- a/src/routes/logo.rs +++ b/src/routes/logo.rs @@ -1,9 +1,14 @@ -use axum::{extract::Query, routing::get, Router}; -use cairo::{Context, Format, ImageSurface}; -use rust_embed::RustEmbed; +use std::time::Instant; + +use axum::{ + extract::{Query, State}, + routing::get, + Router, +}; +use cairo::{freetype::Face, Context, FontFace, Format, ImageSurface, SvgSurface}; use serde::Deserialize; -use crate::{polygon, SharedState}; +use crate::{color::Color, polygon, text::DrawableText, SharedState}; #[derive(Deserialize, Default)] #[allow(non_camel_case_types)] @@ -22,60 +27,39 @@ enum LogoOrientation { } #[derive(Deserialize, Default)] -struct ImageProperties { +pub struct ImageProperties { #[serde(default)] variant: LogoVariant, #[serde(default)] orientation: LogoOrientation, } -#[derive(RustEmbed)] -#[folder = "artwork/logo"] -#[include = "*_T.png"] -struct ImageFiles; - -fn get_surface_and_logo_coordiates( - properties: &ImageProperties, -) -> (ImageSurface, (f64, f64), f64, f64) { - if let LogoVariant::NoText = properties.variant { - return ( - ImageSurface::create(Format::ARgb32, 400, 400).unwrap(), - (200.0, 200.0), - 148.0, - 67.0, - ); +fn get_surface_size(properties: &ImageProperties) -> (i32, i32) { + match (&properties.variant, &properties.orientation) { + (LogoVariant::NoText, _) => (400, 400), + (_, LogoOrientation::Landscape) => (2127, 591), + (_, LogoOrientation::Portrait) => (1654, 1654), } - - let background_image_path = match (&properties.variant, &properties.orientation) { - (LogoVariant::DarkText, LogoOrientation::Landscape) => "landscape/4C_T.png", - (LogoVariant::LightText, LogoOrientation::Landscape) => "landscape/W_T.png", - (LogoVariant::DarkText, LogoOrientation::Portrait) => "portrait/4C_T.png", - (LogoVariant::LightText, LogoOrientation::Portrait) => "portrait/W_T.png", - _ => unreachable!(), - }; - - let (coordinates, outer_radius, inner_radius) = match &properties.orientation { - LogoOrientation::Landscape => ((412.0, 299.0), 209.0, 99.0), - LogoOrientation::Portrait => ((828.0, 563.0), 253.0, 118.0), - }; - - let image = ImageFiles::get(background_image_path).unwrap(); - ( - ImageSurface::create_from_png(&mut image.data.as_ref()).unwrap(), - coordinates, - outer_radius, - inner_radius, - ) } -async fn handler(Query(properties): Query) -> impl axum::response::IntoResponse { - let (surface, logo_coordinates, logo_outer_radius, logo_inner_radius) = - get_surface_and_logo_coordiates(&properties); - let context = Context::new(&surface).unwrap(); +fn create_image_surface(properties: &ImageProperties) -> ImageSurface { + let (width, height) = get_surface_size(properties); + ImageSurface::create(Format::ARgb32, width, height).unwrap() +} - context.set_source_rgba(0.0, 0.0, 0.0, 0.0); +fn create_svg_surface(properties: &ImageProperties) -> SvgSurface { + let (width, height) = get_surface_size(properties); - context.paint().unwrap(); + SvgSurface::new::(width as f64, height as f64, None).unwrap() +} + +fn draw_logo(context: &Context, properties: &ImageProperties) { + let (logo_coordinates, logo_outer_radius, logo_inner_radius) = + match (&properties.variant, &properties.orientation) { + (LogoVariant::NoText, _) => ((200.0, 200.0), 148.0, 67.0), + (_, LogoOrientation::Landscape) => ((412.0, 299.0), 209.0, 99.0), + (_, LogoOrientation::Portrait) => ((828.0, 563.0), 253.0, 118.0), + }; polygon::draw_polygon_of_segmented_polygons( logo_coordinates, @@ -85,6 +69,81 @@ async fn handler(Query(properties): Query) -> impl axum::respon &context, ) .unwrap(); +} + +fn draw_text( + context: &Context, + font_regular: &Face, + font_light: &Face, + properties: &ImageProperties, +) { + if let LogoVariant::NoText = properties.variant { + return; + } + + let color = match &properties.variant { + LogoVariant::DarkText => Color::from("#383F50"), + LogoVariant::LightText => Color::from("#ffffff"), + _ => unreachable!(), + }; + + context.set_source_rgb( + color.r as f64 / 255.0, + color.g as f64 / 255.0, + color.b as f64 / 255.0, + ); + + let texts = match &properties.orientation { + LogoOrientation::Landscape => ( + DrawableText { + position: (760.0, 356.0), + size: 228.0, + spacing: 45.5, + text: "makerlab", + }, + DrawableText { + position: (1495.0, 504.0), + size: 110.0, + spacing: 10.5, + text: "MURNAU", + }, + ), + LogoOrientation::Portrait => ( + DrawableText { + position: (48.0, 1242.0), + size: 280.0, + spacing: 53.5, + text: "makerlab", + }, + DrawableText { + position: (493.0, 1424.0), + size: 135.0, + spacing: 13.0, + text: "MURNAU", + }, + ), + }; + + context.set_font_face(&FontFace::create_from_ft(font_regular).unwrap()); + texts.0.draw(&context); + + context.set_font_face(&FontFace::create_from_ft(font_light).unwrap()); + texts.1.draw(&context); +} + +#[axum_macros::debug_handler] +pub async fn png( + Query(properties): Query, + State(state): State, +) -> impl axum::response::IntoResponse { + let (font_regular, font_light) = state.fonts.get().await; + + // cannot use await after this, because surface does not implement Send + let surface = create_image_surface(&properties); + let context = Context::new(&surface).unwrap(); + + draw_logo(&context, &properties); + draw_text(&context, &font_regular, &font_light, &properties); let mut data: Vec = Vec::new(); surface.write_to_png(&mut data).unwrap(); @@ -95,6 +154,24 @@ async fn handler(Query(properties): Query) -> impl axum::respon ) } -pub fn routes() -> Router { - Router::new().route("/", get(handler)) +#[axum_macros::debug_handler] +pub async fn svg( + Query(properties): Query, + State(state): State, +) -> impl axum::response::IntoResponse { + let (font_regular, font_light) = state.fonts.get().await; + + // cannot use await after this, because surface does not implement Send + let surface = create_svg_surface(&properties); + let context = Context::new(&surface).unwrap(); + + draw_logo(&context, &properties); + draw_text(&context, &font_regular, &font_light, &properties); + + let mut data: Vec = Vec::new(); + surface.write_to_png(&mut data).unwrap(); + ( + axum::response::AppendHeaders([(axum::http::header::CONTENT_TYPE, "image/png")]), + data, + ) } diff --git a/src/text.rs b/src/text.rs new file mode 100644 index 0000000..f62e1b3 --- /dev/null +++ b/src/text.rs @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use cairo::{ + freetype::{Face, Library}, + Context, +}; +use rust_embed::RustEmbed; +use tokio::sync::Mutex; + +#[derive(RustEmbed)] +#[folder = "fonts"] +struct FontFiles; + +struct InternalFonts { + light: Face, + regular: Face, +} +unsafe impl Send for InternalFonts {} + +#[derive(Clone)] +pub struct EmbeddedFonts { + fonts: Arc>, +} + +impl EmbeddedFonts { + fn load_face(lib: &Library, name: &str) -> Result { + let font_file = FontFiles::get(name).unwrap(); + let font_data = Vec::from(font_file.data); + lib.new_memory_face(font_data, 0) + } + + pub fn load() -> EmbeddedFonts { + let lib = Library::init().unwrap(); + let font_regular = Self::load_face(&lib, "Nunito-Regular.ttf").unwrap(); + let font_light = Self::load_face(&lib, "Nunito-Light.ttf").unwrap(); + + let fonts = InternalFonts { + regular: font_regular, + light: font_light, + }; + + EmbeddedFonts { + fonts: Arc::new(Mutex::new(fonts)), + } + } + + pub async fn get(&self) -> (Face, Face) { + let fonts = self.fonts.lock().await; + (fonts.regular.clone(), fonts.light.clone()) + } +} + +pub struct DrawableText { + pub position: (f64, f64), + pub size: f64, + pub spacing: f64, + pub text: &'static str, +} + +impl DrawableText { + pub fn draw(&self, context: &Context) { + context.new_path(); + context.set_font_size(self.size); + context.line_to(self.position.0, self.position.1); + + for char in self.text.chars() { + context.text_path(&String::from(char)); + let (x, y) = context.current_point().unwrap(); + context.line_to(x + self.spacing, y); + } + context.close_path(); + context.fill().unwrap(); + } +}