diff --git a/Cargo.lock b/Cargo.lock
index e9ff38dc659b1766bfd5a6c8bec247bab9ab5004..d3b708d5cf91e6b6c7e6307d8772a58bf627cc5f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -120,15 +120,6 @@ dependencies = [
  "num-traits",
 ]
 
-[[package]]
-name = "arrayvec"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
-dependencies = [
- "nodrop",
-]
-
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -302,8 +293,8 @@ source = "git+https://github.com/bevyengine/bevy?rev=38c7d5eb9e81ab8e1aec0367359
 dependencies = [
  "Inflector",
  "bevy_macro_utils",
- "quote 1.0.10",
- "syn 1.0.81",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -341,9 +332,9 @@ version = "0.5.0"
 source = "git+https://github.com/bevyengine/bevy?rev=38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c#38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c"
 dependencies = [
  "bevy_macro_utils",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -440,6 +431,7 @@ dependencies = [
  "crevice",
  "kayak_components",
  "kayak_core",
+ "kayak_font",
  "kayak_render_macros",
  "serde",
  "serde_json",
@@ -466,8 +458,8 @@ version = "0.5.0"
 source = "git+https://github.com/bevyengine/bevy?rev=38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c#38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c"
 dependencies = [
  "cargo-manifest",
- "quote 1.0.10",
- "syn 1.0.81",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -538,9 +530,9 @@ version = "0.5.0"
 source = "git+https://github.com/bevyengine/bevy?rev=38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c#38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c"
 dependencies = [
  "bevy_macro_utils",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
  "uuid",
 ]
 
@@ -820,8 +812,8 @@ dependencies = [
  "lazy_static",
  "lazycell",
  "peeking_take_while",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
+ "proc-macro2",
+ "quote",
  "regex",
  "rustc-hash",
  "shlex",
@@ -875,9 +867,9 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -1221,9 +1213,9 @@ name = "crevice-derive"
 version = "0.8.0"
 source = "git+https://github.com/bevyengine/bevy?rev=38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c#38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -1323,10 +1315,10 @@ checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
 dependencies = [
  "fnv",
  "ident_case",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
+ "proc-macro2",
+ "quote",
  "strsim",
- "syn 1.0.81",
+ "syn",
 ]
 
 [[package]]
@@ -1336,8 +1328,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
 dependencies = [
  "darling_core",
- "quote 1.0.10",
- "syn 1.0.81",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -1356,9 +1348,9 @@ version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -1377,9 +1369,9 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56f9c37d395456d9c1785499421c6288931b6e3dbc2500acb95b8adaa0e69db3"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -1425,16 +1417,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "euclid"
-version = "0.19.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "596b99621b9477e7a5f94d2d8dd13a9c5c302ac358b822c67a42b6f1054450e1"
-dependencies = [
- "euclid_macros",
- "num-traits",
-]
-
 [[package]]
 name = "euclid"
 version = "0.22.6"
@@ -1444,17 +1426,6 @@ dependencies = [
  "num-traits",
 ]
 
-[[package]]
-name = "euclid_macros"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdcb84c18ea5037a1c5a23039b4ff29403abce2e0d6b1daa11cf0bde2b30be15"
-dependencies = [
- "proc-macro2 0.4.30",
- "quote 0.6.13",
- "syn 0.15.44",
-]
-
 [[package]]
 name = "event-listener"
 version = "2.5.1"
@@ -1656,9 +1627,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f2a9333e0f9c7bca94dfc20bcf44fa12a61eeec662d6e007563ff748aa59c70"
 dependencies = [
  "inflections",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -1729,7 +1700,7 @@ version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782"
 dependencies = [
- "euclid 0.22.6",
+ "euclid",
  "svg_fmt",
 ]
 
@@ -1927,13 +1898,10 @@ dependencies = [
 name = "kayak_font"
 version = "0.1.0"
 dependencies = [
- "arrayvec 0.7.2",
- "bitflags",
- "fontdue",
- "lyon_geom",
- "lyon_path",
- "png",
- "ttf-parser 0.13.3",
+ "bevy",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
 ]
 
 [[package]]
@@ -1943,9 +1911,9 @@ dependencies = [
  "kayak_core",
  "pretty_assertions",
  "proc-macro-error",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -2050,26 +2018,6 @@ dependencies = [
  "cfg-if 1.0.0",
 ]
 
-[[package]]
-name = "lyon_geom"
-version = "0.12.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdb9bf1f1d43be9a9cc2343a7a096dc113cc25337a13e8f99721b01d1d548b60"
-dependencies = [
- "arrayvec 0.4.12",
- "euclid 0.19.9",
- "num-traits",
-]
-
-[[package]]
-name = "lyon_path"
-version = "0.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9dc8e0746b7cca11960b602f7fe037bb067746a01eab4aa502fed1494544843"
-dependencies = [
- "lyon_geom",
-]
-
 [[package]]
 name = "mach"
 version = "0.2.3"
@@ -2295,9 +2243,9 @@ checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d"
 dependencies = [
  "darling",
  "proc-macro-crate 0.1.5",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -2318,12 +2266,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "nodrop"
-version = "0.1.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
-
 [[package]]
 name = "nom"
 version = "5.1.2"
@@ -2367,9 +2309,9 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -2440,9 +2382,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9"
 dependencies = [
  "proc-macro-crate 1.1.0",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -2585,7 +2527,7 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee"
 dependencies = [
- "unicode-xid 0.2.2",
+ "unicode-xid",
 ]
 
 [[package]]
@@ -2630,9 +2572,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
 dependencies = [
  "proc-macro-error-attr",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
  "version_check",
 ]
 
@@ -2642,27 +2584,18 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
+ "proc-macro2",
+ "quote",
  "version_check",
 ]
 
-[[package]]
-name = "proc-macro2"
-version = "0.4.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
-dependencies = [
- "unicode-xid 0.1.0",
-]
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
 dependencies = [
- "unicode-xid 0.2.2",
+ "unicode-xid",
 ]
 
 [[package]]
@@ -2671,22 +2604,13 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9926767b8b8244d7b6b64546585121d193c3d0b4856ccd656b7bfa9deb91ab6a"
 
-[[package]]
-name = "quote"
-version = "0.6.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
-dependencies = [
- "proc-macro2 0.4.30",
-]
-
 [[package]]
 name = "quote"
 version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
 dependencies = [
- "proc-macro2 1.0.32",
+ "proc-macro2",
 ]
 
 [[package]]
@@ -2906,9 +2830,9 @@ version = "1.0.130"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -3069,11 +2993,11 @@ version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
+ "proc-macro2",
+ "quote",
  "serde",
  "serde_derive",
- "syn 1.0.81",
+ "syn",
 ]
 
 [[package]]
@@ -3083,13 +3007,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
 dependencies = [
  "base-x",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
+ "proc-macro2",
+ "quote",
  "serde",
  "serde_derive",
  "serde_json",
  "sha1",
- "syn 1.0.81",
+ "syn",
 ]
 
 [[package]]
@@ -3120,26 +3044,15 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"
 
-[[package]]
-name = "syn"
-version = "0.15.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
-dependencies = [
- "proc-macro2 0.4.30",
- "quote 0.6.13",
- "unicode-xid 0.1.0",
-]
-
 [[package]]
 name = "syn"
 version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "unicode-xid 0.2.2",
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
 ]
 
 [[package]]
@@ -3166,9 +3079,9 @@ version = "1.0.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -3208,9 +3121,9 @@ version = "0.1.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -3280,12 +3193,6 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
 
-[[package]]
-name = "unicode-xid"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
-
 [[package]]
 name = "unicode-xid"
 version = "0.2.2"
@@ -3356,9 +3263,9 @@ dependencies = [
  "bumpalo",
  "lazy_static",
  "log",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
  "wasm-bindgen-shared",
 ]
 
@@ -3380,7 +3287,7 @@ version = "0.2.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
 dependencies = [
- "quote 1.0.10",
+ "quote",
  "wasm-bindgen-macro-support",
 ]
 
@@ -3390,9 +3297,9 @@ version = "0.2.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
 dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.81",
+ "proc-macro2",
+ "quote",
+ "syn",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -3419,7 +3326,7 @@ version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d1577ecc4f6992b9e965878ac594efb24eed2bdf089c11f45b3d1c5f216e2e30"
 dependencies = [
- "arrayvec 0.7.2",
+ "arrayvec",
  "js-sys",
  "log",
  "naga",
@@ -3440,7 +3347,7 @@ version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3bdcbfa4885b32c2b1feb2faeb8b6a76065b752b8f08751b82f994e937687f46"
 dependencies = [
- "arrayvec 0.7.2",
+ "arrayvec",
  "bitflags",
  "cfg_aliases",
  "copyless",
@@ -3462,7 +3369,7 @@ version = "0.11.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0e493835d9edb153d5c8a9d8d016e1811dbe32ddb707a110be1453c7b051d3ec"
 dependencies = [
- "arrayvec 0.7.2",
+ "arrayvec",
  "ash",
  "bit-set",
  "bitflags",
diff --git a/bevy_kayak_ui/Cargo.toml b/bevy_kayak_ui/Cargo.toml
index 074e9d38188e5639ddff6feb01740598a685420e..3e9f474a7985ad0cb1b2c5e5dde3855e1407520d 100644
--- a/bevy_kayak_ui/Cargo.toml
+++ b/bevy_kayak_ui/Cargo.toml
@@ -9,6 +9,7 @@ bevy = { git = "https://github.com/bevyengine/bevy", rev = "38c7d5eb9e81ab8e1aec
 kayak_core = { path = "../kayak_core" }
 kayak_components = { path = "../kayak_components" }
 kayak_render_macros = { path = "../kayak_render_macros" }
+kayak_font = { path = "../kayak_font" }
 #kayak_font = { path = "../kayak_font" }
 crevice = { git = "https://github.com/bevyengine/bevy", rev = "38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c" }
 serde = "1.0"
diff --git a/bevy_kayak_ui/src/render/unified/font/extract.rs b/bevy_kayak_ui/src/render/unified/font/extract.rs
index be26bc88c30f0324332bd10acdb92620f485bafe..6bc4a3a05128430dd4dae4d2da05d0a307353ced 100644
--- a/bevy_kayak_ui/src/render/unified/font/extract.rs
+++ b/bevy_kayak_ui/src/render/unified/font/extract.rs
@@ -4,13 +4,14 @@ use bevy::{
     sprite2::Rect,
 };
 use kayak_core::render_primitive::RenderPrimitive;
+use kayak_font::KayakFont;
 
 use crate::{
     render::unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType},
     to_bevy_color, BevyContext,
 };
 
-use super::{font::KayakFont, font_mapping::FontMapping};
+use super::font_mapping::FontMapping;
 
 pub fn extract_texts(
     mut commands: Commands,
diff --git a/bevy_kayak_ui/src/render/unified/font/font.rs b/bevy_kayak_ui/src/render/unified/font/font.rs
deleted file mode 100644
index e081b89a6d21f2633126e510749d861eb01cdb7f..0000000000000000000000000000000000000000
--- a/bevy_kayak_ui/src/render/unified/font/font.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use std::collections::HashMap;
-
-use super::sdf::Sdf;
-use bevy::{prelude::Handle, reflect::TypeUuid, render2::texture::Image};
-
-#[derive(Debug, Clone, TypeUuid)]
-#[uuid = "4fe4732c-6731-49bb-bafc-4690d636b848"]
-pub struct KayakFont {
-    pub sdf: Sdf,
-    pub atlas_image: Handle<Image>,
-    char_ids: HashMap<char, u32>,
-}
-
-impl KayakFont {
-    pub fn new(sdf: Sdf, atlas_image: Handle<Image>) -> Self {
-        Self {
-            sdf,
-            atlas_image,
-            char_ids: HashMap::default(),
-        }
-    }
-
-    pub fn generate_char_ids(&mut self) {
-        let mut count = 0;
-        for glyph in self.sdf.glyphs.iter() {
-            self.char_ids.insert(glyph.unicode, count);
-            count += 1;
-        }
-    }
-
-    pub fn get_char_id(&self, c: char) -> Option<u32> {
-        self.char_ids.get(&c).and_then(|id| Some(*id))
-    }
-}
diff --git a/bevy_kayak_ui/src/render/unified/font/font_mapping.rs b/bevy_kayak_ui/src/render/unified/font/font_mapping.rs
index eeb405ab60d8c6d6d48ab722c9cf0caaa31a33f1..743eedd5436a3e61d1e692e0f03c9b857b9f815c 100644
--- a/bevy_kayak_ui/src/render/unified/font/font_mapping.rs
+++ b/bevy_kayak_ui/src/render/unified/font/font_mapping.rs
@@ -1,6 +1,5 @@
 use bevy::{prelude::Handle, utils::HashMap};
-
-use super::font::KayakFont;
+use kayak_font::KayakFont;
 
 pub struct FontMapping {
     count: u16,
diff --git a/bevy_kayak_ui/src/render/unified/font/mod.rs b/bevy_kayak_ui/src/render/unified/font/mod.rs
index 9728e4455d494359384e9783064b132ebaefb62d..5ea79d0c7fb71c92614302edbfe02eb919e7ddbf 100644
--- a/bevy_kayak_ui/src/render/unified/font/mod.rs
+++ b/bevy_kayak_ui/src/render/unified/font/mod.rs
@@ -12,20 +12,14 @@ use bevy::{
     },
     utils::HashSet,
 };
-
-use self::{extract::extract_texts, sdf::Sdf};
-
-use super::pipeline::UnifiedPipeline;
+use kayak_font::{KayakFont, Sdf, FontTextureCache};
 
 mod extract;
-mod font;
 mod font_mapping;
-mod font_texture_cache;
-mod sdf;
 
-pub use font::*;
+use self::extract::extract_texts;
+use super::pipeline::UnifiedPipeline;
 pub use font_mapping::*;
-pub(crate) use font_texture_cache::FontTextureCache;
 
 #[derive(Default)]
 pub struct TextRendererPlugin;
@@ -169,5 +163,5 @@ fn create_and_update_font_cache_texture(
     mut font_texture_cache: ResMut<FontTextureCache>,
     images: Res<RenderAssets<Image>>,
 ) {
-    font_texture_cache.process_new(&device, &queue, &pipeline, &images);
+    font_texture_cache.process_new(&device, &queue, pipeline.into_inner(), &images);
 }
diff --git a/bevy_kayak_ui/src/render/unified/font/sdf/mod.rs b/bevy_kayak_ui/src/render/unified/font/sdf/mod.rs
deleted file mode 100644
index 07f9b9af11e7a77120ba74f765fe716093de53a4..0000000000000000000000000000000000000000
--- a/bevy_kayak_ui/src/render/unified/font/sdf/mod.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-use self::{atlas::Atlas, glyph::Glyph, metrics::Metrics};
-use bevy::math::Vec2;
-use serde::Deserialize;
-
-mod atlas;
-mod glyph;
-mod metrics;
-
-#[derive(Deserialize, Debug, Clone)]
-pub struct Sdf {
-    pub atlas: Atlas,
-    metrics: Metrics,
-    pub glyphs: Vec<Glyph>,
-    kerning: Vec<KerningData>,
-}
-
-#[derive(serde::Deserialize, Debug, Clone, Copy)]
-pub struct KerningData {
-    pub unicode1: u32,
-    pub unicode2: u32,
-    pub advance: f32,
-}
-
-impl Sdf {
-    pub fn from_string(data: String) -> Sdf {
-        let value: Sdf = match serde_path_to_error::deserialize(
-            &mut serde_json::Deserializer::from_str(&data),
-        ) {
-            Ok(v) => v,
-            Err(err) => {
-                let path = err.path().to_string();
-                dbg!(err);
-                panic!("failed to deserialize json! path: {}", path);
-            }
-        };
-
-        value
-    }
-
-    pub fn max_glyph_size(&self) -> Vec2 {
-        let mut size = Vec2::new(0.0, 0.0);
-        self.glyphs.iter().for_each(|glyph| {
-            if let Some(atlas_bounds) = glyph.atlas_bounds {
-                let atlas_size = atlas_bounds.size();
-                if atlas_size.x > size.x {
-                    size.x = atlas_size.x;
-                }
-                if atlas_size.y > size.y {
-                    size.y = atlas_size.y;
-                }
-            }
-        });
-
-        size
-    }
-}
-
-#[test]
-fn test_sdf_loader() {
-    let sdf = Sdf::from_string(include_str!("../../../../../../msdfgen/test.json").to_string());
-    dbg!(sdf.max_glyph_size());
-}
diff --git a/bevy_kayak_ui/src/render/unified/pipeline.rs b/bevy_kayak_ui/src/render/unified/pipeline.rs
index c2d1819a38d55f87b50010f94871b3f852dd8283..5bf11216b00a6193ba8c11a6677c6edfb475d0a9 100644
--- a/bevy_kayak_ui/src/render/unified/pipeline.rs
+++ b/bevy_kayak_ui/src/render/unified/pipeline.rs
@@ -31,8 +31,8 @@ use bevy::{
 };
 use bytemuck::{Pod, Zeroable};
 use crevice::std140::AsStd140;
+use kayak_font::{FontRenderingPipeline, FontTextureCache, KayakFont};
 
-use super::font::{FontTextureCache, KayakFont};
 use super::UNIFIED_SHADER_HANDLE;
 use crate::render::ui_pass::TransparentUI;
 
@@ -55,6 +55,12 @@ const QUAD_VERTEX_POSITIONS: &[Vec3] = &[
     const_vec3!([1.0, 1.0, 0.0]),
 ];
 
+impl FontRenderingPipeline for UnifiedPipeline {
+    fn get_font_image_layout(&self) -> &BindGroupLayout {
+        &self.font_image_layout
+    }
+}
+
 impl FromWorld for UnifiedPipeline {
     fn from_world(world: &mut World) -> Self {
         let world = world.cell();
@@ -586,7 +592,7 @@ impl Draw<TransparentUI> for DrawUI {
             let unified_pipeline = unified_pipeline.into_inner();
             if let Some(font_handle) = extracted_quad.font_handle.as_ref() {
                 if let Some(image_bindings) =
-                    font_texture_cache.into_inner().bind_groups.get(font_handle)
+                    font_texture_cache.into_inner().get_binding(font_handle)
                 {
                     pass.set_bind_group(1, image_bindings, &[]);
                 } else {
diff --git a/kayak_font/Cargo.toml b/kayak_font/Cargo.toml
index 79436b77847607641ead408d6144d56d548ac08b..f5ded9aef2a7f94113971945d39f0e85f8665262 100644
--- a/kayak_font/Cargo.toml
+++ b/kayak_font/Cargo.toml
@@ -6,12 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-arrayvec = "0.7"
-bitflags = "1.0"
-lyon_path = "0.12"
-lyon_geom = "0.12"
-fontdue = "0.6"
-ttf-parser = "0.13"
-
-[dev-dependencies]
-png = "0.16.3"
+bevy = { git = "https://github.com/bevyengine/bevy", rev = "38c7d5eb9e81ab8e1aec03673599b25a9aa0c69c" }
+serde = "1.0"
+serde_json = "1.0"
+serde_path_to_error = "0.1"
diff --git a/kayak_font/assets/roboto.json b/kayak_font/assets/roboto.json
new file mode 100644
index 0000000000000000000000000000000000000000..60008778e8ac698e344216c88570a2f698dbd4e3
--- /dev/null
+++ b/kayak_font/assets/roboto.json
@@ -0,0 +1,1529 @@
+{
+    "atlas": {
+        "type": "msdf",
+        "distanceRange": 2,
+        "size": 32.875,
+        "width": 212,
+        "height": 212,
+        "yOrigin": "bottom"
+    },
+    "metrics": {
+        "emSize": 1,
+        "lineHeight": 1.171875,
+        "ascender": 0.927734375,
+        "descender": -0.244140625,
+        "underlineY": -0.09765625,
+        "underlineThickness": 0.048828125
+    },
+    "glyphs": [
+        {
+            "unicode": 32,
+            "advance": 0.24755859375
+        },
+        {
+            "unicode": 33,
+            "advance": 0.25732421875,
+            "planeBounds": {
+                "left": 0.040337044023288991,
+                "bottom": -0.042654059232414394,
+                "right": 0.22284654972671103,
+                "top": 0.7482204654824145
+            },
+            "atlasBounds": {
+                "left": 112.5,
+                "bottom": 120.5,
+                "right": 118.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 34,
+            "advance": 0.31982421875,
+            "planeBounds": {
+                "left": 0.029865917597433435,
+                "bottom": 0.47730327649714832,
+                "right": 0.30363017615256649,
+                "top": 0.78148578600285168
+            },
+            "atlasBounds": {
+                "left": 57.5,
+                "bottom": 6.5,
+                "right": 66.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 35,
+            "advance": 0.61572265625,
+            "planeBounds": {
+                "left": 0.026383896744296635,
+                "bottom": -0.039968512357414394,
+                "right": 0.63474891575570347,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 119.5,
+                "bottom": 120.5,
+                "right": 139.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 36,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.022450726295152143,
+                "bottom": -0.13959645318441066,
+                "right": 0.53956099245484801,
+                "top": 0.86420582818441072
+            },
+            "atlasBounds": {
+                "left": 70.5,
+                "bottom": 178.5,
+                "right": 87.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 37,
+            "advance": 0.732421875,
+            "planeBounds": {
+                "left": 0.02030730156844112,
+                "bottom": -0.055177637832699571,
+                "right": 0.71992707343155904,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 188.5,
+                "bottom": 184.5,
+                "right": 211.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 38,
+            "advance": 0.62158203125,
+            "planeBounds": {
+                "left": 0.014592740019011461,
+                "bottom": -0.055177637832699571,
+                "right": 0.65337600998098866,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 36.5,
+                "bottom": 147.5,
+                "right": 57.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 39,
+            "advance": 0.17431640625,
+            "planeBounds": {
+                "left": 0.010868435123574159,
+                "bottom": 0.48096538587214832,
+                "right": 0.16295968987642587,
+                "top": 0.78514789537785168
+            },
+            "atlasBounds": {
+                "left": 67.5,
+                "bottom": 6.5,
+                "right": 72.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 40,
+            "advance": 0.341796875,
+            "planeBounds": {
+                "left": 0.026547276021863096,
+                "bottom": -0.26115156398526612,
+                "right": 0.36114803647813687,
+                "top": 0.83390547023526618
+            },
+            "atlasBounds": {
+                "left": 0.5,
+                "bottom": 175.5,
+                "right": 11.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 41,
+            "advance": 0.34765625,
+            "planeBounds": {
+                "left": -0.019595302103136911,
+                "bottom": -0.26115156398526612,
+                "right": 0.31500545835313687,
+                "top": 0.83390547023526618
+            },
+            "atlasBounds": {
+                "left": 12.5,
+                "bottom": 175.5,
+                "right": 23.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 42,
+            "advance": 0.4306640625,
+            "planeBounds": {
+                "left": -0.028258116979562771,
+                "bottom": 0.26080438302043729,
+                "right": 0.45843389822956265,
+                "top": 0.74749639822956271
+            },
+            "atlasBounds": {
+                "left": 0.5,
+                "bottom": 0.5,
+                "right": 16.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 43,
+            "advance": 0.56689453125,
+            "planeBounds": {
+                "left": -0.0072351027804181973,
+                "bottom": 0.025895615494296635,
+                "right": 0.57071166528041828,
+                "top": 0.63426063450570347
+            },
+            "atlasBounds": {
+                "left": 154.5,
+                "bottom": 17.5,
+                "right": 173.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 44,
+            "advance": 0.1962890625,
+            "planeBounds": {
+                "left": -0.024188487701996185,
+                "bottom": -0.1846343646031369,
+                "right": 0.18873926895199622,
+                "top": 0.14996639585313687
+            },
+            "atlasBounds": {
+                "left": 49.5,
+                "bottom": 5.5,
+                "right": 56.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 45,
+            "advance": 0.27587890625,
+            "planeBounds": {
+                "left": -0.01488422350285174,
+                "bottom": 0.22595632574857413,
+                "right": 0.28929828600285168,
+                "top": 0.37804758050142584
+            },
+            "atlasBounds": {
+                "left": 201.5,
+                "bottom": 86.5,
+                "right": 211.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 46,
+            "advance": 0.26318359375,
+            "planeBounds": {
+                "left": 0.035210090898288991,
+                "bottom": -0.042914909101711009,
+                "right": 0.21771959660171103,
+                "top": 0.13959459660171103
+            },
+            "atlasBounds": {
+                "left": 105.5,
+                "bottom": 10.5,
+                "right": 111.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 47,
+            "advance": 0.412109375,
+            "planeBounds": {
+                "left": -0.032336100879277595,
+                "bottom": -0.10090434143298473,
+                "right": 0.42393766337927752,
+                "top": 0.75080668518298488
+            },
+            "atlasBounds": {
+                "left": 156.5,
+                "bottom": 183.5,
+                "right": 171.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 48,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.021962445045152143,
+                "bottom": -0.055177637832699571,
+                "right": 0.53907271120484801,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 58.5,
+                "bottom": 147.5,
+                "right": 75.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 49,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.052182041646863096,
+                "bottom": -0.038259527982414394,
+                "right": 0.38678280210313687,
+                "top": 0.7526149967324145
+            },
+            "atlasBounds": {
+                "left": 140.5,
+                "bottom": 120.5,
+                "right": 151.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 50,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.011391991444866975,
+                "bottom": -0.035085699857414394,
+                "right": 0.5589205085551332,
+                "top": 0.7557888248574145
+            },
+            "atlasBounds": {
+                "left": 152.5,
+                "bottom": 120.5,
+                "right": 170.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 51,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.012685101295152145,
+                "bottom": -0.055177637832699571,
+                "right": 0.52979536745484801,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 76.5,
+                "bottom": 147.5,
+                "right": 93.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 52,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": -0.0065026809054181973,
+                "bottom": -0.039968512357414394,
+                "right": 0.57144408715541828,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 171.5,
+                "bottom": 120.5,
+                "right": 190.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 53,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.04002885129515215,
+                "bottom": -0.044851324857414394,
+                "right": 0.55713911745484801,
+                "top": 0.7460231998574145
+            },
+            "atlasBounds": {
+                "left": 142.5,
+                "bottom": 92.5,
+                "right": 159.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 54,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.03050736692015215,
+                "bottom": -0.044607184232414394,
+                "right": 0.54761763307984801,
+                "top": 0.7462673404824145
+            },
+            "atlasBounds": {
+                "left": 180.5,
+                "bottom": 92.5,
+                "right": 197.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 55,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.0040677726948669755,
+                "bottom": -0.039968512357414394,
+                "right": 0.5515962898051332,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 146.5,
+                "bottom": 65.5,
+                "right": 164.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 56,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.022206585670152143,
+                "bottom": -0.055177637832699571,
+                "right": 0.53931685182984801,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 118.5,
+                "bottom": 147.5,
+                "right": 135.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 57,
+            "advance": 0.5615234375,
+            "planeBounds": {
+                "left": 0.013905804420152145,
+                "bottom": -0.035329840482414394,
+                "right": 0.53101607057984801,
+                "top": 0.7555446842324145
+            },
+            "atlasBounds": {
+                "left": 44.5,
+                "bottom": 38.5,
+                "right": 61.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 58,
+            "advance": 0.2421875,
+            "planeBounds": {
+                "left": 0.030571419023288991,
+                "bottom": -0.040266493880703365,
+                "right": 0.21308092472671103,
+                "top": 0.56809852513070347
+            },
+            "atlasBounds": {
+                "left": 201.5,
+                "bottom": 65.5,
+                "right": 207.5,
+                "top": 85.5
+            }
+        },
+        {
+            "unicode": 59,
+            "advance": 0.21142578125,
+            "planeBounds": {
+                "left": -0.013202159576996183,
+                "bottom": -0.18442735563212925,
+                "right": 0.19972559707699622,
+                "top": 0.57602891813212931
+            },
+            "atlasBounds": {
+                "left": 98.5,
+                "bottom": 39.5,
+                "right": 105.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 60,
+            "advance": 0.50830078125,
+            "planeBounds": {
+                "left": -0.0084827263545627658,
+                "bottom": 0.05711869504515215,
+                "right": 0.47820928885456265,
+                "top": 0.57422896120484801
+            },
+            "atlasBounds": {
+                "left": 174.5,
+                "bottom": 20.5,
+                "right": 190.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 61,
+            "advance": 0.548828125,
+            "planeBounds": {
+                "left": 0.034486023645437264,
+                "bottom": 0.15293971304657797,
+                "right": 0.52117803885456271,
+                "top": 0.51795872445342206
+            },
+            "atlasBounds": {
+                "left": 32.5,
+                "bottom": 4.5,
+                "right": 48.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 62,
+            "advance": 0.5224609375,
+            "planeBounds": {
+                "left": 0.030579773645437264,
+                "bottom": 0.05760697629515215,
+                "right": 0.51727178885456271,
+                "top": 0.57471724245484801
+            },
+            "atlasBounds": {
+                "left": 191.5,
+                "bottom": 20.5,
+                "right": 207.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 63,
+            "advance": 0.47216796875,
+            "planeBounds": {
+                "left": -0.0087268669795627658,
+                "bottom": -0.037771246732414394,
+                "right": 0.47796514822956265,
+                "top": 0.7531032779824145
+            },
+            "atlasBounds": {
+                "left": 62.5,
+                "bottom": 38.5,
+                "right": 78.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 64,
+            "advance": 0.89794921875,
+            "planeBounds": {
+                "left": 0.012067393169925377,
+                "bottom": -0.26313160943441061,
+                "right": 0.89419667073646536,
+                "top": 0.74067067193441072
+            },
+            "atlasBounds": {
+                "left": 88.5,
+                "bottom": 178.5,
+                "right": 117.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 65,
+            "advance": 0.65234375,
+            "planeBounds": {
+                "left": -0.02339387030655888,
+                "bottom": -0.039968512357414394,
+                "right": 0.67622590155655904,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 20.5,
+                "bottom": 38.5,
+                "right": 43.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 66,
+            "advance": 0.62255859375,
+            "planeBounds": {
+                "left": 0.050698632069866974,
+                "bottom": -0.039968512357414394,
+                "right": 0.5982271491801332,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 79.5,
+                "bottom": 38.5,
+                "right": 97.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 67,
+            "advance": 0.65087890625,
+            "planeBounds": {
+                "left": 0.027604599869296632,
+                "bottom": -0.055177637832699571,
+                "right": 0.63596961888070347,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 136.5,
+                "bottom": 147.5,
+                "right": 156.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 68,
+            "advance": 0.65576171875,
+            "planeBounds": {
+                "left": 0.050626225344581804,
+                "bottom": -0.039968512357414394,
+                "right": 0.62857299340541828,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 0.5,
+                "bottom": 38.5,
+                "right": 19.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 69,
+            "advance": 0.568359375,
+            "planeBounds": {
+                "left": 0.04979447629515215,
+                "bottom": -0.039968512357414394,
+                "right": 0.56690474245484801,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 183.5,
+                "bottom": 65.5,
+                "right": 200.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 70,
+            "advance": 0.552734375,
+            "planeBounds": {
+                "left": 0.04417924192015215,
+                "bottom": -0.039968512357414394,
+                "right": 0.56128950807984801,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 165.5,
+                "bottom": 65.5,
+                "right": 182.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 71,
+            "advance": 0.68115234375,
+            "planeBounds": {
+                "left": 0.014104458769011461,
+                "bottom": -0.055177637832699571,
+                "right": 0.65288772873098866,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 32.5,
+                "bottom": 119.5,
+                "right": 53.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 72,
+            "advance": 0.712890625,
+            "planeBounds": {
+                "left": 0.051530381119296635,
+                "bottom": -0.039968512357414394,
+                "right": 0.65989540013070347,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 125.5,
+                "bottom": 65.5,
+                "right": 145.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 73,
+            "advance": 0.27197265625,
+            "planeBounds": {
+                "left": 0.044975715898288991,
+                "bottom": -0.039968512357414394,
+                "right": 0.22748522160171103,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 205.5,
+                "bottom": 92.5,
+                "right": 211.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 74,
+            "advance": 0.5517578125,
+            "planeBounds": {
+                "left": -0.0083109924548478552,
+                "bottom": -0.044851324857414394,
+                "right": 0.50879927370484801,
+                "top": 0.7460231998574145
+            },
+            "atlasBounds": {
+                "left": 86.5,
+                "bottom": 65.5,
+                "right": 103.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 75,
+            "advance": 0.626953125,
+            "planeBounds": {
+                "left": 0.050797959244296635,
+                "bottom": -0.039968512357414394,
+                "right": 0.65916297825570347,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 65.5,
+                "bottom": 65.5,
+                "right": 85.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 76,
+            "advance": 0.5380859375,
+            "planeBounds": {
+                "left": 0.039540570045152094,
+                "bottom": -0.039968512357414394,
+                "right": 0.5566508362048479,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 47.5,
+                "bottom": 65.5,
+                "right": 64.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 77,
+            "advance": 0.873046875,
+            "planeBounds": {
+                "left": 0.040842034517585606,
+                "bottom": -0.039968512357414394,
+                "right": 0.8317165592324145,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 20.5,
+                "bottom": 65.5,
+                "right": 46.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 78,
+            "advance": 0.712890625,
+            "planeBounds": {
+                "left": 0.051530381119296635,
+                "bottom": -0.039968512357414394,
+                "right": 0.65989540013070347,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 104.5,
+                "bottom": 65.5,
+                "right": 124.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 79,
+            "advance": 0.6875,
+            "planeBounds": {
+                "left": 0.024114224394011459,
+                "bottom": -0.055177637832699571,
+                "right": 0.66289749435598866,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 72.5,
+                "bottom": 119.5,
+                "right": 93.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 80,
+            "advance": 0.630859375,
+            "planeBounds": {
+                "left": 0.049161381594581804,
+                "bottom": -0.039968512357414394,
+                "right": 0.62710814965541828,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 0.5,
+                "bottom": 65.5,
+                "right": 19.5,
+                "top": 91.5
+            }
+        },
+        {
+            "unicode": 81,
+            "advance": 0.6875,
+            "planeBounds": {
+                "left": 0.021184536894011459,
+                "bottom": -0.15598079550855509,
+                "right": 0.65996780685598866,
+                "top": 0.75656673300855526
+            },
+            "atlasBounds": {
+                "left": 134.5,
+                "bottom": 181.5,
+                "right": 155.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 82,
+            "advance": 0.61572265625,
+            "planeBounds": {
+                "left": 0.051114506594581804,
+                "bottom": -0.039968512357414394,
+                "right": 0.62906127465541828,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 160.5,
+                "bottom": 92.5,
+                "right": 179.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 83,
+            "advance": 0.59326171875,
+            "planeBounds": {
+                "left": 0.0083898972195818027,
+                "bottom": -0.055177637832699571,
+                "right": 0.58633666528041828,
+                "top": 0.76611513783269969
+            },
+            "atlasBounds": {
+                "left": 192.5,
+                "bottom": 147.5,
+                "right": 211.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 84,
+            "advance": 0.5966796875,
+            "planeBounds": {
+                "left": -0.020563509980988541,
+                "bottom": -0.039968512357414394,
+                "right": 0.61821975998098866,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 120.5,
+                "bottom": 92.5,
+                "right": 141.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 85,
+            "advance": 0.6484375,
+            "planeBounds": {
+                "left": 0.036710209719581804,
+                "bottom": -0.044851324857414394,
+                "right": 0.61465697778041828,
+                "top": 0.7460231998574145
+            },
+            "atlasBounds": {
+                "left": 100.5,
+                "bottom": 92.5,
+                "right": 119.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 86,
+            "advance": 0.63623046875,
+            "planeBounds": {
+                "left": -0.03120637030655888,
+                "bottom": -0.039968512357414394,
+                "right": 0.66841340155655904,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 76.5,
+                "bottom": 92.5,
+                "right": 99.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 87,
+            "advance": 0.88720703125,
+            "planeBounds": {
+                "left": -0.0085198580085550802,
+                "bottom": -0.039968512357414394,
+                "right": 0.90402767050855526,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 45.5,
+                "bottom": 92.5,
+                "right": 75.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 88,
+            "advance": 0.626953125,
+            "planeBounds": {
+                "left": -0.0051826506059885393,
+                "bottom": -0.039968512357414394,
+                "right": 0.63360061935598866,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 23.5,
+                "bottom": 92.5,
+                "right": 44.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 89,
+            "advance": 0.6005859375,
+            "planeBounds": {
+                "left": -0.03528435420627371,
+                "bottom": -0.039968512357414394,
+                "right": 0.63391716670627374,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 0.5,
+                "bottom": 92.5,
+                "right": 22.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 90,
+            "advance": 0.5986328125,
+            "planeBounds": {
+                "left": -0.0034012595057033666,
+                "bottom": -0.039968512357414394,
+                "right": 0.60496375950570347,
+                "top": 0.7509060123574145
+            },
+            "atlasBounds": {
+                "left": 191.5,
+                "bottom": 120.5,
+                "right": 211.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 91,
+            "advance": 0.26513671875,
+            "planeBounds": {
+                "left": 0.026447948847433435,
+                "bottom": -0.18703214115969582,
+                "right": 0.30021220740256649,
+                "top": 0.84718839115969591
+            },
+            "atlasBounds": {
+                "left": 37.5,
+                "bottom": 177.5,
+                "right": 46.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 92,
+            "advance": 0.41015625,
+            "planeBounds": {
+                "left": -0.017199382129277595,
+                "bottom": -0.10090434143298473,
+                "right": 0.43907438212927752,
+                "top": 0.75080668518298488
+            },
+            "atlasBounds": {
+                "left": 172.5,
+                "bottom": 183.5,
+                "right": 187.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 93,
+            "advance": 0.26513671875,
+            "planeBounds": {
+                "left": -0.040202441777566537,
+                "bottom": -0.18703214115969582,
+                "right": 0.23356181677756652,
+                "top": 0.84718839115969591
+            },
+            "atlasBounds": {
+                "left": 60.5,
+                "bottom": 177.5,
+                "right": 69.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 94,
+            "advance": 0.41796875,
+            "planeBounds": {
+                "left": -0.0049199441539924237,
+                "bottom": 0.32051950897100762,
+                "right": 0.42093556915399238,
+                "top": 0.74637502227899244
+            },
+            "atlasBounds": {
+                "left": 17.5,
+                "bottom": 2.5,
+                "right": 31.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 95,
+            "advance": 0.451171875,
+            "planeBounds": {
+                "left": -0.032969195579847906,
+                "bottom": -0.11291086175142585,
+                "right": 0.4841410705798479,
+                "top": 0.039180393001425839
+            },
+            "atlasBounds": {
+                "left": 112.5,
+                "bottom": 11.5,
+                "right": 129.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 96,
+            "advance": 0.30908203125,
+            "planeBounds": {
+                "left": -0.0072434574025665669,
+                "bottom": 0.57175877792300378,
+                "right": 0.26652080115256649,
+                "top": 0.78468653457699622
+            },
+            "atlasBounds": {
+                "left": 95.5,
+                "bottom": 9.5,
+                "right": 104.5,
+                "top": 16.5
+            }
+        },
+        {
+            "unicode": 97,
+            "advance": 0.5439453125,
+            "planeBounds": {
+                "left": 0.012685101295152093,
+                "bottom": -0.055231478730988541,
+                "right": 0.5297953674548479,
+                "top": 0.58355179123098866
+            },
+            "atlasBounds": {
+                "left": 191.5,
+                "bottom": 43.5,
+                "right": 208.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 98,
+            "advance": 0.56103515625,
+            "planeBounds": {
+                "left": 0.03343705442015215,
+                "bottom": -0.040529200332699564,
+                "right": 0.55054732057984801,
+                "top": 0.78076357533269969
+            },
+            "atlasBounds": {
+                "left": 54.5,
+                "bottom": 119.5,
+                "right": 71.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 99,
+            "advance": 0.5234375,
+            "planeBounds": {
+                "left": 0.0090229919201521448,
+                "bottom": -0.055231478730988541,
+                "right": 0.52613325807984801,
+                "top": 0.58355179123098866
+            },
+            "atlasBounds": {
+                "left": 138.5,
+                "bottom": 43.5,
+                "right": 155.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 100,
+            "advance": 0.56396484375,
+            "planeBounds": {
+                "left": 0.010731976295152145,
+                "bottom": -0.040529200332699564,
+                "right": 0.52784224245484801,
+                "top": 0.78076357533269969
+            },
+            "atlasBounds": {
+                "left": 14.5,
+                "bottom": 119.5,
+                "right": 31.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 101,
+            "advance": 0.52978515625,
+            "planeBounds": {
+                "left": 0.010976116920152145,
+                "bottom": -0.055231478730988541,
+                "right": 0.52808638307984801,
+                "top": 0.58355179123098866
+            },
+            "atlasBounds": {
+                "left": 173.5,
+                "bottom": 43.5,
+                "right": 190.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 102,
+            "advance": 0.34716796875,
+            "planeBounds": {
+                "left": -0.008753787428707251,
+                "bottom": -0.030519434707699571,
+                "right": 0.38668347492870719,
+                "top": 0.79077334095769969
+            },
+            "atlasBounds": {
+                "left": 0.5,
+                "bottom": 119.5,
+                "right": 13.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 103,
+            "advance": 0.56103515625,
+            "planeBounds": {
+                "left": 0.011464398170152145,
+                "bottom": -0.24560732533269961,
+                "right": 0.52857466432984801,
+                "top": 0.57568545033269969
+            },
+            "atlasBounds": {
+                "left": 174.5,
+                "bottom": 147.5,
+                "right": 191.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 104,
+            "advance": 0.55078125,
+            "planeBounds": {
+                "left": 0.032777039270437264,
+                "bottom": -0.035646387832699571,
+                "right": 0.51946905447956271,
+                "top": 0.78564638783269969
+            },
+            "atlasBounds": {
+                "left": 157.5,
+                "bottom": 147.5,
+                "right": 173.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 105,
+            "advance": 0.24267578125,
+            "planeBounds": {
+                "left": 0.031059700273288991,
+                "bottom": -0.035085699857414394,
+                "right": 0.21356920597671103,
+                "top": 0.7557888248574145
+            },
+            "atlasBounds": {
+                "left": 198.5,
+                "bottom": 92.5,
+                "right": 204.5,
+                "top": 118.5
+            }
+        },
+        {
+            "unicode": 106,
+            "advance": 0.23876953125,
+            "planeBounds": {
+                "left": -0.06852275427756653,
+                "bottom": -0.24823903130941069,
+                "right": 0.20524150427756652,
+                "top": 0.75556325005941072
+            },
+            "atlasBounds": {
+                "left": 118.5,
+                "bottom": 178.5,
+                "right": 127.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 107,
+            "advance": 0.5068359375,
+            "planeBounds": {
+                "left": 0.028798382545152094,
+                "bottom": -0.035646387832699571,
+                "right": 0.5459086487048479,
+                "top": 0.78564638783269969
+            },
+            "atlasBounds": {
+                "left": 100.5,
+                "bottom": 147.5,
+                "right": 117.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 108,
+            "advance": 0.24267578125,
+            "planeBounds": {
+                "left": 0.045292263248574161,
+                "bottom": -0.035646387832699571,
+                "right": 0.19738351800142587,
+                "top": 0.78564638783269969
+            },
+            "atlasBounds": {
+                "left": 94.5,
+                "bottom": 147.5,
+                "right": 99.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 109,
+            "advance": 0.87646484375,
+            "planeBounds": {
+                "left": 0.027586034042300436,
+                "bottom": -0.035139540755703365,
+                "right": 0.84887880970769969,
+                "top": 0.57322547825570347
+            },
+            "atlasBounds": {
+                "left": 82.5,
+                "bottom": 17.5,
+                "right": 109.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 110,
+            "advance": 0.5517578125,
+            "planeBounds": {
+                "left": 0.032777039270437264,
+                "bottom": -0.035139540755703365,
+                "right": 0.51946905447956271,
+                "top": 0.57322547825570347
+            },
+            "atlasBounds": {
+                "left": 18.5,
+                "bottom": 17.5,
+                "right": 34.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 111,
+            "advance": 0.5703125,
+            "planeBounds": {
+                "left": 0.011147850819866975,
+                "bottom": -0.055231478730988541,
+                "right": 0.5586763679301332,
+                "top": 0.58355179123098866
+            },
+            "atlasBounds": {
+                "left": 119.5,
+                "bottom": 43.5,
+                "right": 137.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 112,
+            "advance": 0.56103515625,
+            "planeBounds": {
+                "left": 0.03294877317015215,
+                "bottom": -0.24316591908269961,
+                "right": 0.55005903932984801,
+                "top": 0.57812685658269969
+            },
+            "atlasBounds": {
+                "left": 18.5,
+                "bottom": 147.5,
+                "right": 35.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 113,
+            "advance": 0.568359375,
+            "planeBounds": {
+                "left": 0.010487835670152145,
+                "bottom": -0.24316591908269961,
+                "right": 0.52759810182984801,
+                "top": 0.57812685658269969
+            },
+            "atlasBounds": {
+                "left": 0.5,
+                "bottom": 147.5,
+                "right": 17.5,
+                "top": 174.5
+            }
+        },
+        {
+            "unicode": 114,
+            "advance": 0.33837890625,
+            "planeBounds": {
+                "left": 0.028744541646863096,
+                "bottom": -0.035139540755703365,
+                "right": 0.36334530210313687,
+                "top": 0.57322547825570347
+            },
+            "atlasBounds": {
+                "left": 70.5,
+                "bottom": 17.5,
+                "right": 81.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 115,
+            "advance": 0.515625,
+            "planeBounds": {
+                "left": 0.013001648645437234,
+                "bottom": -0.055231478730988541,
+                "right": 0.49969366385456265,
+                "top": 0.58355179123098866
+            },
+            "atlasBounds": {
+                "left": 156.5,
+                "bottom": 43.5,
+                "right": 172.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 116,
+            "advance": 0.32666015625,
+            "planeBounds": {
+                "left": -0.03431614632842208,
+                "bottom": -0.041776823906844049,
+                "right": 0.330702865078422,
+                "top": 0.68826119890684423
+            },
+            "atlasBounds": {
+                "left": 106.5,
+                "bottom": 40.5,
+                "right": 118.5,
+                "top": 64.5
+            }
+        },
+        {
+            "unicode": 117,
+            "advance": 0.55126953125,
+            "planeBounds": {
+                "left": 0.031068054895437264,
+                "bottom": -0.044905165755703365,
+                "right": 0.51776007010456271,
+                "top": 0.56345985325570347
+            },
+            "atlasBounds": {
+                "left": 110.5,
+                "bottom": 17.5,
+                "right": 126.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 118,
+            "advance": 0.484375,
+            "planeBounds": {
+                "left": -0.017588336204847906,
+                "bottom": -0.040022353255703365,
+                "right": 0.4995219299548479,
+                "top": 0.56834266575570347
+            },
+            "atlasBounds": {
+                "left": 35.5,
+                "bottom": 17.5,
+                "right": 52.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 119,
+            "advance": 0.75146484375,
+            "planeBounds": {
+                "left": -0.020925543607414394,
+                "bottom": -0.040022353255703365,
+                "right": 0.7699489811074145,
+                "top": 0.56834266575570347
+            },
+            "atlasBounds": {
+                "left": 127.5,
+                "bottom": 17.5,
+                "right": 153.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 120,
+            "advance": 0.49560546875,
+            "planeBounds": {
+                "left": -0.011728961204847855,
+                "bottom": -0.040022353255703365,
+                "right": 0.50538130495484801,
+                "top": 0.56834266575570347
+            },
+            "atlasBounds": {
+                "left": 0.5,
+                "bottom": 17.5,
+                "right": 17.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 121,
+            "advance": 0.47314453125,
+            "planeBounds": {
+                "left": -0.022715289329847906,
+                "bottom": -0.25317568470769958,
+                "right": 0.4943949768298479,
+                "top": 0.56811709095769969
+            },
+            "atlasBounds": {
+                "left": 94.5,
+                "bottom": 119.5,
+                "right": 111.5,
+                "top": 146.5
+            }
+        },
+        {
+            "unicode": 122,
+            "advance": 0.49560546875,
+            "planeBounds": {
+                "left": 0.0093395392704372342,
+                "bottom": -0.040022353255703365,
+                "right": 0.49603155447956265,
+                "top": 0.56834266575570347
+            },
+            "atlasBounds": {
+                "left": 53.5,
+                "bottom": 17.5,
+                "right": 69.5,
+                "top": 37.5
+            }
+        },
+        {
+            "unicode": 123,
+            "advance": 0.33837890625,
+            "planeBounds": {
+                "left": -0.0033102869534220817,
+                "bottom": -0.21657315678469582,
+                "right": 0.361708724453422,
+                "top": 0.81764737553469591
+            },
+            "atlasBounds": {
+                "left": 47.5,
+                "bottom": 177.5,
+                "right": 59.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 124,
+            "advance": 0.24365234375,
+            "planeBounds": {
+                "left": 0.045780544498574161,
+                "bottom": -0.16672298300855509,
+                "right": 0.19787179925142587,
+                "top": 0.74582454550855526
+            },
+            "atlasBounds": {
+                "left": 128.5,
+                "bottom": 181.5,
+                "right": 133.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 125,
+            "advance": 0.33837890625,
+            "planeBounds": {
+                "left": -0.02503880257842208,
+                "bottom": -0.21657315678469582,
+                "right": 0.339980208828422,
+                "top": 0.81764737553469591
+            },
+            "atlasBounds": {
+                "left": 24.5,
+                "bottom": 177.5,
+                "right": 36.5,
+                "top": 211.5
+            }
+        },
+        {
+            "unicode": 126,
+            "advance": 0.68017578125,
+            "planeBounds": {
+                "left": 0.020940396269011466,
+                "bottom": 0.15706318322243343,
+                "right": 0.65972366623098866,
+                "top": 0.43082744177756649
+            },
+            "atlasBounds": {
+                "left": 73.5,
+                "bottom": 7.5,
+                "right": 94.5,
+                "top": 16.5
+            }
+        }
+    ],
+    "kerning": []
+}
\ No newline at end of file
diff --git a/kayak_font/examples/main.rs b/kayak_font/examples/main.rs
deleted file mode 100644
index 2bed82802396a4971c6a7b678c0fcbce6819365f..0000000000000000000000000000000000000000
--- a/kayak_font/examples/main.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use std::{fs::File, io::BufWriter};
-
-use kayak_font::{
-    compute_msdf, recolor_contours, rescale_contours, Angle, FlatPathBuilder, PathCollector, Point,
-    Rect,
-};
-
-pub fn main() {
-    let font_data = include_bytes!("../resources/Roboto-Regular.ttf");
-    let face = ttf_parser::Face::from_slice(font_data, 0).unwrap();
-
-    let char_dim: u32 = 64;
-
-    let c = 'A';
-    if let Some(glyph) = face.glyph_index(c) {
-        let mut path_collector = PathCollector::new();
-        path_collector.scale = 1.0; //0.0001; //1024 as f32 / face.units_per_em() as f32;
-        let rect = face.outline_glyph(glyph, &mut path_collector).unwrap();
-        let contours = path_collector.build();
-        let uv_rect = Rect::new(Point::new(0.0, 0.0), lyon_geom::math::Size::new(1.0, 1.0));
-
-        let font_rect = Rect::new(
-            Point::new(rect.x_min as f32, rect.y_min as f32),
-            lyon_geom::math::Size::new(rect.width() as f32, rect.height() as f32),
-        );
-
-        let (contours, _) = rescale_contours(contours, font_rect, uv_rect, 0);
-
-        let contours = recolor_contours(contours, Angle::degrees(3.0), 1);
-        let msdf = compute_msdf(&contours, char_dim as usize);
-
-        let file = File::create(format!("./test-{}.png", c)).unwrap();
-        let ref mut w = BufWriter::new(file);
-
-        let pixels: Vec<u8> = msdf
-            .iter()
-            .flat_map(|y| {
-                y.iter().flat_map(|pixel| {
-                    vec![
-                        (pixel.0 * 255.0) as u8,
-                        (pixel.1 * 255.0) as u8,
-                        (pixel.2 * 255.0) as u8,
-                        255u8,
-                    ]
-                })
-            })
-            .collect();
-
-        let mut encoder = png::Encoder::new(w, char_dim, char_dim);
-        encoder.set_color(png::ColorType::RGBA);
-        let mut writer = encoder.write_header().unwrap();
-        writer.write_image_data(&pixels).unwrap();
-    }
-}
diff --git a/kayak_font/examples/simple.rs b/kayak_font/examples/simple.rs
deleted file mode 100644
index 97efddbdff2d524c27beb9bf9a17a6636d0dd145..0000000000000000000000000000000000000000
--- a/kayak_font/examples/simple.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use std::{fs::File, io::BufWriter};
-
-use kayak_font::Font;
-
-fn main() {
-    let font_bytes = include_bytes!("../resources/Roboto-Regular.ttf");
-    let mut font = Font::new(font_bytes, (64, 64));
-
-    font.add_character('A');
-    font.add_character('B');
-    font.add_character('C');
-    font.add_character('!');
-    font.add_character('&');
-
-    // Characters that have already been calculated wont be calculated again!
-    for _ in 0..1000000 {
-        font.add_character('A');
-    }
-
-    let dimensions = font.cache.dimensions;
-    for (c, _, pixels) in font.get_data_to_process() {
-        let file = File::create(format!("./test-{}.png", c)).unwrap();
-        let ref mut w = BufWriter::new(file);
-
-        let mut encoder = png::Encoder::new(w, dimensions.0, dimensions.1);
-        encoder.set_color(png::ColorType::RGBA);
-        let mut writer = encoder.write_header().unwrap();
-        writer.write_image_data(&pixels).unwrap();
-    }
-}
diff --git a/kayak_font/resources/Roboto-Regular.ttf b/kayak_font/resources/Roboto-Regular.ttf
deleted file mode 100644
index 2b6392ffe8712b9c5450733320cd220d6c0f4bce..0000000000000000000000000000000000000000
Binary files a/kayak_font/resources/Roboto-Regular.ttf and /dev/null differ
diff --git a/bevy_kayak_ui/src/render/unified/font/sdf/atlas.rs b/kayak_font/src/atlas.rs
similarity index 100%
rename from bevy_kayak_ui/src/render/unified/font/sdf/atlas.rs
rename to kayak_font/src/atlas.rs
diff --git a/kayak_font/src/color_flags.rs b/kayak_font/src/color_flags.rs
deleted file mode 100644
index 7322912b4e0c849822302ba5bcc85f374b1af552..0000000000000000000000000000000000000000
--- a/kayak_font/src/color_flags.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-bitflags::bitflags! {
-    pub struct ColorFlags: u32 {
-        const BLACK = 0b000;
-        const RED = 0b001;
-        const GREEN = 0b010;
-        const BLUE = 0b100;
-        const YELLOW = 0b011;
-        const MAGENTA = 0b101;
-        const CYAN = 0b110;
-        const WHITE = 0b111;
-    }
-}
-
-impl ColorFlags {
-    pub(crate) fn switch(self, seed: &mut u64) -> Self {
-        match self {
-            ColorFlags::WHITE | ColorFlags::BLACK => {
-                const START: [ColorFlags; 3] =
-                    [ColorFlags::CYAN, ColorFlags::MAGENTA, ColorFlags::YELLOW];
-                let tr = START[(*seed % 3) as usize];
-                *seed /= 3;
-                tr
-            }
-            ColorFlags::RED | ColorFlags::GREEN | ColorFlags::BLUE => self ^ ColorFlags::WHITE,
-            _ => {
-                let v = self.bits();
-                let v = (v << (1 + (*seed & 1))) & 0b111;
-                let v = match v.count_ones() {
-                    0 => 0b11,           /* Somehow we lost all the bits. Default to yellow */
-                    1 => v | 0b001, /* We just shifted a bit off the left side, add one on the right */
-                    2 => v,         /* We already have 2 bits, nothing to do */
-                    _ => unreachable!(), /* There should never be 3+ bits set */
-                };
-                *seed >>= 1;
-
-                Self::from_bits_truncate(v)
-            }
-        }
-    }
-
-    pub(crate) fn switch_banned(self, seed: &mut u64, banned: ColorFlags) -> Self {
-        let combined = self & banned;
-        match combined {
-            ColorFlags::RED | ColorFlags::GREEN | ColorFlags::BLUE => combined ^ ColorFlags::WHITE,
-            _ => self.switch(seed),
-        }
-    }
-
-    pub fn float_color(self) -> [f32; 3] {
-        match self {
-            ColorFlags::BLACK => [0.0f32, 0.0f32, 0.0f32],
-            ColorFlags::RED => [1.0f32, 0.0f32, 0.0f32],
-            ColorFlags::GREEN => [0.0f32, 1.0f32, 0.0f32],
-            ColorFlags::BLUE => [0.0f32, 0.0f32, 1.0f32],
-            ColorFlags::CYAN => [0.0f32, 1.0f32, 1.0f32],
-            ColorFlags::MAGENTA => [1.0f32, 0.0f32, 1.0f32],
-            ColorFlags::YELLOW => [1.0f32, 1.0f32, 0.0f32],
-            ColorFlags::WHITE => [1.0f32, 1.0f32, 1.0f32],
-            _ => [0.5, 0.7, 0.5],
-        }
-    }
-}
diff --git a/kayak_font/src/contour.rs b/kayak_font/src/contour.rs
deleted file mode 100644
index 6b21056bbca0170bc293f4ba54f802b5b6c57cb6..0000000000000000000000000000000000000000
--- a/kayak_font/src/contour.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-use lyon_geom::math::{Point, Rect};
-use lyon_path::Segment;
-
-use crate::path_element::PathElement;
-
-/// A list of path elements forming a closed loop
-#[derive(Clone, Debug)]
-pub struct Contour {
-    pub elements: Vec<PathElement>,
-}
-
-impl Contour {
-    pub fn winding(&self) -> f32 {
-        let shoelace = |a: Point, b: Point| (b.x - a.x) * (a.y + b.y);
-        let n = self.elements.len();
-        match n {
-            0 => 0.0,
-            1 => {
-                let a = self.elements[0].sample(0.0);
-                let b = self.elements[0].sample(1.0 / 3.0);
-                let c = self.elements[0].sample(2.0 / 3.0);
-
-                shoelace(a, b) + shoelace(b, c) + shoelace(c, a)
-            }
-            2 => {
-                let a = self.elements[0].sample(0.0);
-                let b = self.elements[0].sample(0.5);
-                let c = self.elements[1].sample(0.0);
-                let d = self.elements[1].sample(0.5);
-
-                shoelace(a, b) + shoelace(b, c) + shoelace(c, d) + shoelace(d, a)
-            }
-            _ => {
-                let mut total = 0.0;
-                let mut prev = self.elements[n - 1].sample(0.0);
-
-                for e in &self.elements {
-                    let curr = e.sample(0.0);
-                    total += shoelace(prev, curr);
-                    prev = curr;
-                }
-
-                total
-            }
-        }
-        .signum()
-    }
-}
-
-/// Rescale contours so they fit in the provided rectangle.
-/// Returns the scaled contours along with the transformation used to rescale the contours
-pub fn rescale_contours(
-    mut contours: Vec<Contour>,
-    initial_bounds: Rect,
-    bounds: Rect,
-    _units_per_em: u16,
-) -> (Vec<Contour>, lyon_geom::math::Transform2D) {
-    // let (new_width, new_height) = if initial_bounds.size.width > initial_bounds.size.height {
-    //     let new_width = 1.0;
-    //     let new_height = aspect_ratio_height(
-    //         initial_bounds.size.height,
-    //         initial_bounds.size.width,
-    //         new_width,
-    //     );
-
-    //     (new_width, new_height)
-    // } else {
-    //     let new_height = 1.0;
-    //     let new_width = aspect_ratio_width(
-    //         initial_bounds.size.height,
-    //         initial_bounds.size.width,
-    //         new_height,
-    //     );
-
-    //     (new_width, new_height)
-    // };
-
-
-    // let x_scale = new_width / initial_bounds.size.width;
-    // let y_scale = new_height / initial_bounds.size.height;
-
-    let initial_scale = initial_bounds.size.height.max(initial_bounds.size.width);
-    let bounds_scale = bounds.size.width.max(bounds.size.height);
-
-    // let size = 128.0 / units_per_em as f32;
-    let transformation = lyon_geom::math::Transform2D::create_translation(
-        -initial_bounds.origin.x as f32,
-        -initial_bounds.origin.y as f32,
-    )
-    .post_scale(bounds_scale / initial_scale, bounds_scale / initial_scale)
-    .post_translate(bounds.origin.to_vector());
-    for contour in &mut contours {
-        for mut elem in &mut contour.elements {
-            elem.segment = match elem.segment {
-                Segment::Line(s) => Segment::Line(s.transform(&transformation)),
-                Segment::Quadratic(s) => Segment::Quadratic(s.transform(&transformation)),
-                Segment::Cubic(s) => Segment::Cubic(s.transform(&transformation)),
-                Segment::Arc(s) => Segment::Arc(lyon_geom::Arc {
-                    center: transformation.transform_point(&s.center),
-                    ..s
-                }),
-            }
-        }
-    }
-
-    (contours, transformation)
-}
diff --git a/kayak_font/src/font.rs b/kayak_font/src/font.rs
index 81f4977deef7ebbf16ddec103bc04287ef7c0cac..2a2b06d452d28ce97ae5529d8825216517a7aeda 100644
--- a/kayak_font/src/font.rs
+++ b/kayak_font/src/font.rs
@@ -1,267 +1,36 @@
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 
-use lyon_geom::math::{Angle, Point, Rect, Size};
-use lyon_path::builder::FlatPathBuilder;
 
-use crate::{compute_msdf, recolor_contours, rescale_contours, PathCollector};
+use bevy::{prelude::Handle, reflect::TypeUuid, render2::texture::Image};
 
-#[derive(Debug, Clone)]
-pub struct FontCache {
-    count: usize,
-    pub dimensions: (u32, u32),
-    chars: HashMap<char, (usize, Vec<u8>)>,
-    needs_processing: HashSet<usize>,
-    id_to_char_mappings: HashMap<usize, char>,
-}
-
-impl FontCache {
-    pub fn new(texture_size: (u32, u32)) -> Self {
-        Self {
-            count: 0,
-            dimensions: texture_size,
-            chars: HashMap::default(),
-            needs_processing: HashSet::default(),
-            id_to_char_mappings: HashMap::default(),
-        }
-    }
-
-    pub fn add_character(&mut self, c: char) {
-        self.chars.insert(c, (self.count, vec![]));
-        self.id_to_char_mappings.insert(self.count, c);
-        self.count += 1;
-    }
-
-    fn set_texture(&mut self, c: char, texture_data: Vec<Vec<(f32, f32, f32)>>) {
-        // let pixels: Vec<u8> = texture_data
-        //     .iter()
-        //     .flat_map(|y| {
-        //         y.iter().flat_map(|pixel| {
-        //             vec![
-        //                 (pixel.0 * 255.0) as u8,
-        //                 (pixel.1 * 255.0) as u8,
-        //                 (pixel.2 * 255.0) as u8,
-        //                 255u8,
-        //             ]
-        //         })
-        //     })
-        //     .collect();
-        let pixels = texture_data
-            .iter()
-            .flat_map(|x| {
-                x.iter()
-                    .flat_map(|p| {
-                        vec![
-                            p.0.to_le_bytes(),
-                            p.1.to_le_bytes(),
-                            p.2.to_le_bytes(),
-                            1.0f32.to_le_bytes(),
-                        ]
-                        .into_iter()
-                        .flatten()
-                        .collect::<Vec<u8>>()
-                    })
-                    .collect::<Vec<u8>>()
-            })
-            .collect();
-        self.chars.insert(c, (self.count, pixels));
-        self.needs_processing.insert(self.count);
-        self.id_to_char_mappings.insert(self.count, c);
-        self.count += 1;
-    }
+use crate::Sdf;
 
-    pub fn has_character(&self, c: char) -> bool {
-        self.chars.contains_key(&c)
-    }
-
-    fn get_dimensions(&self) -> (u32, u32) {
-        self.dimensions
-    }
+#[derive(Debug, Clone, TypeUuid)]
+#[uuid = "4fe4732c-6731-49bb-bafc-4690d636b848"]
+pub struct KayakFont {
+    pub sdf: Sdf,
+    pub atlas_image: Handle<Image>,
+    char_ids: HashMap<char, u32>,
 }
 
-#[derive(Debug, Clone)]
-pub struct Font {
-    internal_face: ttf_parser::Face<'static>,
-    font: fontdue::Font,
-    pub cache: FontCache,
-}
-
-impl Font {
-    pub fn new(font_data: &'static [u8], texture_size: (u32, u32)) -> Font {
-        Font {
-            internal_face: ttf_parser::Face::from_slice(&font_data, 0).unwrap(),
-            font: fontdue::Font::from_bytes(font_data.clone(), fontdue::FontSettings::default())
-                .unwrap(),
-            cache: FontCache::new(texture_size),
-        }
-    }
-
-    /// Adds all of the common known characters.
-    pub fn add_all_common(&mut self) {
-        let chars = vec![
-            '`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '~', '!', '@', '#',
-            '$', '%', '^', '&', '*', '(', ')', '_', '+', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
-            'o', 'p', '[', ']', '\\', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}',
-            '|', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', 'A', 'S', 'D', 'F', 'G',
-            'H', 'J', 'K', 'L', ':', '"', 'z', 'x', 'c', 'v', 'b', 'n', 'n', 'm', ',', '.', '/',
-            'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
-        ];
-
-        for char in chars {
-            self.add_character(char);
-        }
-    }
-
-    pub fn get_layout(
-        &self,
-        content: &String,
-        font_size: f32,
-        original_font_size: f32,
-        max_glyph_size: (f32, f32),
-    ) -> Vec<(char, (f32, f32), (f32, f32))> {
-        let mut layout =
-            fontdue::layout::Layout::new(fontdue::layout::CoordinateSystem::PositiveYDown);
-        layout.append(
-            &[&self.font],
-            &fontdue::layout::TextStyle::new(content, font_size, 0),
-        );
-        let font_ratio = font_size / original_font_size;
-        let resized_max_glyph_size = (
-            (max_glyph_size.0 * font_ratio).round(),
-            (max_glyph_size.1 * font_ratio).round(),
-        );
-
-        let glyphs = layout.glyphs();
-        let glyphs: Vec<_> = glyphs
-            .iter()
-            .filter_map(|glyph_position| {
-                if glyph_position.parent == ' ' {
-                    return None;
-                }
-                // let metrics = self.font.metrics(glyph_position.parent, font_size);
-
-                let shift_y = resized_max_glyph_size.1 - glyph_position.height as f32;
-                Some((
-                    glyph_position.parent,
-                    (glyph_position.x, glyph_position.y - shift_y),
-                    resized_max_glyph_size,
-                ))
-            })
-            .collect();
-        glyphs
-    }
-
-    // pub fn get_size(&self, c: char, font_size: f32) -> (f32, f32) {
-    //     if let Some(glyph) = self.internal_face.glyph_index(c) {
-    //         // Collect our path's from the glyph's outline shape.
-    //         let mut path_collector = PathCollector::new();
-    //         let rect = self
-    //             .internal_face
-    //             .outline_glyph(glyph, &mut path_collector)
-    //             .unwrap();
-    //         let metrics = font_size / self.font.units_per_em();
-
-    //         (width as f32 * metrics, height as f32 * metrics)
-    //     } else {
-    //         panic!("")
-    //     }
-    // }
-
-    pub fn get_char_id(&self, c: char) -> usize {
-        if self.cache.has_character(c) {
-            if let Some((id, _)) = self.cache.chars.get(&c) {
-                return *id;
-            }
+impl KayakFont {
+    pub fn new(sdf: Sdf, atlas_image: Handle<Image>) -> Self {
+        Self {
+            sdf,
+            atlas_image,
+            char_ids: HashMap::default(),
         }
-        panic!("No char found!");
     }
 
-    pub fn add_character(&mut self, c: char) {
-        if !self.cache.has_character(c) {
-            if let Some(glyph) = self.internal_face.glyph_index(c) {
-                // Collect our path's from the glyph's outline shape.
-                let mut path_collector = PathCollector::new();
-                let rect = self
-                    .internal_face
-                    .outline_glyph(glyph, &mut path_collector)
-                    .unwrap();
-                let contours = path_collector.build();
-
-                // Bounds of our texture in UV's
-                // TODO: Allow this to change because some people may want texture atlases instead.
-                let uv_rect = Rect::new(Point::new(0.0, 0.0), Size::new(1.0, 1.0));
-
-                // Bounds of our rect in font space coords.
-                let font_rect = Rect::new(
-                    Point::new(rect.x_min as f32, rect.y_min as f32),
-                    Size::new(rect.width() as f32, rect.height() as f32),
-                );
-
-                let (contours, _transform) = rescale_contours(
-                    contours,
-                    font_rect,
-                    uv_rect,
-                    self.internal_face.units_per_em(),
-                );
-                let contours = recolor_contours(contours, Angle::degrees(3.0), 1);
-                let msdf = compute_msdf(&contours, self.cache.get_dimensions().0 as usize);
-
-                self.cache.set_texture(c, msdf);
-            }
+    pub fn generate_char_ids(&mut self) {
+        let mut count = 0;
+        for glyph in self.sdf.glyphs.iter() {
+            self.char_ids.insert(glyph.unicode, count);
+            count += 1;
         }
     }
 
-    pub fn get_data_to_process<'b>(&'b mut self) -> Vec<(char, usize, &'b Vec<u8>)> {
-        let data = self
-            .cache
-            .needs_processing
-            .iter()
-            .filter_map(|unprocessed_id| {
-                if let Some(c) = self.cache.id_to_char_mappings.get(unprocessed_id) {
-                    if let Some((_, data)) = self.cache.chars.get(c) {
-                        return Some((*c, *unprocessed_id, data));
-                    }
-                }
-
-                None
-            })
-            .collect();
-
-        self.cache.needs_processing.clear();
-
-        data
+    pub fn get_char_id(&self, c: char) -> Option<u32> {
+        self.char_ids.get(&c).and_then(|id| Some(*id))
     }
-
-    // Checks the given chars and returns ones that haven't been seen before.
-    pub fn check_chars(&self, chars: std::str::Chars<'_>) -> Vec<char> {
-        chars
-            .into_iter()
-            .filter(|c| !self.cache.chars.contains_key(&c))
-            .collect()
-    }
-
-    pub fn units_per_em(&self) -> f32 {
-        self.font.units_per_em()
-    }
-}
-
-fn get_new_size(org_width: f32, new_width: f32, org_height: f32, new_height: f32) -> (f32, f32) {
-    let ratio = calculate_ratio(org_width, new_width, org_height, new_height);
-    // let ratio = new_width / new_height;
-    (org_width * ratio, org_height * ratio)
-}
-
-pub fn calculate_ratio(org_width: f32, new_width: f32, org_height: f32, new_height: f32) -> f32 {
-    let area_size;
-    let image_size;
-
-    if new_height * org_width > new_width * org_height {
-        area_size = new_height;
-        image_size = org_height;
-    } else {
-        area_size = new_width;
-        image_size = org_width;
-    }
-
-    let ratio = area_size / image_size;
-    ratio
 }
diff --git a/bevy_kayak_ui/src/render/unified/font/sdf/glyph.rs b/kayak_font/src/glyph.rs
similarity index 100%
rename from bevy_kayak_ui/src/render/unified/font/sdf/glyph.rs
rename to kayak_font/src/glyph.rs
diff --git a/kayak_font/src/lib.rs b/kayak_font/src/lib.rs
index 6a97d016cc79e585a2677afe72f921e420481919..3ffc49344dfbaccbf3cc3b7d34e8d43d20cbc774 100644
--- a/kayak_font/src/lib.rs
+++ b/kayak_font/src/lib.rs
@@ -1,28 +1,14 @@
-#![allow(dead_code)]
-mod color_flags;
-mod contour;
+mod atlas;
 mod font;
-mod msdf;
-mod path_collector;
-mod path_element;
-mod recolor;
+mod glyph;
+mod metrics;
+mod renderer;
 mod sdf;
-mod ttf_parser;
-mod utils;
 
-pub use color_flags::ColorFlags;
-pub use contour::{rescale_contours, Contour};
-pub use font::{Font, FontCache};
-pub use lyon_geom::math::{Angle, Point, Rect, Vector};
-pub use lyon_path::builder::FlatPathBuilder;
-pub use msdf::compute_msdf;
-pub use path_collector::PathCollector;
-pub use path_element::PathElement;
-pub use recolor::recolor_contours;
-pub use sdf::compute_sdf;
+pub use atlas::*;
+pub use font::*;
+pub use glyph::*;
+pub use metrics::*;
+pub use sdf::*;
 
-pub(crate) fn median(a: f32, b: f32, c: f32) -> f32 {
-    let min = |a: f32, b: f32| a.min(b);
-    let max = |a: f32, b: f32| a.max(b);
-    max(min(a, b), min(max(a, b), c))
-}
+pub use renderer::*;
diff --git a/bevy_kayak_ui/src/render/unified/font/sdf/metrics.rs b/kayak_font/src/metrics.rs
similarity index 100%
rename from bevy_kayak_ui/src/render/unified/font/sdf/metrics.rs
rename to kayak_font/src/metrics.rs
diff --git a/kayak_font/src/msdf.rs b/kayak_font/src/msdf.rs
deleted file mode 100644
index 8429e93d4e4fa611b56a998517245cd3676abb84..0000000000000000000000000000000000000000
--- a/kayak_font/src/msdf.rs
+++ /dev/null
@@ -1,171 +0,0 @@
-use lyon_geom::math::Vector;
-
-use crate::{contour::Contour, median, utils::EdgeDistance, ColorFlags};
-
-/// Computes an MSDF from a list of contours. The returned vectors are a `dim` by `dim`
-/// matrix of signed distance values. The output represents the signed distances to the contours
-/// within [0, 1] x [0, 1]
-pub fn compute_msdf(contours: &[Contour], dim: usize) -> Vec<Vec<(f32, f32, f32)>> {
-    #[derive(Copy, Clone, PartialEq)]
-    struct MultiDistance {
-        r: f32,
-        g: f32,
-        b: f32,
-        med: f32,
-    }
-    impl MultiDistance {
-        fn new(v: f32) -> Self {
-            Self {
-                r: v,
-                g: v,
-                b: v,
-                med: v,
-            }
-        }
-    }
-    let scale: f32 = 1.0 / (dim as f32);
-    let windings: Vec<i32> = contours.iter().map(|c| c.winding() as i32).collect();
-
-    (0..dim)
-        .map(|y| {
-            let py = (y as f32 + 0.5) * scale;
-            (0..dim)
-                .map(|x| {
-                    // We assume there is at least 1 contour
-                    // If there isn't make everything magenta
-                    if contours.len() == 0 {
-                        return (1.0f32, 0.0, 1.0);
-                    }
-
-                    let px = (x as f32 + 0.5) * scale;
-                    let p = Vector::new(px, py);
-
-                    let mut neg_dist = 1e24f32;
-                    let mut pos_dist = -1e24f32;
-                    let mut d = 1e24f32;
-                    let mut winding = 0;
-                    let mut contour_distances = Vec::new();
-                    contour_distances.reserve(contours.len());
-
-                    let mut sr = EdgeDistance::new();
-                    let mut sg = EdgeDistance::new();
-                    let mut sb = EdgeDistance::new();
-
-                    for (i, contour) in contours.iter().enumerate() {
-                        let mut contour_min_r = EdgeDistance::new();
-                        let mut contour_min_g = EdgeDistance::new();
-                        let mut contour_min_b = EdgeDistance::new();
-
-                        for elem in &contour.elements {
-                            let (d, na) = elem.distance(p.to_point());
-
-                            if elem.color.contains(ColorFlags::RED) && d < contour_min_r.dist {
-                                contour_min_r.dist = d;
-                                contour_min_r.edge = Some(&elem);
-                                contour_min_r.nearest_approach = na;
-                            }
-                            if elem.color.contains(ColorFlags::GREEN) && d < contour_min_g.dist {
-                                contour_min_g.dist = d;
-                                contour_min_g.edge = Some(&elem);
-                                contour_min_g.nearest_approach = na;
-                            }
-                            if elem.color.contains(ColorFlags::BLUE) && d < contour_min_b.dist {
-                                contour_min_b.dist = d;
-                                contour_min_b.edge = Some(&elem);
-                                contour_min_b.nearest_approach = na;
-                            }
-                        }
-
-                        if contour_min_r.dist < sr.dist {
-                            sr = contour_min_r;
-                        }
-                        if contour_min_g.dist < sg.dist {
-                            sg = contour_min_g;
-                        }
-                        if contour_min_b.dist < sb.dist {
-                            sb = contour_min_b;
-                        }
-
-                        let med_min_dist = median(
-                            contour_min_r.dist.distance,
-                            contour_min_g.dist.distance,
-                            contour_min_b.dist.distance,
-                        )
-                        .abs();
-                        if med_min_dist < d {
-                            d = med_min_dist;
-                            winding = -windings[i];
-                        }
-
-                        contour_min_r.to_pseudodistance(p);
-                        contour_min_g.to_pseudodistance(p);
-                        contour_min_b.to_pseudodistance(p);
-
-                        let med_min_dist = median(
-                            contour_min_r.dist.distance,
-                            contour_min_g.dist.distance,
-                            contour_min_b.dist.distance,
-                        );
-
-                        let mut msd = MultiDistance::new(med_min_dist);
-                        msd.r = contour_min_r.dist.distance;
-                        msd.g = contour_min_g.dist.distance;
-                        msd.b = contour_min_b.dist.distance;
-                        msd.med = med_min_dist;
-                        contour_distances.push(msd);
-                        if windings[i] > 0
-                            && med_min_dist >= 0.0
-                            && med_min_dist.abs() < pos_dist.abs()
-                        {
-                            pos_dist = med_min_dist;
-                        }
-                        if windings[i] < 0
-                            && med_min_dist <= 0.0
-                            && med_min_dist.abs() < neg_dist.abs()
-                        {
-                            neg_dist = med_min_dist;
-                        }
-                    }
-
-                    assert!(contour_distances.len() == windings.len());
-
-                    sr.to_pseudodistance(p);
-                    sg.to_pseudodistance(p);
-                    sb.to_pseudodistance(p);
-
-                    let mut mmsd = MultiDistance::new(-1e24);
-                    if pos_dist >= 0.0 && pos_dist.abs() <= neg_dist.abs() {
-                        mmsd.med = -1e24;
-                        winding = 1;
-                        for (csd, cw) in contour_distances.iter().zip(windings.iter()) {
-                            if *cw > 0 && csd.med > mmsd.med && csd.med.abs() < neg_dist.abs() {
-                                mmsd = *csd;
-                            }
-                        }
-                    } else if neg_dist <= 0.0 && neg_dist.abs() <= pos_dist.abs() {
-                        mmsd.med = 1e24;
-                        winding = -1;
-                        for (csd, cw) in contour_distances.iter().zip(windings.iter()) {
-                            if *cw < 0 && csd.med < mmsd.med && csd.med.abs() < pos_dist.abs() {
-                                mmsd = *csd;
-                            }
-                        }
-                    }
-                    for (csd, w) in contour_distances.iter().zip(windings.iter()) {
-                        if *w != winding && csd.med.abs() < mmsd.med.abs() {
-                            mmsd = *csd;
-                        }
-                    }
-
-                    if median(sr.dist.distance, sg.dist.distance, sb.dist.distance) == mmsd.med {
-                        mmsd.r = sr.dist.distance;
-                        mmsd.g = sg.dist.distance;
-                        mmsd.b = sb.dist.distance;
-                    }
-
-                    (mmsd.r / 0.5, mmsd.g / 0.5, mmsd.b / 0.5)
-                })
-                .collect()
-        })
-        .collect()
-}
diff --git a/kayak_font/src/path_collector.rs b/kayak_font/src/path_collector.rs
deleted file mode 100644
index 077bc44911cf62a3f6db4483a20852c31a446495..0000000000000000000000000000000000000000
--- a/kayak_font/src/path_collector.rs
+++ /dev/null
@@ -1,134 +0,0 @@
-use lyon_geom::math::{Angle, Point, Vector};
-use lyon_path::{
-    builder::{FlatPathBuilder, PathBuilder},
-    Segment,
-};
-
-use crate::{color_flags::ColorFlags, contour::Contour, path_element::PathElement};
-
-/// This is a path collector which produces our custom contour type.
-pub struct PathCollector {
-    /// The start point of the last contour
-    pub(crate) contour_start: Point,
-    /// The current pen location
-    pub(crate) pen: Point,
-    /// in-flight path elements
-    pub(crate) elements: Vec<PathElement>,
-    /// Completed contours
-    pub(crate) contours: Vec<Contour>,
-    pub scale: f32,
-}
-
-impl PathCollector {
-    pub fn new() -> Self {
-        Self {
-            contour_start: Point::new(0.0, 0.0),
-            pen: Point::new(0.0, 0.0),
-            elements: Vec::new(),
-            contours: Vec::new(),
-            scale: 1.0,
-        }
-    }
-}
-
-impl PathBuilder for PathCollector {
-    fn quadratic_bezier_to(&mut self, ctrl: Point, to: Point) {
-        self.elements.push(PathElement::new(
-            Segment::Quadratic(lyon_geom::QuadraticBezierSegment {
-                from: self.pen * self.scale,
-                to: to * self.scale,
-                ctrl: ctrl * self.scale,
-            }),
-            ColorFlags::WHITE,
-        ));
-        self.pen = to;
-    }
-
-    fn cubic_bezier_to(&mut self, ctrl1: Point, ctrl2: Point, to: Point) {
-        self.elements.push(PathElement::new(
-            Segment::Cubic(lyon_geom::CubicBezierSegment {
-                from: self.pen * self.scale,
-                to: to * self.scale,
-                ctrl1: ctrl1 * self.scale,
-                ctrl2: ctrl2 * self.scale,
-            }),
-            ColorFlags::WHITE,
-        ));
-
-        self.pen = to;
-    }
-
-    fn arc(&mut self, _center: Point, _radii: Vector, _sweep_angle: Angle, _x_rotation: Angle) {
-        unimplemented!()
-    }
-}
-
-impl FlatPathBuilder for PathCollector {
-    type PathType = Vec<Contour>;
-
-    fn move_to(&mut self, to: Point) {
-        self.pen = to * self.scale;
-        self.contour_start = to * self.scale;
-    }
-
-    fn line_to(&mut self, to: Point) {
-        self.elements.push(PathElement::new(
-            Segment::Line(lyon_geom::LineSegment {
-                from: self.pen * self.scale,
-                to: to * self.scale,
-            }),
-            ColorFlags::WHITE,
-        ));
-        self.pen = to * self.scale;
-    }
-
-    fn close(&mut self) {
-        if (self.pen - self.contour_start).length() > 1E-14 {
-            self.elements.push(PathElement::new(
-                Segment::Line(lyon_geom::LineSegment {
-                    from: self.pen * self.scale,
-                    to: self.contour_start * self.scale,
-                }),
-                ColorFlags::WHITE,
-            ));
-        }
-
-        self.pen = self.contour_start;
-        let elements = std::mem::replace(&mut self.elements, Vec::new());
-
-        self.contours.push(Contour { elements });
-    }
-
-    fn build(self) -> Self::PathType {
-        let mut contours = self.contours;
-        if self.elements.len() > 0 {
-            let final_contour = Contour {
-                elements: self.elements,
-            };
-
-            contours.push(final_contour);
-        }
-
-        contours
-    }
-
-    fn build_and_reset(&mut self) -> Self::PathType {
-        let elements = std::mem::replace(&mut self.elements, Vec::new());
-        if elements.len() > 0 {
-            let final_contour = Contour { elements };
-
-            self.contours.push(final_contour);
-        }
-
-        let tr = std::mem::replace(&mut self.contours, Vec::new());
-
-        self.contour_start = Point::new(0.0, 0.0);
-        self.pen = Point::new(0.0, 0.0);
-
-        tr
-    }
-
-    fn current_position(&self) -> Point {
-        self.pen
-    }
-}
diff --git a/kayak_font/src/path_element.rs b/kayak_font/src/path_element.rs
deleted file mode 100644
index 7500fccbbf88b412570d39a17ab2517b3c213e20..0000000000000000000000000000000000000000
--- a/kayak_font/src/path_element.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-use crate::{color_flags::ColorFlags, utils::AugmentedDistance};
-use lyon_geom::math::{Point, Vector};
-use lyon_path::Segment;
-
-#[derive(Clone, Debug, Copy)]
-pub struct PathElement {
-    pub segment: Segment,
-    pub color: ColorFlags,
-}
-
-impl PathElement {
-    pub fn new(segment: Segment, color: ColorFlags) -> PathElement {
-        Self { segment, color }
-    }
-
-    pub fn sample(&self, t: f32) -> Point {
-        match self.segment {
-            Segment::Line(s) => s.sample(t),
-            Segment::Quadratic(s) => s.sample(t),
-            Segment::Cubic(s) => s.sample(t),
-            Segment::Arc(s) => s.sample(t),
-        }
-    }
-
-    pub fn direction(&self, f: f32) -> Vector {
-        use lyon_geom::Segment as SegmentTrait;
-        let f = f.min(1.0).max(0.0);
-        match self.segment {
-            Segment::Line(s) => s.derivative(f),
-            Segment::Quadratic(s) => s.derivative(f),
-            Segment::Cubic(s) => s.derivative(f),
-            Segment::Arc(s) => s.derivative(f),
-        }
-    }
-
-    /// Split a path element into 3rds
-    pub fn split_in_thirds(&self) -> [PathElement; 3] {
-        macro_rules! segment_case {
-            ($i:expr, $s:expr) => {{
-                let (a, b) = ($i).split(1.0 / 3.0);
-                let (b, c) = b.split(1.0 / 2.0);
-
-                [a, b, c]
-                    .into_iter()
-                    .map(|x| PathElement::new(($s)(x), self.color))
-                    .collect()
-            }};
-        }
-        let segments: arrayvec::ArrayVec<PathElement, 3> = match self.segment {
-            Segment::Line(s) => segment_case!(s, Segment::Line),
-            Segment::Quadratic(s) => segment_case!(s, Segment::Quadratic),
-            Segment::Cubic(s) => segment_case!(s, Segment::Cubic),
-            Segment::Arc(s) => segment_case!(s, Segment::Arc),
-        };
-
-        segments
-            .into_inner()
-            .expect("We should have precisely the right capacity")
-    }
-
-    /// Computes the distance from p to this path element
-    /// Returns the distance from the point to this path element,
-    /// and the distance along this element to the closest point.
-    pub fn distance(&self, p: Point) -> (AugmentedDistance, f32) {
-        use lyon_geom::{LineSegment, QuadraticBezierSegment};
-        match self.segment {
-            Segment::Line(LineSegment { from: s, to: e }) => {
-                let aq = p - s;
-                let ab = e - s;
-                let f = aq.dot(ab) / ab.dot(ab);
-                let eq = if f >= 0.5 { p - e } else { p - s };
-
-                let dist_to_endpoint = eq.length();
-                let endpoint_sd = AugmentedDistance::new(
-                    aq.cross(ab).signum() * dist_to_endpoint,
-                    // ab.normalize().cross(eq.normalize()),
-                    ab.normalize().dot(eq.normalize()).abs(),
-                );
-
-                if 0.0 < f && f < 1.0 {
-                    let ortho = Vector::new(ab.y, -ab.x).normalize();
-                    let ortho_dist = ortho.dot(aq);
-                    if ortho_dist.abs() < endpoint_sd.distance.abs() {
-                        (AugmentedDistance::new(ortho_dist, 0.0), f)
-                    } else {
-                        (endpoint_sd, f)
-                    }
-                } else {
-                    (endpoint_sd, f)
-                }
-            }
-
-            Segment::Quadratic(QuadraticBezierSegment {
-                from: p0,
-                ctrl: p1,
-                to: p2,
-            }) => {
-                use lyon_geom::utils::cubic_polynomial_roots;
-                let qa = p0 - p;
-                let ab = p1 - p0;
-                let br = (p0 - p1) + (p2 - p1);
-                let a = br.dot(br);
-                let b = 3.0 * ab.dot(br);
-                let c = 2.0 * ab.dot(ab) + qa.dot(br);
-                let d = qa.dot(ab);
-                let solutions = cubic_polynomial_roots(a, b, c, d);
-
-                let mut min_dist = ab.cross(qa).signum() * qa.length();
-
-                let mut f = -qa.dot(ab) / ab.dot(ab);
-                {
-                    let ec = p2 - p1;
-                    let ep = p2 - p;
-                    let dist = ec.cross(ep).signum() * ep.length();
-                    if dist.abs() < min_dist.abs() {
-                        min_dist = dist;
-                        f = (p - p1).dot(ec) / ec.dot(ec);
-                    }
-                }
-                for t in solutions {
-                    if t <= 0.0 || 1.0 <= t {
-                        continue;
-                    }
-                    let endpoint = p0 + (ab * 2.0 * t) + (br * t * t);
-                    let delta = endpoint - p;
-                    let dist = (p2 - p0).cross(delta).signum() * delta.length();
-
-                    if dist.abs() < min_dist.abs() {
-                        min_dist = dist;
-                        f = t;
-                    }
-                }
-
-                if 0.0 <= f && f <= 1.0 {
-                    (AugmentedDistance::new(min_dist, 0.0), f)
-                // (AugmentedDistance::new(200f32, 0.0), f)
-                } else if f < 0.5 {
-                    (
-                        AugmentedDistance::new(min_dist, ab.normalize().dot(qa.normalize()).abs()),
-                        f,
-                    )
-                } else {
-                    (
-                        AugmentedDistance::new(
-                            min_dist,
-                            (p2 - p1).normalize().dot((p2 - p).normalize()).abs(),
-                        ),
-                        f,
-                    )
-                }
-            }
-
-            _ => unimplemented!(),
-        }
-    }
-
-    pub(crate) fn to_psuedodistance(
-        &self,
-        dist: AugmentedDistance,
-        p: Vector,
-        near: f32,
-    ) -> AugmentedDistance {
-        if near <= 0.0 {
-            let dir = self.direction(0.0).normalize();
-            let aq = p - self.sample(0.0).to_vector();
-            let ts = aq.dot(dir);
-            if ts < 0.0 {
-                let ds = aq.cross(dir);
-                if ds.abs() <= dist.distance.abs() {
-                    return AugmentedDistance::new(ds, 0.0);
-                }
-            }
-
-            dist
-        } else if near >= 1.0 {
-            let dir = self.direction(1.0).normalize();
-            let aq = p - self.sample(1.0).to_vector();
-            let ts = aq.dot(dir);
-            if ts > 0.0 {
-                let ds = aq.cross(dir);
-                if ds.abs() <= dist.distance.abs() {
-                    return AugmentedDistance::new(ds, 0.0);
-                }
-            }
-
-            dist
-        } else {
-            dist
-        }
-    }
-}
diff --git a/kayak_font/src/recolor.rs b/kayak_font/src/recolor.rs
deleted file mode 100644
index 61cbbe89dea590befa760dc7b36d9ea46451a277..0000000000000000000000000000000000000000
--- a/kayak_font/src/recolor.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-use lyon_geom::math::{Angle, Vector};
-
-use crate::contour::Contour;
-use crate::ColorFlags;
-
-/// Recolor the contours prior to MSDF computation.
-/// This function uses a simple technique,
-/// again based on Viktor's implementation.
-/// It is left as a separate step so you can implement more complex techniques as desired.
-pub fn recolor_contours(contours: Vec<Contour>, threshold: Angle, mut seed: u64) -> Vec<Contour> {
-    let (threshold, _) = threshold.sin_cos();
-
-    // Determine if a point is a corner, assuming i and o are incoming and
-    // outgoing normalized direction vectors
-    let is_corner = |i: Vector, o: Vector| {
-        let d = i.dot(o); /* |i| |o| cos(t) */
-        let c = i.cross(o).abs(); /* |i| |o| sin(t) */
-
-        // if this corner turns more than 90 degrees (detected by dot product/cos)
-        // or if it turns more than the threshold angle (detected by cross product/sin)
-        (d <= 0.0) || (c > threshold)
-    };
-    contours
-        .into_iter()
-        .map(|mut c| {
-            let mut corners = Vec::new();
-            let n = c.elements.len();
-            // Find all the corners
-            if n != 0 {
-                let mut prev_dir = c.elements[n - 1].direction(1.0).normalize();
-                for (i, e) in c.elements.iter().enumerate() {
-                    let c_dir = e.direction(0.0).normalize();
-                    if is_corner(prev_dir, c_dir) {
-                        corners.push(i)
-                    }
-                    prev_dir = e.direction(1.0).normalize();
-                }
-            }
-
-            match corners.len() {
-                0 => {
-                    // The whole contour is smooth, and we initialized all colors to white.
-                    // No work to do
-                    c
-                }
-                1 => {
-                    // "Teardrop" case: there is only one sharp corner so we
-                    // just pick 3 colors up front and cycle through them
-                    let mut colors = [
-                        (ColorFlags::WHITE).switch(&mut seed),
-                        ColorFlags::WHITE,
-                        ColorFlags::WHITE,
-                    ];
-                    colors[1] = colors[0].switch(&mut seed);
-                    colors[2] = colors[1].switch(&mut seed);
-                    let corner = corners[0];
-                    match n {
-                        0 => {
-                            unreachable!();
-                        }
-                        1 => {
-                            // Only a single edge segment, but it's a teardrop.
-                            // We split it in 3 to make the colors happen
-                            let mut split = c.elements[0].split_in_thirds();
-                            split[0].color = colors[0];
-                            split[1].color = colors[1];
-                            split[2].color = colors[2];
-
-                            c.elements.clear();
-                            c.elements.extend_from_slice(&split);
-                        }
-                        2 => {
-                            // 2 segments. We split it into 6, and assign colors by hand
-                            let mut split0 = c.elements[0].split_in_thirds();
-                            let mut split1 = c.elements[1].split_in_thirds();
-                            split0[0].color = colors[0];
-                            split0[1].color = colors[0];
-                            split0[2].color = colors[1];
-                            split1[0].color = colors[1];
-                            split1[1].color = colors[2];
-                            split1[2].color = colors[2];
-
-                            c.elements.clear();
-                            c.elements.extend_from_slice(&split0);
-                            c.elements.extend_from_slice(&split1);
-                        }
-                        _ => {
-                            // We have more than 3 edges to rotate colors through
-                            for (i, e) in c.elements.iter_mut().enumerate() {
-                                // ported from this cursed C++ code:
-                                // contour->edges[(corner+i)%m]->color = (colors+1)[int(3+2.875*i/(m-1)-1.4375+.5)-3];
-                                let i = (n + i - corner) % n; // Emulate the ( corner + i) % m
-                                let idx_fractional =
-                                    3.5f32 + 2.875f32 * (i as f32) / ((n - 1) as f32) - 1.4375f32;
-                                let idx = idx_fractional.floor() as usize - 2;
-                                e.color = colors[idx];
-                            }
-                        }
-                    }
-                    c
-                }
-                _ => {
-                    // We have 2 or more corners
-                    // Cycle through colors, switching whenever we hit another corner
-                    let n_corners = corners.len();
-                    let mut spline = 0;
-                    let start = corners[0];
-                    let mut color = ColorFlags::WHITE;
-                    color = color.switch(&mut seed);
-                    let initial_color = color;
-                    for i in 0..n {
-                        let i = (start + i) % n;
-                        if spline + 1 < n_corners && corners[spline + 1] == i {
-                            spline = spline + 1;
-                            color = color.switch_banned(&mut seed, initial_color);
-                        }
-                        c.elements[i].color = color;
-                    }
-                    c
-                }
-            }
-        })
-        .collect()
-}
diff --git a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs b/kayak_font/src/renderer/font_texture_cache.rs
similarity index 94%
rename from bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs
rename to kayak_font/src/renderer/font_texture_cache.rs
index e195c2b6b5bdaad8c3e43c8b64e1bc30fcb29b27..e170a25c29476f68ef7b7ec1b77e0fe46b2e272b 100644
--- a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs
+++ b/kayak_font/src/renderer/font_texture_cache.rs
@@ -1,3 +1,4 @@
+use crate::{KayakFont, Sdf};
 use bevy::{
     math::Vec2,
     prelude::{Handle, Res},
@@ -16,9 +17,9 @@ use bevy::{
     utils::HashMap,
 };
 
-use crate::render::unified::pipeline::UnifiedPipeline;
-
-use super::{font::KayakFont, sdf::Sdf};
+pub trait FontRenderingPipeline {
+    fn get_font_image_layout(&self) -> &BindGroupLayout;
+}
 
 pub const MAX_CHARACTERS: u32 = 100;
 
@@ -59,11 +60,15 @@ impl FontTextureCache {
         }
     }
 
-    pub fn process_new(
+    pub fn get_binding(&self, handle: &Handle<KayakFont>) -> Option<&BindGroup> {
+        self.bind_groups.get(handle)
+    }
+
+    pub fn process_new<T: FontRenderingPipeline>(
         &mut self,
         device: &RenderDevice,
         queue: &RenderQueue,
-        pipeline: &UnifiedPipeline,
+        pipeline: &T,
         render_images: &Res<RenderAssets<Image>>,
     ) {
         let new_fonts: Vec<_> = self.new_fonts.drain(..).collect();
@@ -151,10 +156,7 @@ impl FontTextureCache {
         images.insert(font_handle, image);
     }
 
-    pub fn get_empty(
-        device: &RenderDevice,
-        image_layout: &BindGroupLayout,
-    ) -> (GpuImage, BindGroup) {
+    pub fn get_empty(device: &RenderDevice, layout: &BindGroupLayout) -> (GpuImage, BindGroup) {
         let texture_descriptor = TextureDescriptor {
             label: Some("font_texture_array"),
             size: Extent3d {
@@ -203,20 +205,20 @@ impl FontTextureCache {
                     resource: BindingResource::Sampler(&image.sampler),
                 },
             ],
-            layout: image_layout,
+            layout,
         });
 
         (image, binding)
     }
 
-    pub fn create_from_atlas(
+    pub fn create_from_atlas<T: FontRenderingPipeline>(
         images: &mut HashMap<Handle<KayakFont>, GpuImage>,
         bind_groups: &mut HashMap<Handle<KayakFont>, BindGroup>,
         sdf: &Sdf,
         font_handle: Handle<KayakFont>,
         device: &RenderDevice,
         queue: &RenderQueue,
-        pipeline: &UnifiedPipeline,
+        pipeline: &T,
         atlas_texture: &GpuImage,
         size: Vec2,
     ) {
@@ -247,7 +249,7 @@ impl FontTextureCache {
                     resource: BindingResource::Sampler(&gpu_image.sampler),
                 },
             ],
-            layout: &pipeline.font_image_layout,
+            layout: &pipeline.get_font_image_layout(),
         });
 
         bind_groups.insert(font_handle.clone_weak(), binding);
diff --git a/kayak_font/src/renderer/mod.rs b/kayak_font/src/renderer/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..43732619d4845bbe63ba467d5669ca5ab882b029
--- /dev/null
+++ b/kayak_font/src/renderer/mod.rs
@@ -0,0 +1,2 @@
+mod font_texture_cache;
+pub use font_texture_cache::*;
diff --git a/kayak_font/src/sdf.rs b/kayak_font/src/sdf.rs
index c1cd976de29931814a94af5857e10d47bbf65f0a..129efb18840e32148c6aa85973249ba62b5a6434 100644
--- a/kayak_font/src/sdf.rs
+++ b/kayak_font/src/sdf.rs
@@ -1,87 +1,61 @@
-use lyon_geom::math::Vector;
-
-use crate::{contour::Contour, utils::EdgeDistance};
-
-/// Computes a SDF from a list of contours. The returned vectors are a `dim` by `dim`
-/// matrix of signed distances. The output represents the signed distances to the contours
-/// within [0, 1] x [0, 1]
-pub fn compute_sdf(contours: &[Contour], dim: usize) -> Vec<Vec<f32>> {
-    let scale: f32 = 1.0 / (dim as f32);
-    let windings: Vec<i32> = contours.iter().map(|c| c.winding() as i32).collect();
-
-    (0..dim)
-        .map(|y| {
-            let py = (y as f32 + 0.5) * scale;
-            (0..dim)
-                .map(|x| {
-                    if contours.len() == 0 {
-                        return 1.0f32;
-                    }
-
-                    let px = (x as f32 + 0.5) * scale;
-                    let p = Vector::new(px, py);
-
-                    let mut neg_dist = 1e24f32;
-                    let mut pos_dist = -1e24f32;
-                    let mut winding = 0;
-                    let mut contour_distances = Vec::new();
-                    contour_distances.reserve(contours.len());
-
-                    for (i, contour) in contours.iter().enumerate() {
-                        let mut contour_min = EdgeDistance::new();
-
-                        for elem in contour.elements.iter() {
-                            let (d, na) = elem.distance(p.to_point());
-
-                            if d < contour_min.dist {
-                                contour_min.dist = d;
-                                contour_min.edge = Some(&elem);
-                                contour_min.nearest_approach = na;
-                            }
-                        }
-
-                        // contour_min.to_pseudodistance(p);
-                        let cmdd = contour_min.dist.distance;
-
-                        contour_distances.push(cmdd);
-
-                        if windings[i] > 0 && cmdd >= 0.0 && cmdd.abs() < pos_dist.abs() {
-                            pos_dist = cmdd;
-                        }
-                        if windings[i] < 0 && cmdd <= 0.0 && cmdd.abs() < neg_dist.abs() {
-                            neg_dist = cmdd;
-                        }
-                    }
+use crate::{atlas::Atlas, glyph::Glyph, metrics::Metrics, SDFType};
+use bevy::math::Vec2;
+use serde::Deserialize;
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Sdf {
+    pub atlas: Atlas,
+    metrics: Metrics,
+    pub glyphs: Vec<Glyph>,
+    kerning: Vec<KerningData>,
+}
 
-                    assert!(contour_distances.len() == windings.len());
+#[derive(serde::Deserialize, Debug, Clone, Copy)]
+pub struct KerningData {
+    pub unicode1: u32,
+    pub unicode2: u32,
+    pub advance: f32,
+}
 
-                    let mut md = -1e24;
-                    if pos_dist >= 0.0 && pos_dist.abs() <= neg_dist.abs() {
-                        md = pos_dist;
-                        winding = 1;
-                        for (d, w) in contour_distances.iter().zip(windings.iter()) {
-                            if *w > 0 && *d > md && d.abs() < neg_dist.abs() {
-                                md = *d;
-                            }
-                        }
-                    } else if neg_dist <= 0.0 && neg_dist.abs() <= pos_dist.abs() {
-                        md = neg_dist;
-                        winding = -1;
-                        for (d, w) in contour_distances.iter().zip(windings.iter()) {
-                            if *w < 0 && *d < md && d.abs() < pos_dist.abs() {
-                                md = *d;
-                            }
-                        }
-                    }
-                    for (c, w) in contour_distances.iter().zip(windings.iter()) {
-                        if *w != winding && c.abs() < md.abs() {
-                            md = *c;
-                        }
-                    }
+impl Sdf {
+    pub fn from_string(data: String) -> Sdf {
+        let value: Sdf = match serde_path_to_error::deserialize(
+            &mut serde_json::Deserializer::from_str(&data),
+        ) {
+            Ok(v) => v,
+            Err(err) => {
+                let path = err.path().to_string();
+                dbg!(err);
+                panic!("failed to deserialize json! path: {}", path);
+            }
+        };
+
+        value
+    }
+
+    pub fn max_glyph_size(&self) -> Vec2 {
+        let mut size = Vec2::new(0.0, 0.0);
+        self.glyphs.iter().for_each(|glyph| {
+            if let Some(atlas_bounds) = glyph.atlas_bounds {
+                let atlas_size = atlas_bounds.size();
+                if atlas_size.x > size.x {
+                    size.x = atlas_size.x;
+                }
+                if atlas_size.y > size.y {
+                    size.y = atlas_size.y;
+                }
+            }
+        });
+
+        size
+    }
+}
 
-                    md / 0.5
-                })
-                .collect()
-        })
-        .collect()
+#[test]
+fn test_sdf_loader() {
+    let sdf = Sdf::from_string(include_str!("../assets/roboto.json").to_string());
+    assert!(sdf.max_glyph_size() == Vec2::new(30.0, 36.0));
+    assert!(sdf.atlas.width == 212);
+    assert!(sdf.atlas.height == 212);
+    assert!(matches!(sdf.atlas.sdf_type, SDFType::Msdf));
 }
diff --git a/kayak_font/src/ttf_parser.rs b/kayak_font/src/ttf_parser.rs
deleted file mode 100644
index 4c8607ca5ee2ae16ae07e9ac8914d18bbb59a615..0000000000000000000000000000000000000000
--- a/kayak_font/src/ttf_parser.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use lyon_geom::math::Point;
-use lyon_path::Segment;
-
-use crate::{ColorFlags, Contour, PathCollector, PathElement};
-
-impl ttf_parser::OutlineBuilder for PathCollector {
-    fn move_to(&mut self, x: f32, y: f32) {
-        let to = Point::new(x, y);
-        self.pen = to;
-        self.contour_start = to;
-    }
-
-    fn line_to(&mut self, x: f32, y: f32) {
-        let to = Point::new(x, y) * self.scale;
-        self.elements.push(PathElement::new(
-            Segment::Line(lyon_geom::LineSegment { from: self.pen, to }),
-            ColorFlags::WHITE,
-        ));
-        self.pen = to;
-    }
-
-    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
-        let ctrl = Point::new(x1, y1) * self.scale;
-        let to = Point::new(x, y) * self.scale;
-        self.elements.push(PathElement::new(
-            Segment::Quadratic(lyon_geom::QuadraticBezierSegment {
-                from: self.pen,
-                to,
-                ctrl,
-            }),
-            ColorFlags::WHITE,
-        ));
-        self.pen = to;
-    }
-
-    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
-        let ctrl1 = Point::new(x1, y1) * self.scale;
-        let ctrl2 = Point::new(x2, y2) * self.scale;
-        let to = Point::new(x, y) * self.scale;
-        self.elements.push(PathElement::new(
-            Segment::Cubic(lyon_geom::CubicBezierSegment {
-                from: self.pen,
-                to,
-                ctrl1,
-                ctrl2,
-            }),
-            ColorFlags::WHITE,
-        ));
-
-        self.pen = to;
-    }
-
-    fn close(&mut self) {
-        if (self.pen - self.contour_start).length() > 1E-14 {
-            self.elements.push(PathElement::new(
-                Segment::Line(lyon_geom::LineSegment {
-                    from: self.pen * self.scale,
-                    to: self.contour_start * self.scale,
-                }),
-                ColorFlags::WHITE,
-            ));
-        }
-
-        self.pen = self.contour_start;
-        let elements = std::mem::replace(&mut self.elements, Vec::new());
-
-        self.contours.push(Contour { elements });
-    }
-}
diff --git a/kayak_font/src/utils.rs b/kayak_font/src/utils.rs
deleted file mode 100644
index 5e8e35fc6e0a8cbc7f0da38abb0c571a0039a86c..0000000000000000000000000000000000000000
--- a/kayak_font/src/utils.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-use lyon_path::math::Vector;
-
-use crate::path_element::PathElement;
-
-/// Represents a distance to an edge segment
-#[derive(Copy, Clone)]
-pub(crate) struct EdgeDistance<'a> {
-    pub(crate) dist: AugmentedDistance,
-    pub(crate) edge: Option<&'a PathElement>,
-    pub(crate) nearest_approach: f32,
-}
-
-impl<'a> EdgeDistance<'a> {
-    /// Create a new edge point, initialized to infinite distance
-    pub(crate) fn new() -> Self {
-        Self {
-            dist: AugmentedDistance::new(-1e24, 0.0),
-            edge: None,
-            nearest_approach: 0.0,
-        }
-    }
-
-    pub(crate) fn to_pseudodistance(&mut self, p: Vector) {
-        match self.edge {
-            Some(edge) => self.dist = edge.to_psuedodistance(self.dist, p, self.nearest_approach),
-            None => {}
-        }
-    }
-}
-
-/// A signed distance, augmented with the cosine of the angle
-/// between the tangent of the edge and the vector from the
-/// point of nearest approach to the measured point.
-#[derive(Copy, Clone, PartialEq, Debug)]
-pub struct AugmentedDistance {
-    /// The actual distance
-    pub(crate) distance: f32,
-    /// The cosine of the angle between the tangent vector of the path segment
-    /// at the point of closest approach and the vector from the point of
-    /// closest approach to the point to which distance was measured. This is used to
-    dot: f32,
-}
-
-impl AugmentedDistance {
-    pub(crate) fn new(distance: f32, dot: f32) -> Self {
-        Self { distance, dot }
-    }
-}
-
-impl std::cmp::PartialOrd for AugmentedDistance {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        use std::cmp::Ordering;
-
-        match self.distance.abs().partial_cmp(&other.distance.abs()) {
-            Some(Ordering::Less) => Some(Ordering::Less),
-            Some(Ordering::Greater) => Some(Ordering::Greater),
-            Some(Ordering::Equal) => self.dot.partial_cmp(&other.dot),
-            None => None,
-        }
-    }
-}