From b21393ccf45aba5fcd1565113495ba7c20ffabf0 Mon Sep 17 00:00:00 2001
From: Eemeli <eemeli.o.lehtonen@utu.fi>
Date: Sun, 15 Jan 2023 05:43:30 +0200
Subject: [PATCH] framebuffer logger + some ansi escapes

---
 .cargo/runner.sh                      |   2 +
 Cargo.lock                            | 510 ++++++++++++++++++++++++++
 Cargo.toml                            |   3 +
 build.rs                              |  73 ++--
 src/arch/x86_64/idt.rs                |  38 +-
 src/arch/x86_64/limine/framebuffer.rs |  15 +-
 src/arch/x86_64/limine/mod.rs         |   3 -
 src/log.rs                            |  34 +-
 src/main.rs                           |  21 +-
 src/qemu.rs                           |   3 -
 src/video/font.rs                     | 325 ++++++++++++----
 src/video/framebuffer.rs              | 126 +++++--
 src/video/mod.rs                      |   1 +
 13 files changed, 1006 insertions(+), 148 deletions(-)

diff --git a/.cargo/runner.sh b/.cargo/runner.sh
index b7c557b..1f65173 100755
--- a/.cargo/runner.sh
+++ b/.cargo/runner.sh
@@ -41,6 +41,7 @@ target/limine/limine-deploy $KERNEL.iso
 if [ "$(basename $KERNEL)" = "hyperion" ]; then
     # Run the created image with QEMU.
     qemu-system-x86_64 \
+        -enable-kvm \
         -machine q35 \
         -cpu qemu64 \
         -M smm=off \
@@ -55,6 +56,7 @@ else
     set +e
     # Run the created image with QEMU.
     qemu-system-x86_64 \
+        -enable-kvm \
         -machine q35 \
         -cpu qemu64 \
         -M smm=off \
diff --git a/Cargo.lock b/Cargo.lock
index 1131368..e26fdd6 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 = "autocfg"
 version = "1.1.0"
@@ -20,10 +26,196 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
+[[package]]
+name = "bumpalo"
+version = "3.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
+
+[[package]]
+name = "bytemuck"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "either"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+
+[[package]]
+name = "exr"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eb5f255b5980bb0c8cf676b675d1a99be40f316881444f44e0462eaf5df5ded"
+dependencies = [
+ "bit_field",
+ "flume",
+ "half",
+ "lebe",
+ "miniz_oxide",
+ "smallvec",
+ "threadpool",
+]
+
+[[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 = "flume"
+version = "0.10.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "nanorand",
+ "pin-project",
+ "spin",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gif"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "half"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "hyperion"
 version = "0.1.0"
 dependencies = [
+ "image",
  "limine",
  "spin",
  "uart_16550",
@@ -31,6 +223,55 @@ dependencies = [
  "x86_64",
 ]
 
+[[package]]
+name = "image"
+version = "0.24.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "exr",
+ "gif",
+ "jpeg-decoder",
+ "num-rational",
+ "num-traits",
+ "png",
+ "scoped_threadpool",
+ "tiff",
+]
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
+dependencies = [
+ "rayon",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lebe"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
 [[package]]
 name = "limine"
 version = "0.1.9"
@@ -47,18 +288,184 @@ dependencies = [
  "scopeguard",
 ]
 
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "nanorand"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+
+[[package]]
+name = "pin-project"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "png"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "num_cpus",
+]
+
 [[package]]
 name = "rustversion"
 version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
 
+[[package]]
+name = "scoped_threadpool"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
 [[package]]
 name = "spin"
 version = "0.9.4"
@@ -68,6 +475,37 @@ dependencies = [
  "lock_api",
 ]
 
+[[package]]
+name = "syn"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "tiff"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471"
+dependencies = [
+ "flate2",
+ "jpeg-decoder",
+ "weezl",
+]
+
 [[package]]
 name = "uart_16550"
 version = "0.2.18"
@@ -79,12 +517,84 @@ dependencies = [
  "x86_64",
 ]
 
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
 [[package]]
 name = "volatile"
 version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3ca98349dda8a60ae74e04fd90c7fb4d6a4fbe01e6d3be095478aa0b76f6c0c"
 
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+
+[[package]]
+name = "weezl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
+
 [[package]]
 name = "x86_64"
 version = "0.14.10"
diff --git a/Cargo.toml b/Cargo.toml
index ecdebf3..49de3ad 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,3 +23,6 @@ x86_64 = "0.14.10"
 uart_16550 = "0.2.18"
 limine = { version = "0.1.9", optional = true }
 #tracing = { version = "0.1.37", default-features = false }
+
+[build-dependencies]
+image = "0.24.5"
diff --git a/build.rs b/build.rs
index 14da3e8..fd0ad99 100644
--- a/build.rs
+++ b/build.rs
@@ -1,11 +1,8 @@
 use std::{
     env::var,
     error::Error,
-    fs::{self, File, OpenOptions},
-    io::Read,
+    fs::{self, File},
     io::Write,
-    path::PathBuf,
-    process::Command,
 };
 
 //
@@ -43,30 +40,56 @@ fn main() -> Result<(), Box<dyn Error>> {
         panic!();
     };
 
-    let unifont_path = "target/hyperion/unifont.bmp";
-    let read_unifont = || {
-        let mut file = OpenOptions::new()
-            .read(true)
-            .create(false)
-            .write(false)
-            .open(unifont_path)?;
+    // generate kernel font from the bitmap image
 
-        let mut buf = Vec::new();
-        file.read_to_end(buf)?;
-        Ok::<_, Box<dyn Error>>(buf)
-    };
-
-    let unifont = if let Ok(file) = read_unifont() {
-        file
-    } else {
-        Command::new("wget")
-            .arg("http://unifoundry.com/pub/unifont/unifont-15.0.01/unifont-15.0.01.bmp")
-            .args(["-O", unifont_path])
-            .spawn()
+    let bmp_date = fs::metadata("./src/video/font.bmp")
+        .unwrap()
+        .modified()
+        .unwrap();
+    let rs_date = fs::metadata("./src/video/font.rs")
+        .unwrap()
+        .modified()
+        .unwrap();
+    // panic!("{bmp_date:?} {rs_date:?}");
+    if bmp_date > rs_date {
+        let mut generated_rs = File::options()
+            .write(true)
+            .truncate(true)
+            .open("./src/video/font.rs")
             .unwrap();
 
-        read_unifont().unwrap()
-    };
+        let bmp = image::open("./src/video/font.bmp").unwrap().to_luma8();
+        assert_eq!(bmp.width(), 4096);
+        assert_eq!(bmp.height(), 16);
+        write!(
+            generated_rs,
+            "pub static FONT: [([u16; 16], bool); 256] = ["
+        )
+        .unwrap();
+
+        for i in 0..=255_u8 {
+            let mut byte = ([0u16; 16], false);
+
+            bmp.chunks(16)
+                .skip(i as usize)
+                .step_by(256)
+                .enumerate()
+                .for_each(|(i, s)| {
+                    s.iter().enumerate().for_each(|(j, b)| {
+                        if *b != 255 {
+                            byte.0[i] |= 1 << j
+                        }
+                    })
+                });
+
+            // set the flag if the character is 16 wide instead of 8 wide
+            byte.1 = !byte.0.iter().all(|c| *c < 0x100);
+
+            write!(generated_rs, "\n\t{byte:?},").unwrap();
+        }
+
+        write!(generated_rs, "\n];").unwrap();
+    }
 
     Ok(())
 }
diff --git a/src/arch/x86_64/idt.rs b/src/arch/x86_64/idt.rs
index 6866baa..9c9f128 100644
--- a/src/arch/x86_64/idt.rs
+++ b/src/arch/x86_64/idt.rs
@@ -27,7 +27,43 @@ extern "x86-interrupt" fn double_fault(stack: InterruptStackFrame, ec: u64) -> !
         // TODO: This won't be safe when multiple CPUs are running
         crate::qemu::unlock();
     }
