diff --git a/Cargo.lock b/Cargo.lock index ab6188dd69e175688d8df09bab5aba45d1d0fa63..ad41dd1005078d12266268bd5aba249b90aabc52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "adler" version = "0.2.3" @@ -12,6 +14,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "async-trait" version = "0.1.41" @@ -65,7 +76,7 @@ checksum = "1374191e2dd25f9ae02e3aa95041ed5d747fc77b3c102b49fe2dd9a8117a6244" dependencies = [ "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -111,6 +122,7 @@ dependencies = [ "phf", "png", "reqwest", + "rusttype", "serde", "serde_json", "serenity", @@ -145,7 +157,7 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", + "num-traits 0.2.14", "serde", "time", "winapi 0.3.9", @@ -204,7 +216,7 @@ dependencies = [ "diesel_derives", "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.14", "pq-sys", ] @@ -775,6 +787,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "num-bigint" version = "0.2.6" @@ -783,7 +801,7 @@ checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" dependencies = [ "autocfg", "num-integer", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -804,7 +822,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", - "num-traits", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -871,6 +898,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb5259643245d3f292c7a146b2df53bba24d7eab159410e648eb73dc164669d" +dependencies = [ + "num-traits 0.1.43", + "unreachable", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1231,6 +1268,17 @@ dependencies = [ "webpki", ] +[[package]] +name = "rusttype" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ff03da02f6d340bbee5ec55eed03ff9abd6ea013b93bc7c35973cc28f65999" +dependencies = [ + "arrayvec", + "ordered-float", + "stb_truetype 0.2.8", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1405,6 +1453,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stb_truetype" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1bec4382294c5a680fcebd29f8451e8d8c04479a026f6909004e2ab1cb425d" +dependencies = [ + "stb_truetype 0.3.1", +] + +[[package]] +name = "stb_truetype" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51" +dependencies = [ + "byteorder", +] + [[package]] name = "syn" version = "0.15.44" @@ -1663,6 +1729,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -1699,6 +1774,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "want" version = "0.3.0" @@ -1887,6 +1968,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff711e02a0840644b63e9906fd12bc15e7346f08531467b3962b3ed712bdd6ba" dependencies = [ "num-derive", - "num-traits", + "num-traits 0.2.14", "phf", ] diff --git a/Cargo.toml b/Cargo.toml index c738368f406adb776162ba684964814c61dcc44e..3d6dc6b496ae8a92c13f76e32e573fa260bfd94a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ diesel = { version = "1.4", features = ["postgres", "numeric"] } dotenv = "0.15.0" bigdecimal = "0.1.2" reqwest = { version = "0.10", features = ["json"] } -serde_json = "1.0" \ No newline at end of file +serde_json = "1.0" +rusttype = "0.4.3" diff --git a/src/amongus.ttf b/src/amongus.ttf new file mode 100644 index 0000000000000000000000000000000000000000..359054c46a43ce71ef78e3d43397d20d14a49946 Binary files /dev/null and b/src/amongus.ttf differ diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 730a63e8b2d6a1d2130b04a4c0fc8bdb0aa05827..f02fdb00fa8c4adc783f09bed92131a3e6631b27 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,9 +1,11 @@ mod joke; mod react; +mod sus; mod xbasic; use crate::handlers::joke::*; use crate::handlers::react::*; +use crate::handlers::sus::*; use crate::handlers::xbasic::*; use serenity::async_trait; @@ -46,6 +48,7 @@ impl Default for Dispatcher { Box::new(XbasicHandler::default()), Box::new(JokeHandler::default()), Box::new(ReactHandler::default()), + Box::new(SusHandler::default()), ], } } diff --git a/src/handlers/sus.rs b/src/handlers/sus.rs new file mode 100644 index 0000000000000000000000000000000000000000..82856b0c1e01a089059464777dd1eaf3f43baee8 --- /dev/null +++ b/src/handlers/sus.rs @@ -0,0 +1,160 @@ +use crate::framebuffer::FrameBuffer; +use crate::handlers::LineHandler; +use rusttype::LayoutIter; +use rusttype::{point, FontCollection, Scale}; +use serenity::async_trait; +use serenity::http::AttachmentType; +use serenity::model::channel::Message; +use serenity::prelude::*; +use std::borrow::Cow; + +const COLOUR: (u8, u8, u8) = (255, 0, 0); +const SCALE: Scale = Scale { x: 48.0, y: 48.0 }; +const PADDING_X: u32 = 0; +const PADDING_Y: u32 = 0; +const MAX_WIDTH: i32 = 800; +const Y_GAP: i32 = 5; + +// Wrap text when width exceeds MAX_WIDTH +struct WrappingLayoutIter<'a> { + iter: LayoutIter<'a, 'a>, + offset_x: i32, + offset_y: i32, + cur_x: i32, + cur_y: i32, +} + +impl<'a> WrappingLayoutIter<'a> { + fn new(iter: LayoutIter<'a, 'a>, offset_x: i32, offset_y: i32) -> Self { + Self { + iter, + offset_x, + offset_y, + cur_x: 0, + cur_y: 0, + } + } +} + +impl<'a> Iterator for WrappingLayoutIter<'a> { + type Item = Vec<(i32, i32, f32)>; + + fn next(&mut self) -> Option<Self::Item> { + let (glyph, bounding_box) = loop { + let glyph = self.iter.next()?; + if let Some(bb) = glyph.pixel_bounding_box() { + break (glyph, bb); + } + }; + + if bounding_box.max.x + self.cur_x > MAX_WIDTH { + self.cur_x = -bounding_box.min.x; + self.cur_y += bounding_box.max.y - bounding_box.min.y + Y_GAP; + } + + let mut buf = Vec::new(); + + glyph.draw(|x, y, a| { + let x = x as i32 + bounding_box.min.x + self.offset_x + self.cur_x; + let y = y as i32 + bounding_box.min.y + self.offset_y + self.cur_y; + + buf.push((x, y, a)); + }); + + Some(buf) + } +} + +#[derive(Default)] +pub struct SusHandler; + +impl SusHandler { + async fn render_font(&self, ctx: &Context, msg: &Message, s: &str) { + let font_data = include_bytes!("../amongus.ttf"); + let collection = FontCollection::from_bytes(font_data as &[u8]); + let font = collection.into_font().unwrap(); + + let start = point(0.0, 0.0); + + // Find image dimensions + let mut min_x = 0; + let mut max_x = 0; + let mut max_y = 0; + let mut min_y = 0; + for arr in WrappingLayoutIter::new(font.layout(s, SCALE, start), 0, 0) { + for (x, y, _) in arr { + if x < min_x { + min_x = x; + } + + if x > max_x { + max_x = x; + } + + if y < min_y { + min_y = y; + } + + if y > max_y { + max_y = y; + } + } + } + + let offset_x = -min_x; + let offset_y = -min_y; + max_x += offset_x; + max_y += offset_y; + + let mut framebuffer = + FrameBuffer::new(max_x as u32 + PADDING_X * 2, max_y as u32 + PADDING_Y * 2); + + for arr in WrappingLayoutIter::new( + font.layout(s, SCALE, start), + offset_x + PADDING_X as i32, + offset_y + PADDING_Y as i32, + ) { + for (x, y, a) in arr { + framebuffer.set_pixel( + x as u32, + y as u32, + COLOUR.0, + COLOUR.1, + COLOUR.2, + (a * 255.0) as u8, + ); + } + } + + let buf = framebuffer.as_png_vec(); + + msg.channel_id + .send_message(&ctx, |e| { + e.add_file(AttachmentType::Bytes { + data: Cow::Borrowed(&buf), + filename: "output.png".to_string(), + }); + + e + }) + .await + .unwrap(); + } +} + +#[async_trait] +impl LineHandler for SusHandler { + async fn line(&self, ctx: &Context, msg: &Message, line: &str) { + if line.starts_with("!SUS") { + let s = line.split(' ').collect::<Vec<_>>()[1..] + .join(" ") + .to_uppercase(); + + if s.is_empty() { + return; + } + + self.render_font(ctx, msg, &s).await; + } + } +}