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..791e89ca93a42cf0fe4b72d0bea3031bce7ff17c
--- /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 = 5;
+const PADDING_Y: u32 = 5;
+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;
+		}
+	}
+}