-    panic!("INT: Double fault ({ec})\n{stack:#?}")
+
+    crate::qemu::_print(format_args_nl!("INT: Double fault ({ec})\n{stack:#?}"));
+
+    let mut sp = stack.stack_pointer.as_ptr() as *const [u8; 8];
+    for i in 0isize..256 {
+        let sp = unsafe { sp.offset(i) };
+        let bytes: [u8; 8] = unsafe { *sp };
+        let graphic = |c: u8| {
+            if c.is_ascii_graphic() {
+                c as char
+            } else {
+                '.'
+            }
+        };
+        crate::qemu::_print(format_args_nl!(
+            "{:#x}:  {:02x} {:02x} {:02x} {:02x}  {:02x} {:02x} {:02x} {:02x}   {}{}{}{}{}{}{}{}",
+            sp as usize,
+            bytes[0],
+            bytes[1],
+            bytes[2],
+            bytes[3],
+            bytes[4],
+            bytes[5],
+            bytes[6],
+            bytes[7],
+            graphic(bytes[0]),
+            graphic(bytes[1]),
+            graphic(bytes[2]),
+            graphic(bytes[3]),
+            graphic(bytes[4]),
+            graphic(bytes[5]),
+            graphic(bytes[6]),
+            graphic(bytes[7]),
+        ));
+    }
+
+    panic!();
 }
 
 //
diff --git a/src/arch/x86_64/limine/framebuffer.rs b/src/arch/x86_64/limine/framebuffer.rs
index 8b53040..08b9332 100644
--- a/src/arch/x86_64/limine/framebuffer.rs
+++ b/src/arch/x86_64/limine/framebuffer.rs
@@ -1,6 +1,6 @@
 use crate::{
     println,
-    video::framebuffer::{Framebuffer, FBO},
+    video::framebuffer::{get_fbo, Framebuffer, FramebufferInfo, FBO},
 };
 use core::slice;
 use limine::LimineFramebufferRequest;
@@ -24,14 +24,17 @@ pub fn init() {
             let buf = unsafe { slice::from_raw_parts_mut(fb.address.as_ptr()?, fb.size()) };
             Some(Framebuffer {
                 buf,
-                width: fb.width as _,
-                height: fb.height as _,
-                pitch: fb.pitch as _,
+                info: FramebufferInfo {
+                    width: fb.width as _,
+                    height: fb.height as _,
+                    pitch: fb.pitch as _,
+                },
             })
         });
 
-    if let Some(fbo) = fbo {
+    if let Some(mut fbo) = fbo {
+        fbo.clear();
         FBO.call_once(|| Mutex::new(fbo));
     }
-    println!("Global framebuffer {:#?}", FBO.get())
+    println!("Global framebuffer: {:#?}", get_fbo().map(|f| f.info))
 }
diff --git a/src/arch/x86_64/limine/mod.rs b/src/arch/x86_64/limine/mod.rs
index 032aa5e..754f283 100644
--- a/src/arch/x86_64/limine/mod.rs
+++ b/src/arch/x86_64/limine/mod.rs
@@ -18,9 +18,6 @@ pub extern "C" fn _start() -> ! {
 
     framebuffer::init();
 
-    // the initial terminal logger crashes if used after initializing GDT and IDT
-    crate::log::disable_term();
-
     gdt::init();
     idt::init();
 
diff --git a/src/log.rs b/src/log.rs
index 92d39e7..0cb6a59 100644
--- a/src/log.rs
+++ b/src/log.rs
@@ -19,12 +19,20 @@ macro_rules! println {
 
 //
 
-pub fn enable_term() {
-    LOGGER.term.store(true, Ordering::SeqCst);
+// pub fn enable_term() {
+//     LOGGER.term.store(true, Ordering::SeqCst);
+// }
+//
+// pub fn disable_term() {
+//     LOGGER.term.store(false, Ordering::SeqCst);
+// }
+
+pub fn enable_fbo() {
+    LOGGER.fbo.store(true, Ordering::SeqCst);
 }
 
-pub fn disable_term() {
-    LOGGER.term.store(false, Ordering::SeqCst);
+pub fn disable_fbo() {
+    LOGGER.fbo.store(false, Ordering::SeqCst);
 }
 
 pub fn enable_qemu() {
@@ -40,24 +48,34 @@ pub fn disable_qemu() {
 static LOGGER: Lazy<Logger> = Lazy::new(Logger::init);
 
 struct Logger {
-    term: AtomicBool,
+    // Log to a bootloader given terminal
+    // term: AtomicBool,
+
+    // Log to a framebuffer
+    fbo: AtomicBool,
+
+    // Log to a QEMU serial
     qemu: AtomicBool,
 }
 
 impl Logger {
     fn init() -> Self {
         Logger {
-            term: true.into(),
+            // term: false.into(),
+            fbo: true.into(),
             qemu: true.into(),
         }
     }
 
     fn print(&self, args: Arguments) {
+        // if self.term.load(Ordering::SeqCst) {
+        //     crate::arch::boot::_print(args);
+        // }
         if self.qemu.load(Ordering::SeqCst) {
             crate::qemu::_print(args);
         }
-        if self.term.load(Ordering::SeqCst) {
-            crate::arch::boot::_print(args);
+        if self.fbo.load(Ordering::SeqCst) {
+            crate::video::logger::_print(args);
         }
     }
 }
diff --git a/src/main.rs b/src/main.rs
index 420484f..e7e77af 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,7 +11,10 @@
 
 use spin::Mutex;
 
-use crate::video::framebuffer::{Color, FBO};
+use crate::{
+    term::escape::encode::EscapeEncoder,
+    video::framebuffer::{get_fbo, Color, FBO},
+};
 
 //
 
@@ -20,6 +23,7 @@ pub mod arch;
 pub mod log;
 pub mod panic;
 pub mod qemu;
+pub mod term;
 #[cfg(test)]
 pub mod testfw;
 pub mod video;
@@ -39,8 +43,8 @@ pub static BOOTLOADER: Mutex<&'static str> = Mutex::new(KERNEL);
 //
 
 fn kernel_main() -> ! {
-    println!("Hello from {KERNEL}");
-    println!(" - {KERNEL} was booted with {}", BOOTLOADER.lock());
+    println!("Hello from {}", KERNEL.cyan());
+    println!(" - {} was booted with {}", KERNEL.cyan(), BOOTLOADER.lock());
 
     // error handling test
     // stack_overflow(79999999);
@@ -48,13 +52,10 @@ fn kernel_main() -> ! {
     //     *(0xFFFFFFFFDEADC0DE as *mut u8) = 42;
     // }
 
-    if let Some(fbo) = FBO.get() {
-        let mut fbo = fbo.lock();
-        fbo.fill(40, 40, 40, 40, Color::RED);
-        fbo.fill(50, 50, 60, 40, Color::GREEN);
-        fbo.fill(5, 15, 80, 20, Color::BLUE);
-
-        fbo.put_bytes(100, 100, b"ABABABAB\0", Color::WHITE, Color::BLACK);
+    if let Some(mut fbo) = get_fbo() {
+        fbo.fill(240, 240, 40, 40, Color::RED);
+        fbo.fill(250, 250, 60, 40, Color::GREEN);
+        fbo.fill(205, 215, 80, 20, Color::BLUE);
     }
 
     #[cfg(test)]
diff --git a/src/qemu.rs b/src/qemu.rs
index f65d38d..a2e715f 100644
--- a/src/qemu.rs
+++ b/src/qemu.rs
@@ -13,9 +13,6 @@ pub fn _print(args: Arguments) {
 }
 
 /// Unlocks the COM1 writer IF it is locked by this exact thread
-///
-/// SAFETY: this is unsafe unless called from the same thread, this is intended for double fault
-/// handling
 pub unsafe fn unlock() {
     // TODO: SMP
     // if COM1_LOCKER.load(Ordering::SeqCst) != crate::THREAD {
diff --git a/src/video/font.rs b/src/video/font.rs
index 25a6137..1224e22 100644
--- a/src/video/font.rs
+++ b/src/video/font.rs
@@ -1,67 +1,258 @@
-// #[derive(Debug, Clone, Copy, Default)]
-// pub struct FontChar {
-//     bitmap: [u8; 16],
-// }
-
-#[allow(clippy::unusual_byte_groupings)]
-pub static FONT: [[u8; 16]; 256] = {
-    let default = [
-        0b_11111111,
-        0b_11111111,
-        0b_11000011,
-        0b_11000011, //
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011, //
-        0b_11111111,
-        0b_11111111,
-    ];
-
-    let mut font = [default; 256];
-
-    font[b'A' as usize] = [
-        0b_00000000,
-        0b_00000000,
-        0b_00000000, //
-        0b_01111110,
-        0b_11111111,
-        0b_11100111,
-        0b_11000011,
-        0b_11111111,
-        0b_11111111,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_11000011,
-        0b_00000000, //
-        0b_00000000,
-        0b_00000000,
-    ];
-    font[b'B' as usize] = [
-        0b_00000000,
-        0b_00000000,
-        0b_00000000, //
-        0b_11111110,
-        0b_11111111,
-        0b_11000111,
-        0b_11000111,
-        0b_11111110,
-        0b_11111110,
-        0b_11000111,
-        0b_11000111,
-        0b_11111111,
-        0b_11111110,
-        0b_00000000, //
-        0b_00000000,
-        0b_00000000,
-    ];
-
-    font
-};
+pub static FONT: [([u16; 16], bool); 256] = [
+	([21845, 32768, 1, 32768, 1, 35410, 2647, 35418, 31123, 32768, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 51612, 19011, 64076, 19025, 51598, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 42460, 9347, 39052, 9361, 42126, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 42462, 9347, 39070, 9347, 42142, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 64926, 4675, 37470, 4675, 37278, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 45662, 19011, 51934, 27459, 62046, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 42380, 5203, 35934, 5203, 42386, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 35790, 2131, 35790, 2131, 64462, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40056, 649, 35960, 4233, 36472, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40772, 1093, 33916, 1093, 33860, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40708, 261, 40708, 261, 33148, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40772, 1093, 33860, 1065, 33808, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40828, 261, 40828, 261, 33028, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 36728, 4357, 36612, 2309, 37240, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 36472, 4357, 37176, 4417, 36412, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40824, 1029, 33848, 1089, 40764, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 47260, 2213, 47268, 2213, 48028, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 37262, 6227, 36946, 4179, 47502, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 39310, 8275, 36946, 2131, 47502, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 39310, 8275, 38994, 8275, 39310, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 41358, 12371, 43090, 14419, 41358, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 51602, 10839, 39510, 11227, 51794, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 51756, 22851, 59532, 18577, 51342, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40414, 9347, 40094, 9347, 40094, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 42188, 11555, 46562, 9507, 42284, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 37244, 6917, 38268, 4357, 37244, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 47708, 19011, 47692, 19025, 47502, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 62366, 2115, 35230, 2563, 61918, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 36472, 265, 34424, 2057, 34568, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 36464, 265, 34408, 2121, 34672, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 36408, 329, 34360, 2089, 34632, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 36424, 329, 34376, 2121, 34608, 1, 32768, 1, 32768, 1, 43690], true),
+	([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 0, 16, 16, 0, 0], false),
+	([0, 0, 68, 68, 68, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 72, 72, 72, 126, 36, 36, 126, 18, 18, 18, 0, 0], false),
+	([0, 0, 0, 0, 16, 124, 146, 18, 28, 112, 144, 146, 124, 16, 0, 0], false),
+	([0, 0, 0, 0, 140, 82, 82, 44, 16, 16, 104, 148, 148, 98, 0, 0], false),
+	([0, 0, 0, 0, 56, 68, 68, 40, 24, 148, 162, 66, 98, 156, 0, 0], false),
+	([0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 32, 16, 16, 8, 8, 8, 8, 8, 8, 16, 16, 32, 0], false),
+	([0, 0, 0, 4, 8, 8, 16, 16, 16, 16, 16, 16, 8, 8, 4, 0], false),
+	([0, 0, 0, 0, 0, 0, 16, 146, 84, 56, 84, 146, 16, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 16, 16, 16, 254, 16, 16, 16, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 16, 16, 8], false),
+	([0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 0, 0], false),
+	([0, 0, 0, 0, 64, 64, 32, 16, 16, 8, 8, 4, 2, 2, 0, 0], false),
+	([0, 0, 0, 0, 24, 36, 66, 98, 82, 74, 70, 66, 36, 24, 0, 0], false),
+	([0, 0, 0, 0, 16, 24, 20, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 64, 48, 8, 4, 2, 2, 126, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 64, 56, 64, 64, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 32, 48, 40, 36, 34, 34, 126, 32, 32, 32, 0, 0], false),
+	([0, 0, 0, 0, 126, 2, 2, 2, 62, 64, 64, 64, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 56, 4, 2, 2, 62, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 126, 64, 64, 32, 32, 32, 16, 16, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 66, 60, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 66, 124, 64, 64, 64, 32, 28, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 0, 24, 24, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 0, 24, 16, 16, 8, 0], false),
+	([0, 0, 0, 0, 0, 64, 32, 16, 8, 4, 8, 16, 32, 64, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 126, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 2, 4, 8, 16, 32, 16, 8, 4, 2, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 64, 32, 16, 16, 0, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 56, 68, 82, 106, 74, 74, 74, 114, 4, 120, 0, 0], false),
+	([0, 0, 0, 0, 24, 36, 36, 66, 66, 126, 66, 66, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 62, 66, 66, 66, 62, 66, 66, 66, 66, 62, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 2, 2, 2, 2, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 30, 34, 66, 66, 66, 66, 66, 66, 34, 30, 0, 0], false),
+	([0, 0, 0, 0, 126, 2, 2, 2, 62, 2, 2, 2, 2, 126, 0, 0], false),
+	([0, 0, 0, 0, 126, 2, 2, 2, 62, 2, 2, 2, 2, 2, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 2, 2, 114, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 0, 0, 66, 66, 66, 66, 126, 66, 66, 66, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 124, 16, 16, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 0, 0, 248, 32, 32, 32, 32, 32, 32, 34, 34, 28, 0, 0], false),
+	([0, 0, 0, 0, 66, 34, 18, 10, 6, 6, 10, 18, 34, 66, 0, 0], false),
+	([0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 126, 0, 0], false),
+	([0, 0, 0, 0, 66, 66, 102, 102, 90, 90, 66, 66, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 66, 70, 70, 74, 74, 82, 82, 98, 98, 66, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 62, 66, 66, 66, 62, 2, 2, 2, 2, 2, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 66, 66, 66, 66, 90, 102, 60, 192, 0], false),
+	([0, 0, 0, 0, 62, 66, 66, 66, 62, 18, 34, 34, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 2, 12, 48, 64, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 254, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 130, 130, 130, 68, 68, 68, 40, 40, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 66, 66, 66, 66, 90, 90, 102, 102, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 66, 66, 36, 36, 24, 24, 36, 36, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 130, 130, 68, 68, 40, 16, 16, 16, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 126, 64, 64, 32, 16, 8, 4, 2, 2, 126, 0, 0], false),
+	([0, 0, 0, 112, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 112, 0], false),
+	([0, 0, 0, 0, 2, 2, 4, 8, 8, 16, 16, 32, 64, 64, 0, 0], false),
+	([0, 0, 0, 14, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 14, 0], false),
+	([0, 0, 24, 36, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 0], false),
+	([0, 4, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 60, 66, 64, 124, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 0, 2, 2, 2, 58, 70, 66, 66, 66, 66, 70, 58, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 60, 66, 2, 2, 2, 2, 66, 60, 0, 0], false),
+	([0, 0, 0, 64, 64, 64, 92, 98, 66, 66, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 60, 66, 66, 126, 2, 2, 66, 60, 0, 0], false),
+	([0, 0, 0, 48, 8, 8, 8, 62, 8, 8, 8, 8, 8, 8, 0, 0], false),
+	([0, 0, 0, 0, 0, 64, 92, 34, 34, 34, 28, 4, 60, 66, 66, 60], false),
+	([0, 0, 0, 2, 2, 2, 58, 70, 66, 66, 66, 66, 66, 66, 0, 0], false),
+	([0, 0, 0, 16, 16, 0, 24, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 0, 32, 32, 0, 48, 32, 32, 32, 32, 32, 32, 32, 18, 12], false),
+	([0, 0, 0, 2, 2, 2, 34, 18, 10, 6, 10, 18, 34, 66, 0, 0], false),
+	([0, 0, 0, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 110, 146, 146, 146, 146, 146, 146, 146, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 58, 70, 66, 66, 66, 66, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 60, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 58, 70, 66, 66, 66, 66, 70, 58, 2, 2], false),
+	([0, 0, 0, 0, 0, 0, 92, 98, 66, 66, 66, 66, 98, 92, 64, 64], false),
+	([0, 0, 0, 0, 0, 0, 58, 70, 66, 2, 2, 2, 2, 2, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 60, 66, 2, 12, 48, 64, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 8, 8, 8, 62, 8, 8, 8, 8, 8, 48, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 66, 66, 66, 36, 36, 36, 24, 24, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 130, 146, 146, 146, 146, 146, 146, 108, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 66, 66, 36, 24, 24, 36, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 100, 88, 64, 64, 60], false),
+	([0, 0, 0, 0, 0, 0, 126, 64, 32, 16, 8, 4, 2, 126, 0, 0], false),
+	([0, 0, 0, 48, 8, 8, 16, 16, 8, 4, 8, 16, 16, 8, 8, 48], false),
+	([0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], false),
+	([0, 0, 0, 12, 16, 16, 8, 8, 16, 32, 16, 8, 8, 16, 16, 12], false),
+	([0, 0, 0, 140, 146, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([21845, 32768, 1, 32768, 1, 35790, 2131, 35794, 2131, 64462, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 47502, 19027, 52174, 19011, 47682, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 47506, 19027, 47710, 2643, 35218, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 51662, 19027, 63950, 18515, 51278, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 51666, 19031, 63962, 19027, 51666, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40238, 9573, 42404, 9509, 40238, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 35794, 2135, 35802, 2131, 64466, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 45980, 18499, 63884, 18961, 51662, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 45982, 18499, 63902, 18947, 51678, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 47570, 1171, 39070, 8339, 40082, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 48036, 8485, 41276, 8485, 39204, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 45986, 2339, 37154, 8469, 39176, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 47182, 18515, 51278, 18499, 48066, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 51278, 18515, 51278, 18499, 46018, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 33904, 1169, 33904, 1105, 33936, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 48028, 16451, 45452, 2577, 63950, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 48028, 16451, 45452, 16913, 47566, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 62350, 2131, 45138, 16467, 48014, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 41550, 12883, 41550, 8771, 61826, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 47694, 16979, 45646, 2627, 63874, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 47580, 1155, 39052, 8337, 40078, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 52124, 18499, 63554, 18499, 52124, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 37188, 4461, 38228, 6981, 37188, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 45532, 19011, 63948, 18513, 51278, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 45534, 19011, 63966, 18499, 51294, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 61852, 2627, 45644, 16977, 47502, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 55756, 17443, 50596, 17705, 55750, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 48028, 4163, 36940, 4177, 48014, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 48028, 4163, 37250, 4611, 47580, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 40816, 1033, 33840, 1089, 33848, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 62348, 2131, 35218, 2579, 61900, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 37176, 6985, 38200, 4361, 37128, 1, 32768, 1, 32768, 1, 43690], true),
+	([21845, 32768, 1, 32768, 1, 61900, 2643, 35294, 2131, 61522, 1, 32768, 1, 32768, 1, 43690], true),
+	([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 16, 16, 0, 16, 16, 16, 16, 16, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 16, 16, 124, 146, 18, 18, 146, 124, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 112, 8, 8, 8, 62, 8, 8, 8, 124, 134, 0, 0], false),
+	([0, 0, 0, 0, 0, 66, 60, 36, 66, 66, 36, 60, 66, 0, 0, 0], false),
+	([0, 0, 0, 0, 130, 68, 40, 16, 254, 16, 254, 16, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 16, 16, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 2, 60, 66, 66, 60, 64, 66, 60, 0, 0], false),
+	([36, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 153, 165, 133, 133, 165, 153, 66, 60, 0, 0], false),
+	([0, 0, 56, 64, 120, 68, 120, 0, 124, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 72, 72, 36, 36, 18, 36, 36, 72, 72, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 64, 64, 64, 0, 0], false),
+	([21845, 32768, 1, 50780, 10819, 37836, 4689, 37454, 1, 32768, 1, 33728, 1, 32768, 1, 43690], true),
+	([0, 0, 0, 0, 60, 66, 157, 165, 165, 157, 149, 165, 66, 60, 0, 0], false),
+	([0, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 24, 36, 36, 24, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 16, 16, 16, 254, 16, 16, 16, 0, 254, 0, 0, 0], false),
+	([0, 0, 0, 28, 34, 32, 24, 4, 2, 62, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 28, 34, 32, 28, 32, 34, 28, 0, 0, 0, 0, 0, 0], false),
+	([0, 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 102, 154, 2, 1], false),
+	([0, 0, 0, 0, 252, 94, 94, 94, 92, 80, 80, 80, 80, 80, 80, 0], false),
+	([0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 12], false),
+	([0, 0, 0, 8, 12, 10, 8, 8, 8, 62, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 56, 68, 68, 68, 56, 0, 124, 0, 0, 0, 0, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 18, 18, 36, 36, 72, 36, 36, 18, 18, 0, 0], false),
+	([0, 0, 0, 0, 68, 70, 36, 20, 20, 72, 104, 84, 114, 66, 0, 0], false),
+	([0, 0, 0, 0, 68, 70, 36, 20, 20, 40, 88, 68, 34, 114, 0, 0], false),
+	([0, 0, 0, 0, 70, 72, 36, 24, 22, 72, 104, 84, 114, 66, 0, 0], false),
+	([0, 0, 0, 0, 8, 8, 0, 8, 8, 4, 2, 66, 66, 60, 0, 0], false),
+	([12, 48, 0, 0, 24, 36, 36, 66, 66, 126, 66, 66, 66, 66, 0, 0], false),
+	([48, 12, 0, 0, 24, 36, 36, 66, 66, 126, 66, 66, 66, 66, 0, 0], false),
+	([24, 36, 0, 0, 24, 36, 36, 66, 66, 126, 66, 66, 66, 66, 0, 0], false),
+	([76, 50, 0, 0, 24, 36, 36, 66, 66, 126, 66, 66, 66, 66, 0, 0], false),
+	([36, 36, 0, 0, 24, 36, 36, 66, 66, 126, 66, 66, 66, 66, 0, 0], false),
+	([24, 36, 24, 0, 24, 36, 36, 66, 66, 126, 66, 66, 66, 66, 0, 0], false),
+	([0, 0, 0, 0, 248, 20, 18, 18, 254, 18, 18, 18, 18, 242, 0, 0], false),
+	([0, 0, 0, 0, 60, 66, 66, 2, 2, 2, 2, 66, 66, 60, 16, 12], false),
+	([12, 48, 0, 0, 126, 2, 2, 2, 62, 2, 2, 2, 2, 126, 0, 0], false),
+	([48, 12, 0, 0, 126, 2, 2, 2, 62, 2, 2, 2, 2, 126, 0, 0], false),
+	([24, 36, 0, 0, 126, 2, 2, 2, 62, 2, 2, 2, 2, 126, 0, 0], false),
+	([36, 36, 0, 0, 126, 2, 2, 2, 62, 2, 2, 2, 2, 126, 0, 0], false),
+	([24, 96, 0, 0, 124, 16, 16, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([48, 12, 0, 0, 124, 16, 16, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([24, 36, 0, 0, 124, 16, 16, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([36, 36, 0, 0, 124, 16, 16, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 0, 0, 30, 34, 66, 66, 79, 66, 66, 66, 34, 30, 0, 0], false),
+	([76, 50, 0, 0, 66, 70, 70, 74, 74, 82, 82, 98, 98, 66, 0, 0], false),
+	([12, 48, 0, 0, 60, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([48, 12, 0, 0, 60, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([24, 36, 0, 0, 60, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([76, 50, 0, 0, 60, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([36, 36, 0, 0, 60, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 0, 66, 36, 24, 36, 66, 0, 0, 0, 0], false),
+	([0, 0, 0, 64, 92, 34, 98, 82, 82, 74, 74, 70, 68, 58, 2, 0], false),
+	([12, 48, 0, 0, 66, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([48, 12, 0, 0, 66, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([24, 36, 0, 0, 66, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([36, 36, 0, 0, 66, 66, 66, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([48, 12, 0, 0, 130, 130, 68, 68, 40, 16, 16, 16, 16, 16, 0, 0], false),
+	([0, 0, 0, 2, 2, 30, 34, 66, 66, 34, 30, 2, 2, 2, 0, 0], false),
+	([0, 0, 0, 0, 28, 34, 34, 18, 26, 34, 66, 66, 74, 50, 0, 0], false),
+	([0, 0, 12, 48, 0, 0, 60, 66, 64, 124, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 48, 12, 0, 0, 60, 66, 64, 124, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 24, 36, 0, 0, 60, 66, 64, 124, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 76, 50, 0, 0, 60, 66, 64, 124, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 36, 36, 0, 0, 60, 66, 64, 124, 66, 66, 98, 92, 0, 0], false),
+	([0, 24, 36, 24, 0, 0, 60, 66, 64, 124, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 124, 146, 144, 252, 18, 18, 146, 124, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 60, 66, 2, 2, 2, 2, 66, 60, 16, 12], false),
+	([0, 0, 12, 48, 0, 0, 60, 66, 66, 126, 2, 2, 66, 60, 0, 0], false),
+	([0, 0, 48, 12, 0, 0, 60, 66, 66, 126, 2, 2, 66, 60, 0, 0], false),
+	([0, 0, 24, 36, 0, 0, 60, 66, 66, 126, 2, 2, 66, 60, 0, 0], false),
+	([0, 0, 36, 36, 0, 0, 60, 66, 66, 126, 2, 2, 66, 60, 0, 0], false),
+	([0, 0, 12, 48, 0, 0, 24, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 48, 12, 0, 0, 24, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 24, 36, 0, 0, 24, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 36, 36, 0, 0, 24, 16, 16, 16, 16, 16, 16, 124, 0, 0], false),
+	([0, 0, 76, 48, 40, 68, 64, 124, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 76, 50, 0, 0, 58, 70, 66, 66, 66, 66, 66, 66, 0, 0], false),
+	([0, 0, 12, 48, 0, 0, 60, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 48, 12, 0, 0, 60, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 24, 36, 0, 0, 60, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 76, 50, 0, 0, 60, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 36, 36, 0, 0, 60, 66, 66, 66, 66, 66, 66, 60, 0, 0], false),
+	([0, 0, 0, 0, 0, 0, 24, 0, 0, 126, 0, 0, 24, 0, 0, 0], false),
+	([0, 0, 0, 0, 0, 64, 60, 98, 82, 82, 74, 74, 70, 60, 2, 0], false),
+	([0, 0, 12, 48, 0, 0, 66, 66, 66, 66, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 48, 12, 0, 0, 66, 66, 66, 66, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 24, 36, 0, 0, 66, 66, 66, 66, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 36, 36, 0, 0, 66, 66, 66, 66, 66, 66, 98, 92, 0, 0], false),
+	([0, 0, 48, 12, 0, 0, 66, 66, 66, 66, 66, 100, 88, 64, 64, 60], false),
+	([0, 0, 0, 2, 2, 2, 58, 70, 66, 66, 66, 66, 70, 58, 2, 2], false),
+	([0, 0, 36, 36, 0, 0, 66, 66, 66, 66, 66, 100, 88, 64, 64, 60], false),
+];
\ No newline at end of file
diff --git a/src/video/framebuffer.rs b/src/video/framebuffer.rs
index e26a9cb..1d6d838 100644
--- a/src/video/framebuffer.rs
+++ b/src/video/framebuffer.rs
@@ -1,6 +1,9 @@
 use super::font::FONT;
-use core::fmt;
-use spin::{Mutex, Once};
+use core::{
+    fmt,
+    ops::{Deref, DerefMut},
+};
+use spin::{Lazy, Mutex, MutexGuard, Once};
 
 //
 
@@ -8,9 +11,20 @@ pub static FBO: Once<Mutex<Framebuffer>> = Once::new();
 
 //
 
+pub fn get_fbo() -> Option<MutexGuard<'static, Framebuffer>> {
+    FBO.get().map(|mtx| mtx.lock())
+}
+
+//
+
 pub struct Framebuffer {
     pub buf: &'static mut [u8],
 
+    pub info: FramebufferInfo,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct FramebufferInfo {
     pub width: usize, // not the pixels to the next row
     pub height: usize,
     pub pitch: usize, // pixels to the next row
@@ -39,24 +53,51 @@ impl Framebuffer {
         }
     }
 
-    pub fn put_byte(&mut self, x: usize, y: usize, ch: u8, fg: Color, bg: Color) {
-        let map = FONT[ch as usize];
+    pub fn put_byte(&mut self, x: usize, y: usize, ch: u8, fg: Color, bg: Color) -> bool {
+        let (map, double_wide) = FONT[ch as usize];
 
         for (yd, row) in map.into_iter().enumerate() {
-            for xd in 0..8 {
-                self.set(
-                    x + xd,
-                    y + yd,
-                    if (row & 1 << (7 - xd)) != 0 { fg } else { bg },
-                );
+            for xd in 0..if double_wide { 16 } else { 8 } {
+                self.set(x + xd, y + yd, if (row & 1 << xd) != 0 { fg } else { bg });
             }
         }
+
+        double_wide
     }
 
-    pub fn put_bytes(&mut self, x: usize, y: usize, s: &[u8], fg: Color, bg: Color) {
-        for (offs, ch) in s.iter().enumerate() {
-            self.put_byte(x + 12 * offs, y, *ch, fg, bg)
+    pub fn scroll(&mut self, h: usize) {
+        for y in h..self.height {
+            let two_rows = &mut self.buf[(y - 1) * self.info.pitch..(y + 1) * self.info.pitch];
+
+            self.buf.copy_within(
+                y * self.info.pitch..(y + 1) * self.info.pitch,
+                (y - h) * self.info.pitch,
+            );
         }
+
+        self.buf[(self.info.height - h) * self.info.pitch..].fill(0);
+    }
+
+    pub fn clear(&mut self) {
+        self.buf.fill(0);
+    }
+
+    pub fn info(&self) -> FramebufferInfo {
+        self.info
+    }
+}
+
+impl Deref for Framebuffer {
+    type Target = FramebufferInfo;
+
+    fn deref(&self) -> &Self::Target {
+        &self.info
+    }
+}
+
+impl DerefMut for Framebuffer {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.info
     }
 }
 
@@ -72,21 +113,56 @@ impl Color {
         Self { r, g, b }
     }
 
-    pub const fn as_u32(&self) -> u32 {
-        u32::from_ne_bytes([self.r, self.g, self.b, 0])
+    pub const fn from_u32(code: u32) -> Self {
+        let [r, g, b, _] = code.to_ne_bytes();
+        Self::new(r, g, b)
     }
 
-    pub const fn as_arr(&self) -> [u8; 4] {
-        [self.r, self.g, self.b, 0]
+    pub const fn from_hex(hex_code: &str) -> Self {
+        Self::from_hex_bytes(hex_code.as_bytes())
+    }
+
+    pub const fn from_hex_bytes(hex_code: &[u8]) -> Self {
+        match hex_code {
+            [r0, r1, g0, g1, b0, b1, _, _]
+            | [r0, r1, g0, g1, b0, b1]
+            | [b'#', r0, r1, g0, g1, b0, b1, _, _]
+            | [b'#', r0, r1, g0, g1, b0, b1] => {
+                Self::from_hex_bytes_2([*r0, *r1, *g0, *g1, *b0, *b1])
+            }
+            _ => {
+                panic!("Invalid color hex code")
+            }
+        }
+    }
+
+    pub const fn from_hex_bytes_2(hex_code: [u8; 6]) -> Self {
+        const fn parse_hex_char(c: u8) -> u8 {
+            match c {
+                b'0'..=b'9' => c - b'0',
+                b'a'..=b'f' => c - b'a' + 0xa,
+                _ => c,
+            }
+        }
+
+        const fn parse_byte(str_byte: [u8; 2]) -> u8 {
+            parse_hex_char(str_byte[0]) | parse_hex_char(str_byte[1]) << 4
+        }
+
+        let r = parse_byte([hex_code[0], hex_code[1]]);
+        let g = parse_byte([hex_code[2], hex_code[3]]);
+        let b = parse_byte([hex_code[4], hex_code[5]]);
+
+        Self::new(r, g, b)
     }
-}
 
-impl fmt::Debug for Framebuffer {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("Framebuffer")
-            .field("width", &self.width)
-            .field("height", &self.height)
-            .field("pitch", &self.pitch)
-            .finish_non_exhaustive()
+    pub const fn as_u32(&self) -> u32 {
+        // self.b as u32 | (self.g as u32) << 8 | (self.r as u32) << 16
+        u32::from_le_bytes([self.b, self.g, self.r, 0])
+    }
+
+    pub const fn as_arr(&self) -> [u8; 4] {
+        self.as_u32().to_ne_bytes()
+        // [self.r, self.g, self.b, 0]
     }
 }
diff --git a/src/video/mod.rs b/src/video/mod.rs
index 27cbe8d..7e1c0fb 100644
--- a/src/video/mod.rs
+++ b/src/video/mod.rs
@@ -1,2 +1,3 @@
 pub mod font;
 pub mod framebuffer;
+pub mod logger;
-- 
GitLab