diff --git a/assets/lato-light - Copy.png b/assets/lato-light - Copy.png
deleted file mode 100644
index 103c6324efec5ccfef51aab02cde1afb0d7895ae..0000000000000000000000000000000000000000
Binary files a/assets/lato-light - Copy.png and /dev/null differ
diff --git a/assets/lato-light.kttf b/assets/lato-light.kttf
new file mode 100644
index 0000000000000000000000000000000000000000..15e547e1171fc26bd38cfd25279144307481d46c
--- /dev/null
+++ b/assets/lato-light.kttf
@@ -0,0 +1,5 @@
+{
+    "file": "lato-light.ttf",
+    "char_range_start": "0x20",
+    "char_range_end": "0x7f"
+}
\ No newline at end of file
diff --git a/assets/lato-light.kttf-cached.png b/assets/lato-light.kttf-cached.png
new file mode 100644
index 0000000000000000000000000000000000000000..9cec7f72d026cb867b02cba99b0ffa9129c52187
Binary files /dev/null and b/assets/lato-light.kttf-cached.png differ
diff --git a/assets/lato-light.ttf b/assets/lato-light.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..dfa72ce808fbb783042da88ce1bae5d9adb54fb6
Binary files /dev/null and b/assets/lato-light.ttf differ
diff --git a/assets/roboto.kttf b/assets/roboto.kttf
new file mode 100644
index 0000000000000000000000000000000000000000..9c7d59c6ed7f1c15a4d89b38b98ae946f470213b
--- /dev/null
+++ b/assets/roboto.kttf
@@ -0,0 +1,5 @@
+{
+    "file": "roboto.ttf",
+    "char_range_start": "0x20",
+    "char_range_end": "0x7f"
+}
\ No newline at end of file
diff --git a/assets/roboto.kttf-cached.png b/assets/roboto.kttf-cached.png
new file mode 100644
index 0000000000000000000000000000000000000000..07d69a7ff42902c316d4d34295c32460a815e84e
Binary files /dev/null and b/assets/roboto.kttf-cached.png differ
diff --git a/assets/roboto.ttf b/assets/roboto.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..2b6392ffe8712b9c5450733320cd220d6c0f4bce
Binary files /dev/null and b/assets/roboto.ttf differ
diff --git a/book/src/chapter_6.md b/book/src/chapter_6.md
index 7581bf697f5edd856ac7505462b387f6e460cf04..446bad97fb064e784468f2cb9f249e832ebb8a6d 100644
--- a/book/src/chapter_6.md
+++ b/book/src/chapter_6.md
@@ -4,7 +4,18 @@ Kayak UI uses SDF(signed distance fields) for rendering fonts. More specifically
 - Fast rendering!
 - No need for a new asset for each font size. MSDF's can size to any font size!
 
-Font's are stored as an atlased image and a json file which tells Kayak about the font glyphs. Check out `roboto.kayak_font` and `roboto.png` in the `assets` folder.
+Fonts are stored in two different ways. First a font can be defined as a Kayak TTF(kttf) file. 
+These font files are relatively simple and simply link to a ttf font:
+```json
+{
+    "file": "roboto.ttf",
+    "char_range_start": "0x20",
+    "char_range_end": "0x7f"
+}
+``` 
+The char range is a defined as u32 char values. 0x20 through 0x7f represents most of the standard English language characters. Font's using this method are processed in native rust into MSDF's. The output is cached as the generation can take a while. 
+
+Fonts are also stored as an atlased image and a json file which tells Kayak about the font glyphs. These fonts are generated using `msdf-atlas-gen`. Check out `roboto.kayak_font` and `roboto.png` in the `assets` folder. The cached file name will be located next to the kttf file and have the file format of: `{font_name}.kttf-cached.png`.
 
 ## Generating new fonts.
 In order to create a new font you need to use the `msdf-atlas-gen` tool. This can be found at:
diff --git a/examples/clipping.rs b/examples/clipping.rs
index ee5a3032f5133edca1ad952381d1f22f4c866f88..322a0ed2c81f869f3a28dbd20973c23d17364c09 100644
--- a/examples/clipping.rs
+++ b/examples/clipping.rs
@@ -6,7 +6,7 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("lato-light.kayak_font"));
+    font_mapping.set_default(asset_server.load("lato-light.kttf"));
 
     let image = asset_server.load("panel.png");
 
diff --git a/examples/conditional_widget.rs b/examples/conditional_widget.rs
index b668f17ce4655b6706002df9a4dcf201fb0b84fb..6c652ac76a6dabbbd7a2c6c4f0de094dd7602de0 100644
--- a/examples/conditional_widget.rs
+++ b/examples/conditional_widget.rs
@@ -109,7 +109,7 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("lato-light.kayak_font"));
+    font_mapping.set_default(asset_server.load("lato-light.kttf"));
 
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
diff --git a/examples/main_menu.rs b/examples/main_menu.rs
index a1ec90cd1e51d239f9b45a14e9249b9db4031f24..467dc38ede2ef83a8c22635cd8627f436d084d9c 100644
--- a/examples/main_menu.rs
+++ b/examples/main_menu.rs
@@ -96,8 +96,9 @@ fn menu_button_render(
                         content: button_text,
                         size: 28.0,
                         user_styles: KStyle {
-                            top: Units::Stretch(1.0).into(),
-                            bottom: Units::Stretch(1.0).into(),
+                            // top: Units::Stretch(1.0).into(),
+                            top: Units::Pixels(-16.0).into(),
+                            height: Units::Pixels(40.0).into(),
                             ..Default::default()
                         },
                         ..Default::default()
@@ -120,7 +121,7 @@ fn startup(
     asset_server: Res<AssetServer>,
     mut preload_resource: ResMut<PreloadResource>,
 ) {
-    font_mapping.set_default(asset_server.load("lato-light.kayak_font"));
+    font_mapping.set_default(asset_server.load("lato-light.kttf"));
 
     let mut widget_context = KayakRootContext::new();
     widget_context.add_plugin(KayakWidgetsContextPlugin);
diff --git a/examples/simple_state.rs b/examples/simple_state.rs
index 0e2e8e2d0768e06dae6320fd45dca1b90bb6e389..0042d2684a05abd4977c1d00e391772e94a734cc 100644
--- a/examples/simple_state.rs
+++ b/examples/simple_state.rs
@@ -83,7 +83,7 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("lato-light.kayak_font"));
+    font_mapping.set_default(asset_server.load("lato-light.kttf"));
 
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
diff --git a/kayak_font/Cargo.toml b/kayak_font/Cargo.toml
index 1d24693eb10705332968dd199b9abcd55e0e61ff..f326aa45e4afb0c5a59d84f3c7f65fefaf7920d7 100644
--- a/kayak_font/Cargo.toml
+++ b/kayak_font/Cargo.toml
@@ -18,6 +18,11 @@ bevy_renderer = ["bevy"]
 anyhow = { version = "1.0" }
 nanoserde = "0.1.30"
 unicode-segmentation = "1.10.0"
+num = "0.4"
+num-derive = "0.3"
+num-traits = "0.2"
+ttf-parser = "0.17"
+image = "0.24"
 
 # Provides UAX #14 line break segmentation
 xi-unicode = "0.3"
diff --git a/kayak_font/assets/roboto.kttf b/kayak_font/assets/roboto.kttf
new file mode 100644
index 0000000000000000000000000000000000000000..9c7d59c6ed7f1c15a4d89b38b98ae946f470213b
--- /dev/null
+++ b/kayak_font/assets/roboto.kttf
@@ -0,0 +1,5 @@
+{
+    "file": "roboto.ttf",
+    "char_range_start": "0x20",
+    "char_range_end": "0x7f"
+}
\ No newline at end of file
diff --git a/kayak_font/assets/roboto.kttf-cached.png b/kayak_font/assets/roboto.kttf-cached.png
new file mode 100644
index 0000000000000000000000000000000000000000..07d69a7ff42902c316d4d34295c32460a815e84e
Binary files /dev/null and b/kayak_font/assets/roboto.kttf-cached.png differ
diff --git a/kayak_font/assets/roboto.ttf b/kayak_font/assets/roboto.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..2b6392ffe8712b9c5450733320cd220d6c0f4bce
Binary files /dev/null and b/kayak_font/assets/roboto.ttf differ
diff --git a/kayak_font/examples/bevy.rs b/kayak_font/examples/bevy.rs
index 5eb6a1a737feddb92d39cc455164e673ee81c1d3..ac6413002da01dd77fef9096436c433bd93b8026 100644
--- a/kayak_font/examples/bevy.rs
+++ b/kayak_font/examples/bevy.rs
@@ -18,7 +18,7 @@ const FONT_SIZE: f32 = 24.0;
 const INITIAL_SIZE: Vec2 = Vec2::from_array([400.0, 300.0]);
 const INITIAL_POS: Vec2 = Vec2::from_array([-200.0, 0.0]);
 const INSTRUCTIONS: &str =
-    "Press 'A' and 'D' to shrink and grow the text box.\nPress 'Space' to cycle text alignment.";
+    "Press 'A' and 'D' to shrink and grow the text box.\nPress \"Space\" to cycle text alignment.";
 
 #[derive(Component)]
 struct Instructions;
@@ -26,7 +26,8 @@ struct Instructions;
 fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
     commands.spawn(Camera2dBundle::default());
 
-    let font_handle: Handle<KayakFont> = asset_server.load("roboto.kayak_font");
+    let font_handle: Handle<KayakFont> = asset_server.load("roboto.kttf");
+    // let font_handle: Handle<KayakFont> = asset_server.load("roboto.kayak_font");
 
     commands
         .spawn(Text {
diff --git a/kayak_font/roboto.kttf-cached.png b/kayak_font/roboto.kttf-cached.png
new file mode 100644
index 0000000000000000000000000000000000000000..07d69a7ff42902c316d4d34295c32460a815e84e
Binary files /dev/null and b/kayak_font/roboto.kttf-cached.png differ
diff --git a/kayak_font/src/atlas.rs b/kayak_font/src/atlas.rs
index 917163e47fe064462e52fc21a91f125ca6d4b0ed..3f094cdc5f069c2d8cdabf0ace6024f17dd959b7 100644
--- a/kayak_font/src/atlas.rs
+++ b/kayak_font/src/atlas.rs
@@ -6,6 +6,12 @@ pub enum SDFType {
     Msdf,
 }
 
+impl Default for SDFType {
+    fn default() -> Self {
+        Self::Msdf
+    }
+}
+
 #[derive(DeJson, Debug, Copy, Clone, PartialEq, Eq)]
 pub enum Origin {
     #[nserde(rename = "bottom")]
@@ -18,7 +24,13 @@ pub enum Origin {
     Top,
 }
 
-#[derive(DeJson, Debug, Copy, Clone, PartialEq)]
+impl Default for Origin {
+    fn default() -> Self {
+        Self::Bottom
+    }
+}
+
+#[derive(DeJson, Default, Debug, Copy, Clone, PartialEq)]
 pub struct Atlas {
     #[nserde(rename = "type")]
     pub sdf_type: SDFType,
diff --git a/kayak_font/src/bevy/font_texture.rs b/kayak_font/src/bevy/font_texture.rs
index 26110bf83642556c3e0064a0e1210f014c9dc5fc..1b4b43a2f379355eff394aca8b1bd66367066c6c 100644
--- a/kayak_font/src/bevy/font_texture.rs
+++ b/kayak_font/src/bevy/font_texture.rs
@@ -22,7 +22,7 @@ pub fn init_font_texture(
     let not_processed_fonts = not_processed.drain(..).collect::<Vec<_>>();
     for font_handle in not_processed_fonts {
         if let Some(font) = fonts.get(&font_handle) {
-            if let Some(mut texture) = images.get_mut(&font.atlas_image) {
+            if let Some(mut texture) = images.get_mut(font.image.get()) {
                 texture.texture_descriptor.format = TextureFormat::Rgba8Unorm;
                 texture.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor {
                     label: Some("Present Sampler"),
diff --git a/kayak_font/src/bevy/loader.rs b/kayak_font/src/bevy/loader.rs
index 4bc0748f1fea1d51973d32c0c73efd4f103f5639..6e9c236e4ee50566280f52b5006ec1db3e3ec161 100644
--- a/kayak_font/src/bevy/loader.rs
+++ b/kayak_font/src/bevy/loader.rs
@@ -1,4 +1,4 @@
-use crate::{KayakFont, Sdf};
+use crate::{ImageType, KayakFont, Sdf};
 use bevy::asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset};
 
 #[derive(Default)]
@@ -16,7 +16,7 @@ impl AssetLoader for KayakFontLoader {
             let atlas_image_path = AssetPath::new(path, None);
             let font = KayakFont::new(
                 Sdf::from_bytes(bytes),
-                load_context.get_handle(atlas_image_path.clone()),
+                ImageType::Atlas(load_context.get_handle(atlas_image_path.clone())),
             );
 
             let asset = LoadedAsset::new(font).with_dependency(atlas_image_path);
diff --git a/kayak_font/src/bevy/mod.rs b/kayak_font/src/bevy/mod.rs
index 660e2edf0912d16e91d5a4e4ae6d2fdf3465b151..d2140100b608b146a22422944f864ef9ec263c01 100644
--- a/kayak_font/src/bevy/mod.rs
+++ b/kayak_font/src/bevy/mod.rs
@@ -24,6 +24,7 @@ mod plugin {
     impl Plugin for KayakFontPlugin {
         fn build(&self, app: &mut bevy::prelude::App) {
             app.add_asset::<KayakFont>()
+                .add_asset_loader(crate::ttf::loader::TTFLoader)
                 .add_asset_loader(KayakFontLoader)
                 .add_system(init_font_texture);
 
diff --git a/kayak_font/src/bevy/renderer/extract.rs b/kayak_font/src/bevy/renderer/extract.rs
index 94bc95cc0eaa91735abed21fb07532e1e9005669..d770ee67e59e65c1ce5d4b0ebc8766e4f50d6c2b 100644
--- a/kayak_font/src/bevy/renderer/extract.rs
+++ b/kayak_font/src/bevy/renderer/extract.rs
@@ -46,7 +46,7 @@ pub(crate) fn extract_fonts(
 
     for handle in changed_assets {
         let font_asset = font_assets.get(&handle).unwrap();
-        if let Some(image) = textures.get(&font_asset.atlas_image) {
+        if let Some(image) = textures.get(font_asset.image.get()) {
             if !image
                 .texture_descriptor
                 .usage
diff --git a/kayak_font/src/bevy/renderer/font_texture_cache.rs b/kayak_font/src/bevy/renderer/font_texture_cache.rs
index 5bd2981f22157999520cd5484bedfccff7741b0b..ec1baecfc02a7489c1f2b4f9e5e67b783ea1f21c 100644
--- a/kayak_font/src/bevy/renderer/font_texture_cache.rs
+++ b/kayak_font/src/bevy/renderer/font_texture_cache.rs
@@ -1,4 +1,4 @@
-use crate::{KayakFont, Sdf};
+use crate::{ImageType, KayakFont, Sdf};
 use bevy::{
     math::Vec2,
     prelude::{Handle, Res, Resource},
@@ -73,20 +73,34 @@ impl FontTextureCache {
         for kayak_font_handle in new_fonts {
             let mut was_processed = true;
             if let Some(font) = self.fonts.get(&kayak_font_handle) {
-                if let Some(atlas_texture) = render_images.get(&font.atlas_image) {
-                    Self::create_from_atlas(
-                        &mut self.images,
-                        &mut self.bind_groups,
-                        &font.sdf,
-                        kayak_font_handle.clone_weak(),
-                        device,
-                        queue,
-                        pipeline,
-                        atlas_texture,
-                        font.sdf.max_glyph_size().into(),
-                    );
+                if matches!(font.image, ImageType::Array(..)) {
+                    if let Some(array_texture) = render_images.get(font.image.get()) {
+                        Self::create_from_array(
+                            &mut self.bind_groups,
+                            kayak_font_handle.clone_weak(),
+                            device,
+                            pipeline,
+                            array_texture,
+                        );
+                    } else {
+                        was_processed = false;
+                    }
                 } else {
-                    was_processed = false;
+                    if let Some(atlas_texture) = render_images.get(font.image.get()) {
+                        Self::create_from_atlas(
+                            &mut self.images,
+                            &mut self.bind_groups,
+                            &font.sdf,
+                            kayak_font_handle.clone_weak(),
+                            device,
+                            queue,
+                            pipeline,
+                            atlas_texture,
+                            font.sdf.max_glyph_size().into(),
+                        );
+                    } else {
+                        was_processed = false;
+                    }
                 }
             }
             if !was_processed {
@@ -300,4 +314,30 @@ impl FontTextureCache {
         let command_buffer = command_encoder.finish();
         queue.submit(vec![command_buffer]);
     }
+
+    pub fn create_from_array<T: FontRenderingPipeline>(
+        bind_groups: &mut HashMap<Handle<KayakFont>, BindGroup>,
+        font_handle: Handle<KayakFont>,
+        device: &RenderDevice,
+        pipeline: &T,
+        array_texture: &GpuImage,
+    ) {
+        // create bind group
+        let binding = device.create_bind_group(&BindGroupDescriptor {
+            label: Some("text_image_bind_group"),
+            entries: &[
+                BindGroupEntry {
+                    binding: 0,
+                    resource: BindingResource::TextureView(&array_texture.texture_view),
+                },
+                BindGroupEntry {
+                    binding: 1,
+                    resource: BindingResource::Sampler(&array_texture.sampler),
+                },
+            ],
+            layout: pipeline.get_font_image_layout(),
+        });
+
+        bind_groups.insert(font_handle.clone_weak(), binding);
+    }
 }
diff --git a/kayak_font/src/font.rs b/kayak_font/src/font.rs
index 38d4c020972d8f46a4c8e1655b2e73105f43a92a..49b170fe97058c2c63df23f335bfd3abf2e18b85 100644
--- a/kayak_font/src/font.rs
+++ b/kayak_font/src/font.rs
@@ -14,12 +14,28 @@ use crate::{
 #[uuid = "4fe4732c-6731-49bb-bafc-4690d636b848"]
 pub struct KayakFont {
     pub sdf: Sdf,
-    pub atlas_image: Handle<Image>,
+    pub image: ImageType,
     pub missing_glyph: Option<char>,
     char_ids: HashMap<char, u32>,
     max_glyph_size: (f32, f32),
 }
 
+#[cfg(feature = "bevy_renderer")]
+#[derive(Debug, Clone, PartialEq)]
+pub enum ImageType {
+    Atlas(Handle<Image>),
+    Array(Handle<Image>),
+}
+
+impl ImageType {
+    pub fn get(&self) -> &Handle<Image> {
+        match self {
+            Self::Atlas(handle) => handle,
+            Self::Array(handle) => handle,
+        }
+    }
+}
+
 #[cfg(not(feature = "bevy_renderer"))]
 #[derive(Debug, Clone)]
 pub struct KayakFont {
@@ -30,7 +46,7 @@ pub struct KayakFont {
 }
 
 impl KayakFont {
-    pub fn new(sdf: Sdf, #[cfg(feature = "bevy_renderer")] atlas_image: Handle<Image>) -> Self {
+    pub fn new(sdf: Sdf, #[cfg(feature = "bevy_renderer")] image_type: ImageType) -> Self {
         let max_glyph_size = sdf.max_glyph_size();
         assert!(
             sdf.glyphs.len() < u32::MAX as usize,
@@ -55,7 +71,7 @@ impl KayakFont {
         Self {
             sdf,
             #[cfg(feature = "bevy_renderer")]
-            atlas_image,
+            image: image_type,
             missing_glyph,
             char_ids,
             max_glyph_size,
@@ -260,7 +276,6 @@ impl KayakFont {
                 rect.position.0 += shift_x;
             }
         }
-
         TextLayout::new(glyph_rects, lines, size, properties)
     }
 
diff --git a/kayak_font/src/lib.rs b/kayak_font/src/lib.rs
index 48e8cdddc414c84ebead34c3c3bac47959ceafd2..30e246008d37a7c67aee7fc29d05a37d0c6867ca 100644
--- a/kayak_font/src/lib.rs
+++ b/kayak_font/src/lib.rs
@@ -3,7 +3,9 @@ mod font;
 mod glyph;
 mod layout;
 mod metrics;
+mod msdf;
 mod sdf;
+mod ttf;
 mod utility;
 
 pub use atlas::*;
@@ -18,14 +20,17 @@ pub mod bevy;
 
 #[cfg(test)]
 mod tests {
-    use crate::{Alignment, KayakFont, Sdf, TextProperties};
+    use crate::{Alignment, ImageType, KayakFont, Sdf, TextProperties};
 
     fn make_font() -> KayakFont {
         let bytes = std::fs::read("assets/roboto.kayak_font")
             .expect("a `roboto.kayak_font` file in the `assets/` directory of this crate");
 
         #[cfg(feature = "bevy_renderer")]
-        return KayakFont::new(Sdf::from_bytes(&bytes), bevy::asset::Handle::default());
+        return KayakFont::new(
+            Sdf::from_bytes(&bytes),
+            ImageType::Atlas(bevy::asset::Handle::default()),
+        );
 
         #[cfg(not(feature = "bevy_renderer"))]
         return KayakFont::new(Sdf::from_bytes(&bytes));
diff --git a/kayak_font/src/metrics.rs b/kayak_font/src/metrics.rs
index 83d307f6206bb9231492057ea2d960fda28306b6..d610e29a49ca51f0194bad2e4e2e20dadb80e1c4 100644
--- a/kayak_font/src/metrics.rs
+++ b/kayak_font/src/metrics.rs
@@ -1,6 +1,6 @@
 use nanoserde::DeJson;
 
-#[derive(DeJson, Debug, Copy, Clone, PartialEq)]
+#[derive(DeJson, Default, Debug, Copy, Clone, PartialEq)]
 pub struct Metrics {
     #[nserde(rename = "emSize")]
     em_size: f32,
diff --git a/kayak_font/src/msdf/bitmap.rs b/kayak_font/src/msdf/bitmap.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4bbe4737d11265fcfc71bbe176c1d3c2524d2ddf
--- /dev/null
+++ b/kayak_font/src/msdf/bitmap.rs
@@ -0,0 +1,80 @@
+#![allow(dead_code)]
+
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
+pub struct FloatRGB {
+    pub r: f32,
+    pub g: f32,
+    pub b: f32,
+}
+
+impl FloatRGB {
+    pub fn new(r: f32, g: f32, b: f32) -> Self {
+        Self { r, g, b }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct FloatBmp {
+    buffer: Vec<f32>,
+    w: usize,
+    h: usize,
+}
+
+impl FloatBmp {
+    pub fn new(w: usize, h: usize) -> Self {
+        Self {
+            buffer: Vec::with_capacity(w * h),
+            w,
+            h,
+        }
+    }
+
+    pub fn width(&self) -> usize {
+        self.w
+    }
+
+    pub fn height(&self) -> usize {
+        self.h
+    }
+
+    pub fn set_pixel(&mut self, x: usize, y: usize, value: f32) {
+        self.buffer[x + (y * self.w)] = value;
+    }
+
+    pub fn get_pixel(&self, x: usize, y: usize) -> f32 {
+        self.buffer[x + (y * self.w)]
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct FloatRGBBmp {
+    pub buffer: Vec<FloatRGB>,
+    w: usize,
+    h: usize,
+}
+
+impl FloatRGBBmp {
+    pub fn new(w: usize, h: usize) -> Self {
+        Self {
+            buffer: vec![FloatRGB::new(0.0, 0.0, 0.0); w * h],
+            w,
+            h,
+        }
+    }
+
+    pub fn width(&self) -> usize {
+        self.w
+    }
+
+    pub fn height(&self) -> usize {
+        self.h
+    }
+
+    pub fn set_pixel(&mut self, x: usize, y: usize, value: FloatRGB) {
+        self.buffer[x + (y * self.w)] = value;
+    }
+
+    pub fn get_pixel(&self, x: usize, y: usize) -> FloatRGB {
+        self.buffer[x + (y * self.w)]
+    }
+}
diff --git a/kayak_font/src/msdf/contour.rs b/kayak_font/src/msdf/contour.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dc3f55adbf13e115851d8abbfa8609b29028db95
--- /dev/null
+++ b/kayak_font/src/msdf/contour.rs
@@ -0,0 +1,199 @@
+#![allow(dead_code)]
+
+use crate::msdf::{edge_segment::EdgeSegment, vector::Vector2, EdgeColor};
+
+#[derive(Debug, Default, Clone)]
+pub struct Contour {
+    pub edges: Vec<EdgeSegment>,
+    has_calculated_bounds: bool,
+    bounds_left: f64,
+    bounds_right: f64,
+    bounds_top: f64,
+    bounds_bottom: f64,
+}
+
+impl Contour {
+    pub fn new() -> Self {
+        Self {
+            edges: Vec::new(),
+            has_calculated_bounds: false,
+            bounds_left: 0.0,
+            bounds_right: 0.0,
+            bounds_top: 0.0,
+            bounds_bottom: 0.0,
+        }
+    }
+
+    pub fn add_edge(&mut self, edge: EdgeSegment) -> &EdgeSegment {
+        self.edges.push(edge);
+        self.edges.last().unwrap()
+    }
+
+    pub fn add_line(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) -> &EdgeSegment {
+        self.add_edge(EdgeSegment::new_linear(
+            Vector2::new(x0, y0),
+            Vector2::new(x1, y1),
+            EdgeColor::WHITE,
+        ))
+    }
+
+    pub fn add_quadratic_segment(
+        &mut self,
+        x0: f64,
+        y0: f64,
+        x1: f64,
+        y1: f64,
+        x2: f64,
+        y2: f64,
+    ) -> &EdgeSegment {
+        self.add_edge(EdgeSegment::new_quadratic(
+            Vector2::new(x0, y0),
+            Vector2::new(x1, y1),
+            Vector2::new(x2, y2),
+            EdgeColor::WHITE,
+        ))
+    }
+
+    pub fn add_cubic_segment(
+        &mut self,
+        x0: f64,
+        y0: f64,
+        x1: f64,
+        y1: f64,
+        x2: f64,
+        y2: f64,
+        x3: f64,
+        y3: f64,
+    ) -> &EdgeSegment {
+        self.add_edge(EdgeSegment::new_cubic(
+            Vector2::new(x0, y0),
+            Vector2::new(x1, y1),
+            Vector2::new(x2, y2),
+            Vector2::new(x3, y3),
+            EdgeColor::WHITE,
+        ))
+    }
+
+    pub fn find_bounds(
+        &mut self,
+        left: &mut f64,
+        bottom: &mut f64,
+        right: &mut f64,
+        top: &mut f64,
+    ) {
+        if !self.has_calculated_bounds {
+            self.bounds_left = std::f64::MAX;
+            self.bounds_right = std::f64::MIN;
+            self.bounds_top = std::f64::MIN;
+            self.bounds_bottom = std::f64::MAX;
+
+            for edge in self.edges.iter() {
+                edge.find_bounds(
+                    &mut self.bounds_left,
+                    &mut self.bounds_bottom,
+                    &mut self.bounds_right,
+                    &mut self.bounds_top,
+                );
+            }
+            self.has_calculated_bounds = true;
+        }
+        if self.bounds_left > *left {
+            *left = self.bounds_left;
+        }
+        if self.bounds_right < *right {
+            *right = self.bounds_right;
+        }
+        if self.bounds_bottom < *bottom {
+            *bottom = self.bounds_bottom;
+        }
+        if self.bounds_top > *top {
+            *top = self.bounds_top;
+        }
+    }
+
+    pub fn winding(&self) -> i32 {
+        let mut total: f64 = 0.0;
+        match self.edges.len() {
+            0 => {
+                return 0;
+            }
+            1 => {
+                let a = self.edges[0].point(0.0);
+                let b = self.edges[0].point(1.0 / 3.0);
+                let c = self.edges[0].point(2.0 / 3.0);
+                total += Vector2::shoelace(a, b);
+                total += Vector2::shoelace(b, c);
+                total += Vector2::shoelace(c, a);
+            }
+            2 => {
+                let a = self.edges[0].point(0.0);
+                let b = self.edges[0].point(0.5);
+                let c = self.edges[1].point(0.0);
+                let d = self.edges[1].point(0.5);
+
+                total += Vector2::shoelace(a, b);
+                total += Vector2::shoelace(b, c);
+                total += Vector2::shoelace(c, d);
+                total += Vector2::shoelace(d, a);
+            }
+            _ => {
+                let mut prev = self.edges.last().unwrap().point(0.0);
+                for edge in self.edges.iter() {
+                    let cur = edge.point(0.0);
+                    total += Vector2::shoelace(prev, cur);
+                    prev = cur;
+                }
+            }
+        }
+        return Vector2::sign(total) as i32;
+    }
+
+    pub fn bound_miters(
+        &self,
+        l: &mut f64,
+        b: &mut f64,
+        r: &mut f64,
+        t: &mut f64,
+        border: f64,
+        miter_limit: f64,
+        polarity: i32,
+    ) {
+        if self.edges.is_empty() {
+            return;
+        }
+
+        let mut prev_dir = self.edges.last().unwrap().direction(1.0).normalize(true);
+
+        for edge in self.edges.iter() {
+            let mut dir = edge.direction(0.0).normalize(true);
+            dir = Vector2::new(-dir.x, -dir.y);
+
+            if polarity as f64 * Vector2::cross_product(prev_dir, dir) >= 0.0 {
+                let miter_length;
+                let q = 0.5 * (1.0 - Vector2::dot_product(prev_dir, dir));
+                if q > 0.0 {
+                    miter_length = (1.0 / q.sqrt()).min(miter_limit);
+                    let miter =
+                        edge.point(0.0) + border * miter_length * (prev_dir + dir).normalize(true);
+                    bound_point(l, b, r, t, miter);
+                }
+            }
+            prev_dir = edge.direction(1.0).normalize(true);
+        }
+    }
+}
+
+fn bound_point(l: &mut f64, b: &mut f64, r: &mut f64, t: &mut f64, p: Vector2) {
+    if p.x < *l {
+        *l = p.x;
+    }
+    if p.y < *b {
+        *b = p.y;
+    }
+    if p.x > *r {
+        *r = p.x;
+    }
+    if p.y > *t {
+        *t = p.y;
+    }
+}
diff --git a/kayak_font/src/msdf/edge_coloring.rs b/kayak_font/src/msdf/edge_coloring.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b74f326fc602802d9baa702e7965034010fdf7c4
--- /dev/null
+++ b/kayak_font/src/msdf/edge_coloring.rs
@@ -0,0 +1,307 @@
+#![allow(dead_code)]
+
+use crate::msdf::{edge_segment::EdgeSegment, shape::Shape, vector::Vector2, EdgeColor};
+
+fn is_corner(a_dir: Vector2, b_dir: Vector2, cross_threshold: f64) -> bool {
+    Vector2::dot_product(a_dir, b_dir) <= 0.0
+        || Vector2::cross_product(a_dir, b_dir).abs() > cross_threshold
+}
+
+const MSDFGEN_EDGE_LENGTH_PRECISION: usize = 4;
+
+fn estimate_edge_length(edge: &EdgeSegment) -> f64 {
+    let mut len = 0.0;
+    let mut prev = edge.point(0.0);
+    for i in 1..MSDFGEN_EDGE_LENGTH_PRECISION {
+        let cur = edge.point(1.0 / MSDFGEN_EDGE_LENGTH_PRECISION as f64 * i as f64);
+        len += (cur - prev).length();
+        prev = cur;
+    }
+    return len;
+}
+
+fn switch_color(color: &mut EdgeColor, seed: &mut usize, banned: EdgeColor) {
+    let combined: EdgeColor =
+        num::cast::FromPrimitive::from_usize(*color as usize & banned as usize).unwrap();
+
+    if combined == EdgeColor::RED || combined == EdgeColor::GREEN || combined == EdgeColor::BLUE {
+        *color =
+            num::cast::FromPrimitive::from_usize(combined as usize ^ EdgeColor::WHITE as usize)
+                .unwrap();
+        return;
+    }
+    if *color == EdgeColor::BLACK || *color == EdgeColor::WHITE {
+        match *seed % 3 {
+            0 => {
+                *color = EdgeColor::CYAN;
+            }
+            1 => {
+                *color = EdgeColor::MAGENTA;
+            }
+            2 => {
+                *color = EdgeColor::YELLOW;
+            }
+            _ => panic!("Not supported!"),
+        }
+
+        *seed /= 3;
+        return;
+    }
+
+    let shifted = (*color as usize) << (1 + (*seed & 1));
+    *color = num::cast::FromPrimitive::from_usize(
+        (shifted | shifted >> 3) & (EdgeColor::WHITE as usize),
+    )
+    .unwrap();
+    *seed >>= 1;
+}
+
+pub fn simple(shape: &mut Shape, angle_threshold: f64, mut seed: usize) {
+    let cross_threshold = angle_threshold.sin();
+    let mut corners = Vec::new();
+
+    for contour in shape.contours.iter_mut() {
+        corners.clear();
+
+        let edges = &mut contour.edges;
+
+        let edge_count = edges.len();
+        if edge_count != 0 {
+            let mut prev_dir = edges.last().unwrap().direction(1.0);
+            for i in 0..edge_count {
+                let edge = &edges[i];
+                if is_corner(
+                    prev_dir.normalize(false),
+                    edge.direction(0.0).normalize(false),
+                    cross_threshold,
+                ) {
+                    corners.push(i);
+                }
+                prev_dir = edge.direction(1.0);
+            }
+        }
+
+        if corners.len() == 0 {
+            for i in 0..edge_count {
+                edges[i].set_color(EdgeColor::WHITE);
+            }
+        } else if corners.len() == 1 {
+            let mut colors = vec![EdgeColor::WHITE, EdgeColor::WHITE, EdgeColor::BLACK];
+            switch_color(&mut colors[0], &mut seed, EdgeColor::BLACK);
+            colors[2] = colors[0];
+            switch_color(&mut colors[2], &mut seed, EdgeColor::BLACK);
+
+            let corner = corners[0];
+            if edge_count >= 3 {
+                let m = edge_count;
+                for i in 0..m {
+                    let lookup =
+                        ((3.0 + 2.875 * i as f64 / (m as f64 - 1.0) - 1.4375 + 0.5) as i32 - 3) + 1;
+                    contour.edges[(corner + i) % m].set_color(colors[lookup as usize]);
+                }
+            } else if edge_count >= 1 {
+                let mut parts = [EdgeSegment::default(); 7];
+
+                let (o1, o2, o3) = edges[0].split_in_thirds();
+                parts[0 + 3 * corner] = o1;
+                parts[1 + 3 * corner] = o2;
+                parts[2 + 3 * corner] = o3;
+
+                if edge_count >= 2 {
+                    let (o1, o2, o3) = edges[1].split_in_thirds();
+                    parts[3 - 3 * corner] = o1;
+                    parts[4 - 3 * corner] = o2;
+                    parts[5 - 3 * corner] = o3;
+                    parts[1].set_color(colors[0]);
+                    parts[0].set_color(parts[1].get_color());
+                    parts[3].set_color(colors[1]);
+                    parts[2].set_color(parts[3].get_color());
+                    parts[5].set_color(colors[2]);
+                    parts[4].set_color(parts[5].get_color());
+                } else {
+                    parts[0].set_color(colors[0]);
+                    parts[1].set_color(colors[1]);
+                    parts[2].set_color(colors[2]);
+                }
+                edges.clear();
+                for i in 0..7 {
+                    edges.push(parts[i]);
+                }
+            }
+        } else {
+            let corner_count = corners.len();
+            let mut spline = 0;
+            let start = corners[0];
+
+            let mut color = EdgeColor::WHITE;
+            switch_color(&mut color, &mut seed, EdgeColor::BLACK);
+            let initial_color = color;
+            for i in 0..edge_count {
+                let index = (start + i) % edge_count;
+                if spline + 1 < corner_count && corners[spline + 1] == index {
+                    spline += 1;
+                    let banned_color =
+                        (if spline == corner_count - 1 { 1 } else { 0 }) * initial_color as usize;
+                    switch_color(
+                        &mut color,
+                        &mut seed,
+                        num::cast::FromPrimitive::from_usize(banned_color).unwrap(),
+                    );
+                }
+                edges[index].set_color(color);
+            }
+        }
+    }
+}
+
+struct EdgeColoringInkTrapCorner {
+    pub index: i32,
+    pub prev_edge_length_estimate: f64,
+    pub minor: bool,
+    pub color: EdgeColor,
+    pub spline_length: f64,
+}
+
+pub fn ink_trap(shape: &mut Shape, angle_threshold: f64, mut seed: usize) {
+    let cross_threshold = angle_threshold.sin();
+    let mut corners = Vec::new();
+    for contour in shape.contours.iter_mut() {
+        let mut spline_length = 0.0;
+        corners.clear();
+        if !contour.edges.is_empty() {
+            let mut prev_direction = contour.edges.last().unwrap().direction(1.0);
+            let mut index = 0;
+            for edge in contour.edges.iter() {
+                if is_corner(
+                    prev_direction.normalize(false),
+                    edge.direction(0.0).normalize(false),
+                    cross_threshold,
+                ) {
+                    let corner = EdgeColoringInkTrapCorner {
+                        index,
+                        spline_length,
+                        color: EdgeColor::BLACK,
+                        prev_edge_length_estimate: 0.0,
+                        minor: false,
+                    };
+                    corners.push(corner);
+                    spline_length = 0.0;
+                }
+                spline_length += estimate_edge_length(edge);
+                prev_direction = edge.direction(1.0);
+                index += 1;
+            }
+        }
+
+        if corners.is_empty() {
+            for edge in contour.edges.iter_mut() {
+                edge.set_color(EdgeColor::WHITE);
+            }
+        } else if corners.len() == 1 {
+            let mut colors = vec![EdgeColor::WHITE, EdgeColor::WHITE, EdgeColor::BLACK];
+            switch_color(&mut colors[0], &mut seed, EdgeColor::BLACK);
+            colors[2] = colors[0];
+            switch_color(&mut colors[2], &mut seed, EdgeColor::BLACK);
+            let corner = corners[0].index as usize;
+            if contour.edges.len() >= 3 {
+                let m = contour.edges.len();
+                for i in 0..m {
+                    let lookup =
+                        ((3.0 + 2.875 * i as f64 / (m as f64 - 1.0) - 1.4375 + 0.5) as i32 - 3) + 1;
+                    contour.edges[(corner + i) % m].set_color(colors[lookup as usize]);
+                }
+            } else if contour.edges.len() >= 1 {
+                let mut parts = vec![EdgeSegment::default(); 7];
+                let (o1, o2, o3) = contour.edges[0].split_in_thirds();
+                parts[0 + 3 * corner] = o1;
+                parts[1 + 3 * corner] = o2;
+                parts[2 + 3 * corner] = o3;
+                if contour.edges.len() >= 2 {
+                    let (o1, o2, o3) = contour.edges[1].split_in_thirds();
+                    parts[3 - 3 * corner] = o1;
+                    parts[4 - 3 * corner] = o2;
+                    parts[5 - 3 * corner] = o3;
+                    parts[1].set_color(colors[0]);
+                    let part1_color = parts[1].get_color();
+                    parts[0].set_color(part1_color);
+                    parts[3].set_color(colors[1]);
+                    let part3_color = parts[3].get_color();
+                    parts[2].set_color(part3_color);
+                    parts[5].set_color(colors[2]);
+                    let part5_color = parts[5].get_color();
+                    parts[4].set_color(part5_color);
+                } else {
+                    parts[0].set_color(colors[0]);
+                    parts[1].set_color(colors[1]);
+                    parts[2].set_color(colors[2]);
+                }
+                contour.edges.clear();
+                for part in parts.into_iter() {
+                    contour.edges.push(part);
+                }
+            } else {
+                let corner_count = corners.len();
+                let mut major_corner_count = corner_count;
+
+                if corner_count > 3 {
+                    corners.first_mut().unwrap().prev_edge_length_estimate += spline_length;
+                    for i in 0..corner_count {
+                        if corners[i].prev_edge_length_estimate
+                            > corners[(i + 1) % corner_count].prev_edge_length_estimate
+                            && corners[(i + 1) % corner_count].prev_edge_length_estimate
+                                < corners[(i + 2) % corner_count].prev_edge_length_estimate
+                        {
+                            corners[i].minor = true;
+                            major_corner_count -= 1;
+                        }
+                    }
+
+                    let mut color = EdgeColor::WHITE;
+                    let mut initial_color = EdgeColor::BLACK;
+                    for i in 0..corner_count {
+                        if !corners[i].minor {
+                            major_corner_count -= 1;
+                            switch_color(
+                                &mut color,
+                                &mut seed,
+                                num::cast::FromPrimitive::from_usize(
+                                    !major_corner_count * initial_color as usize,
+                                )
+                                .unwrap(),
+                            );
+                            corners[i].color = color;
+                            if initial_color != EdgeColor::BLACK {
+                                initial_color = color;
+                            }
+                        }
+                    }
+                    for i in 0..corner_count {
+                        if corners[i].minor {
+                            let next_color = corners[(i + 1) % corner_count].color;
+                            corners[i].color = num::cast::FromPrimitive::from_usize(
+                                (color as usize & next_color as usize) ^ EdgeColor::WHITE as usize,
+                            )
+                            .unwrap();
+                        } else {
+                            color = corners[i].color;
+                        }
+                    }
+
+                    let mut spline = 0;
+                    let start = corners[0].index as usize;
+                    let mut color = corners[0].color;
+                    let m = contour.edges.len();
+                    for i in 0..m {
+                        let index = (start + i) % m;
+                        if spline + 1 < corner_count && corners[spline + 1].index as usize == index
+                        {
+                            spline += 1;
+                            color = corners[spline].color;
+                        }
+                        contour.edges[index].set_color(color);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/kayak_font/src/msdf/edge_point.rs b/kayak_font/src/msdf/edge_point.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0fe5d674d5a6a2cc170d180a45816e38d6c4792d
--- /dev/null
+++ b/kayak_font/src/msdf/edge_point.rs
@@ -0,0 +1,17 @@
+use crate::msdf::{edge_segment::EdgeSegment, signed_distance::SignedDistance};
+
+#[derive(Debug, Clone, Copy)]
+pub struct EdgePoint {
+    pub min_distance: SignedDistance,
+    pub near_edge: Option<EdgeSegment>,
+    pub near_param: f64,
+}
+
+impl EdgePoint {
+    // pub fn calculate_contour_color(&mut self, p: Vector2) -> f64 {
+    //     if let Some(near_edge) = self.near_edge {
+    //         near_edge.distance_to_pseudo_distance(&mut self.min_distance, p, self.near_param);
+    //     }
+    //     return self.min_distance.distance;
+    // }
+}
diff --git a/kayak_font/src/msdf/edge_segment/cubic.rs b/kayak_font/src/msdf/edge_segment/cubic.rs
new file mode 100644
index 0000000000000000000000000000000000000000..21444bfa057dc957a9b6cd2680826390bb9dd1d2
--- /dev/null
+++ b/kayak_font/src/msdf/edge_segment/cubic.rs
@@ -0,0 +1,205 @@
+use crate::msdf::{signed_distance::SignedDistance, vector::Vector2, EdgeColor};
+
+use super::{
+    equation_solver::{self, fabs},
+    mix, non_zero_sign, EdgeSegment,
+};
+
+pub const MSDFGEN_CUBIC_SEARCH_STARTS: usize = 4;
+pub const MSDFGEN_CUBIC_SEARCH_STEPS: usize = 4;
+
+pub fn direction(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, param: f64) -> Vector2 {
+    let tangent = mix(
+        mix(p1 - p0, p2 - p1, param),
+        mix(p2 - p1, p3 - p2, param),
+        param,
+    );
+    if !tangent.is_zero() {
+        if param == 0.0 {
+            return p2 - p0;
+        }
+        if param == 1.0 {
+            return p3 - p1;
+        }
+    }
+    tangent
+}
+
+pub fn point(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, param: f64) -> Vector2 {
+    let p12 = mix(p1, p2, param);
+    mix(
+        mix(mix(p0, p1, param), p12, param),
+        mix(p12, mix(p2, p3, param), param),
+        param,
+    )
+}
+
+pub fn find_bounds(
+    p0: Vector2,
+    p1: Vector2,
+    p2: Vector2,
+    p3: Vector2,
+    l: &mut f64,
+    b: &mut f64,
+    r: &mut f64,
+    t: &mut f64,
+) {
+    Vector2::point_bounds(p0, l, b, r, t);
+    Vector2::point_bounds(p3, l, b, r, t);
+
+    let a0 = p1 - p0;
+    let a1 = 2.0 * (p2 - p1 - a0);
+    let a2 = p3 - 3.0 * p2 + 3.0 * p1 - p0;
+
+    let (solutions, result) = equation_solver::solve_quadratic(a2.x, a1.x, a0.x);
+    for i in 0..solutions {
+        let par = result[i as usize];
+        if par > 0.0 && par < 1.0 {
+            Vector2::point_bounds(point(p0, p1, p2, p3, par), l, b, r, t);
+        }
+    }
+
+    let (solutions, result) = equation_solver::solve_quadratic(a2.y, a1.y, a0.y);
+
+    for i in 0..solutions {
+        let par = result[i as usize];
+        if par > 0.0 && par < 1.0 {
+            Vector2::point_bounds(point(p0, p1, p2, p3, par), l, b, r, t);
+        }
+    }
+}
+
+pub fn split_in_thirds(
+    p0: Vector2,
+    p1: Vector2,
+    p2: Vector2,
+    p3: Vector2,
+    color: EdgeColor,
+) -> (EdgeSegment, EdgeSegment, EdgeSegment) {
+    (
+        EdgeSegment::new_cubic(
+            p0,
+            if p0 == p1 { p0 } else { mix(p0, p1, 1.0 / 3.0) },
+            mix(mix(p0, p1, 1.0 / 3.0), mix(p1, p2, 1.0 / 3.0), 1.0 / 3.0),
+            point(p0, p1, p2, p3, 1.0 / 3.0),
+            color,
+        ),
+        EdgeSegment::new_cubic(
+            point(p0, p1, p2, p3, 1.0 / 3.0),
+            mix(
+                mix(mix(p0, p1, 1.0 / 3.0), mix(p1, p2, 1.0 / 3.0), 1.0 / 3.0),
+                mix(mix(p1, p2, 1.0 / 3.0), mix(p2, p3, 1.0 / 3.0), 1.0 / 3.0),
+                2.0 / 3.0,
+            ),
+            mix(
+                mix(mix(p0, p1, 2.0 / 3.0), mix(p1, p2, 2.0 / 3.0), 2.0 / 3.0),
+                mix(mix(p1, p2, 2.0 / 3.0), mix(p2, p3, 2.0 / 3.0), 2.0 / 3.0),
+                1.0 / 3.0,
+            ),
+            point(p0, p1, p2, p3, 2.0 / 3.0),
+            color,
+        ),
+        EdgeSegment::new_cubic(
+            point(p0, p1, p2, p3, 2.0 / 3.0),
+            mix(mix(p1, p2, 2.0 / 3.0), mix(p2, p3, 2.0 / 3.0), 2.0 / 3.0),
+            if p2 == p3 { p3 } else { mix(p2, p3, 2.0 / 3.0) },
+            p3,
+            color,
+        ),
+    )
+}
+
+pub fn signed_distance(
+    p0: Vector2,
+    p1: Vector2,
+    p2: Vector2,
+    p3: Vector2,
+    origin: Vector2,
+) -> (SignedDistance, f64) {
+    let qa = p0 - origin;
+    let ab = p1 - p0;
+    let br = p2 - p1 - ab;
+    let as_ = (p3 - p2) - (p2 - p1) - br;
+    let mut ep_dir = direction(p0, p1, p2, p3, 0.0);
+
+    let mut min_distance = non_zero_sign(Vector2::cross_product(ep_dir, qa)) as f64 * qa.length();
+    let mut param = -Vector2::dot_product(qa, ep_dir) / Vector2::dot_product(ep_dir, ep_dir);
+    {
+        ep_dir = direction(p0, p1, p2, p3, 1.0);
+        let distance = (p3 - origin).length();
+        if distance.abs() < min_distance.abs() {
+            min_distance =
+                non_zero_sign(Vector2::cross_product(ep_dir, p3 - origin)) as f64 * distance;
+            param = Vector2::dot_product(ep_dir - (p3 - origin), ep_dir)
+                / Vector2::dot_product(ep_dir, ep_dir);
+        }
+    }
+
+    for i in 0..MSDFGEN_CUBIC_SEARCH_STARTS {
+        let mut t = (i / MSDFGEN_CUBIC_SEARCH_STARTS) as f64;
+        let mut qe = qa + 3.0 * t * ab + 3.0 * t * t * br + t * t * t * as_;
+        for _ in 0..MSDFGEN_CUBIC_SEARCH_STEPS {
+            let d1 = 3.0 * ab + 6.0 * t * br + 3.0 * t * t * as_;
+            let d2 = 6.0 * br + 6.0 * t * as_;
+            t -= Vector2::dot_product(qe, d1)
+                / (Vector2::dot_product(d1, d1) + Vector2::dot_product(qe, d2));
+
+            if t < 0.0 || t > 1.0 {
+                break;
+            }
+
+            qe = qa + 3.0 * t * ab + 3.0 * t * t * br + t * t * t * as_;
+            let distance = qe.length();
+            if distance < min_distance.abs() {
+                min_distance = non_zero_sign(Vector2::cross_product(d1, qe)) as f64 * distance;
+                param = t;
+            }
+
+            // let qpt = point(p0, p1, p2, p3, t) - origin;
+            // let distance = non_zero_sign(Vector2::cross_product(direction(p0, p1, p2, p3, t), qpt))
+            //     as f64
+            //     * qpt.length();
+
+            // if fabs(distance) < fabs(min_distance) {
+            //     min_distance = distance;
+            //     param = t;
+            // }
+            // if step == MSDFGEN_CUBIC_SEARCH_STEPS {
+            //     break;
+            // }
+            // let d1 = 3.0 * as_ * t * t + 6.0 * br * t + 3.0 * ab;
+            // let d2 = 6.0 * as_ * t + 6.0 * br;
+            // t -= Vector2::dot_product(qpt, d1)
+            //     / (Vector2::dot_product(d1, d1) + Vector2::dot_product(qpt, d2));
+            // if t < 0.0 || t > 1.0 {
+            //     break;
+            // }
+        }
+    }
+
+    if param >= 0.0 && param <= 1.0 {
+        return (SignedDistance::new(min_distance, 0.0), param);
+    } else if param < 0.5 {
+        return (
+            SignedDistance::new(
+                min_distance,
+                fabs(Vector2::dot_product(
+                    direction(p0, p1, p2, p3, 0.0),
+                    qa.normalize(false),
+                )),
+            ),
+            param,
+        );
+    } else {
+        return (
+            SignedDistance::new(
+                min_distance,
+                fabs(Vector2::dot_product(
+                    direction(p0, p1, p2, p3, 1.0).normalize(false),
+                    (p3 - origin).normalize(false),
+                )),
+            ),
+            param,
+        );
+    }
+}
diff --git a/kayak_font/src/msdf/edge_segment/equation_solver.rs b/kayak_font/src/msdf/edge_segment/equation_solver.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6e71ee2bfe7a3b19a307819d59e9bc647fd85c09
--- /dev/null
+++ b/kayak_font/src/msdf/edge_segment/equation_solver.rs
@@ -0,0 +1,81 @@
+const EPSILON: f64 = 1.0e-14;
+
+pub fn fabs(v: f64) -> f64 {
+    v.abs()
+}
+
+pub fn solve_quadratic(a: f64, b: f64, c: f64) -> (i32, [f64; 3]) {
+    let mut result = [0.0; 3];
+
+    if fabs(a) < EPSILON {
+        if fabs(b) < EPSILON {
+            if c == 0.0 {
+                return (-1, result);
+            }
+            return (0, result);
+        }
+        result[0] = -c / b;
+        return (1, result);
+    }
+    let mut dscr = b * b - 4.0 * a * c;
+    if dscr > 0.0 {
+        dscr = dscr.sqrt();
+        result[0] = (-b + dscr) / (2.0 * a);
+        result[1] = (-b - dscr) / (2.0 * a);
+        return (2, result);
+    } else if dscr == 0.0 {
+        result[0] = -b / (2.0 * a);
+        return (1, result);
+    } else {
+        return (0, result);
+    }
+}
+
+pub fn solve_cubic_norm(mut a: f64, b: f64, c: f64) -> (i32, [f64; 3]) {
+    let mut result = [0.0; 3];
+    let a2 = a * a;
+    let mut q = (a2 - 3.0 * b) / 9.0;
+    let r = (a * (2.0 * a2 - 9.0 * b) + 27.0 * c) / 54.0;
+    let r2 = r * r;
+    let q3 = q * q * q;
+    let mut result_a;
+    let result_b;
+    if r2 < q3 {
+        let mut t = r / q3.sqrt();
+        if t < -1.0 {
+            t = -1.0;
+        }
+        if t > 1.0 {
+            t = 1.0;
+        }
+        t = t.acos();
+        a /= 3.0;
+        q = -2.0 * q.sqrt();
+        result[0] = q * (t / 3.0).cos() - a;
+        result[1] = q * ((t + 2.0 * std::f64::consts::PI) / 3.0).cos() - a;
+        result[2] = q * ((t - 2.0 * std::f64::consts::PI) / 3.0).cos() - a;
+        return (3, result);
+    } else {
+        result_a = -(fabs(r) + (r2 - q3).sqrt()).powf(1.0 / 3.0);
+        if r < 0.0 {
+            result_a = -result_a
+        };
+        result_b = if result_a == 0.0 { 0.0 } else { q / result_a };
+        a /= 3.0;
+        result[0] = (result_a + result_b) - a;
+        result[1] = -0.5 * (result_a + result_b) - a;
+        result[2] = 0.5 * 3.0f64.sqrt() * (result_a - result_b);
+        if fabs(result[2]) < EPSILON {
+            return (2, result);
+        }
+        return (1, result);
+    }
+}
+
+pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> (i32, [f64; 3]) {
+    if fabs(a) < EPSILON {
+        solve_quadratic(b, c, d)
+    } else {
+        solve_cubic_norm(b / a, c / a, d / a)
+    }
+}
diff --git a/kayak_font/src/msdf/edge_segment/line.rs b/kayak_font/src/msdf/edge_segment/line.rs
new file mode 100644
index 0000000000000000000000000000000000000000..639b262c8f5aa93934e331476ac0bc83dd896c03
--- /dev/null
+++ b/kayak_font/src/msdf/edge_segment/line.rs
@@ -0,0 +1,50 @@
+use crate::msdf::{signed_distance::SignedDistance, vector::Vector2, EdgeColor};
+
+use super::{mix, non_zero_sign, EdgeSegment};
+
+pub fn direction(p0: Vector2, p1: Vector2, _param: f64) -> Vector2 {
+    p1 - p0
+}
+
+pub fn point(p0: Vector2, p1: Vector2, param: f64) -> Vector2 {
+    mix(p0, p1, param)
+}
+
+pub fn find_bounds(p0: Vector2, p1: Vector2, l: &mut f64, b: &mut f64, r: &mut f64, t: &mut f64) {
+    Vector2::point_bounds(p0, l, b, r, t);
+    Vector2::point_bounds(p1, l, b, r, t);
+}
+
+pub fn split_in_thirds(
+    p0: Vector2,
+    p1: Vector2,
+    color: EdgeColor,
+) -> (EdgeSegment, EdgeSegment, EdgeSegment) {
+    (
+        EdgeSegment::new_linear(p0, point(p0, p1, 1.0 / 3.0), color),
+        EdgeSegment::new_linear(point(p0, p1, 1.0 / 3.0), point(p0, p1, 2.0 / 3.0), color),
+        EdgeSegment::new_linear(point(p0, p1, 2.0 / 3.0), p1, color),
+    )
+}
+
+pub fn signed_distance(p0: Vector2, p1: Vector2, origin: Vector2) -> (SignedDistance, f64) {
+    let aq = origin - p0;
+    let ab = p1 - p0;
+    let param = Vector2::dot_product(aq, ab) / Vector2::dot_product(ab, ab);
+
+    let eq = (if param > 0.5 { p1 } else { p0 }) - origin;
+    let endpoint_distance = eq.length();
+    if param > 0.0 && param < 1.0 {
+        let ortho_distance = Vector2::dot_product(ab.get_ortho_normal(false, false), aq);
+        if ortho_distance.abs() < endpoint_distance {
+            return (SignedDistance::new(ortho_distance, 0.0), param);
+        }
+    }
+    return (
+        SignedDistance::new(
+            non_zero_sign(Vector2::cross_product(aq, ab)) as f64 * endpoint_distance,
+            Vector2::dot_product(ab.normalize(false), eq.normalize(false)).abs(),
+        ),
+        param,
+    );
+}
diff --git a/kayak_font/src/msdf/edge_segment/mod.rs b/kayak_font/src/msdf/edge_segment/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..41411b487fe3213046cb41ee2268910f6117192f
--- /dev/null
+++ b/kayak_font/src/msdf/edge_segment/mod.rs
@@ -0,0 +1,190 @@
+use crate::msdf::{signed_distance::SignedDistance, vector::Vector2, EdgeColor};
+
+mod cubic;
+mod equation_solver;
+mod line;
+mod quadratic;
+
+pub fn non_zero_sign(n: f64) -> i32 {
+    return 2 * (if n > 0.0 { 1 } else { 0 }) - 1;
+}
+pub fn mix(a: Vector2, b: Vector2, weight: f64) -> Vector2 {
+    Vector2::new(
+        (1.0 - weight) * a.x + (weight * b.x),
+        (1.0 - weight) * a.y + (weight * b.y),
+    )
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum EdgeSegment {
+    LineSegment {
+        color: EdgeColor,
+        p0: Vector2,
+        p1: Vector2,
+    },
+    QuadraticSegment {
+        color: EdgeColor,
+        p0: Vector2,
+        p1: Vector2,
+        p2: Vector2,
+    },
+    CubicSegment {
+        color: EdgeColor,
+        p0: Vector2,
+        p1: Vector2,
+        p2: Vector2,
+        p3: Vector2,
+    },
+}
+
+impl Default for EdgeSegment {
+    fn default() -> Self {
+        EdgeSegment::LineSegment {
+            color: EdgeColor::WHITE,
+            p0: Vector2::default(),
+            p1: Vector2::default(),
+        }
+    }
+}
+
+impl EdgeSegment {
+    pub fn new_linear(p0: Vector2, p1: Vector2, color: EdgeColor) -> Self {
+        Self::LineSegment { p0, p1, color }
+    }
+
+    pub fn new_quadratic(p0: Vector2, mut p1: Vector2, p2: Vector2, color: EdgeColor) -> Self {
+        if p1 == p0 || p1 == p2 {
+            p1 = 0.5 * (p0 + p2);
+        }
+        Self::QuadraticSegment { p0, p1, p2, color }
+    }
+
+    pub fn new_cubic(
+        p0: Vector2,
+        mut p1: Vector2,
+        mut p2: Vector2,
+        p3: Vector2,
+        color: EdgeColor,
+    ) -> Self {
+        if (p1 == p0 || p1 == p3) && (p2 == p0 || p2 == p3) {
+            p1 = mix(p0, p3, 1.0 / 3.0);
+            p2 = mix(p0, p3, 2.0 / 3.0);
+        }
+        Self::CubicSegment {
+            p0,
+            p1,
+            p2,
+            p3,
+            color,
+        }
+    }
+
+    pub fn distance_to_pseudo_distance(
+        &self,
+        distance: &mut SignedDistance,
+        origin: Vector2,
+        param: f64,
+    ) {
+        if param < 0.0 {
+            let dir = self.direction(0.0).normalize(false);
+            let aq = origin - self.point(0.0);
+            let ts = Vector2::dot_product(aq, dir);
+            if ts < 0.0 {
+                let pseudo_distance = Vector2::cross_product(aq, dir);
+                if pseudo_distance.abs() <= distance.distance.abs() {
+                    *distance = SignedDistance::new(pseudo_distance, 0.0);
+                }
+            }
+        } else if param > 1.0 {
+            let dir = self.direction(1.0).normalize(false);
+            let bq = origin - self.point(1.0);
+            let ts = Vector2::dot_product(bq, dir);
+            if ts > 0.0 {
+                let pseudo_distance = Vector2::cross_product(bq, dir);
+                if pseudo_distance.abs() <= distance.distance.abs() {
+                    *distance = SignedDistance::new(pseudo_distance, 0.0);
+                }
+            }
+        }
+    }
+
+    pub fn direction(&self, param: f64) -> Vector2 {
+        match *self {
+            Self::LineSegment { p0, p1, .. } => line::direction(p0, p1, param),
+            Self::QuadraticSegment { p0, p1, p2, .. } => quadratic::direction(p0, p1, p2, param),
+            Self::CubicSegment { p0, p1, p2, p3, .. } => cubic::direction(p0, p1, p2, p3, param),
+        }
+    }
+
+    pub fn point(&self, param: f64) -> Vector2 {
+        match *self {
+            Self::LineSegment { p0, p1, .. } => line::point(p0, p1, param),
+            Self::QuadraticSegment { p0, p1, p2, .. } => quadratic::point(p0, p1, p2, param),
+            Self::CubicSegment { p0, p1, p2, p3, .. } => cubic::point(p0, p1, p2, p3, param),
+        }
+    }
+
+    pub fn find_bounds(&self, l: &mut f64, b: &mut f64, r: &mut f64, t: &mut f64) {
+        match *self {
+            Self::LineSegment { p0, p1, .. } => line::find_bounds(p0, p1, l, b, r, t),
+            Self::QuadraticSegment { p0, p1, p2, .. } => {
+                quadratic::find_bounds(p0, p1, p2, l, b, r, t)
+            }
+            Self::CubicSegment { p0, p1, p2, p3, .. } => {
+                cubic::find_bounds(p0, p1, p2, p3, l, b, r, t)
+            }
+        }
+    }
+
+    pub fn split_in_thirds(&self) -> (EdgeSegment, EdgeSegment, EdgeSegment) {
+        match *self {
+            Self::LineSegment { p0, p1, color } => line::split_in_thirds(p0, p1, color),
+            Self::QuadraticSegment { p0, p1, p2, color } => {
+                quadratic::split_in_thirds(p0, p1, p2, color)
+            }
+            Self::CubicSegment {
+                p0,
+                p1,
+                p2,
+                p3,
+                color,
+            } => cubic::split_in_thirds(p0, p1, p2, p3, color),
+        }
+    }
+
+    pub fn signed_distance(&self, origin: Vector2) -> (SignedDistance, f64) {
+        match *self {
+            Self::LineSegment { p0, p1, .. } => line::signed_distance(p0, p1, origin),
+            Self::QuadraticSegment { p0, p1, p2, .. } => {
+                quadratic::signed_distance(p0, p1, p2, origin)
+            }
+            Self::CubicSegment { p0, p1, p2, p3, .. } => {
+                cubic::signed_distance(p0, p1, p2, p3, origin)
+            }
+        }
+    }
+
+    pub fn has_color(&self, c: EdgeColor) -> bool {
+        match *self {
+            Self::LineSegment { color, .. } => color as usize & c as usize != 0,
+            Self::QuadraticSegment { color, .. } => color as usize & c as usize != 0,
+            Self::CubicSegment { color, .. } => color as usize & c as usize != 0,
+        }
+    }
+
+    pub fn get_color(&self) -> EdgeColor {
+        match self {
+            Self::LineSegment { color, .. } => *color,
+            Self::QuadraticSegment { color, .. } => *color,
+            Self::CubicSegment { color, .. } => *color,
+        }
+    }
+
+    pub fn set_color(&mut self, c: EdgeColor) {
+        match self {
+            Self::LineSegment { color, .. } => *color = c,
+            Self::QuadraticSegment { color, .. } => *color = c,
+            Self::CubicSegment { color, .. } => *color = c,
+        }
+    }
+}
diff --git a/kayak_font/src/msdf/edge_segment/quadratic.rs b/kayak_font/src/msdf/edge_segment/quadratic.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c0edbc6492a27197fbbad4f826ae617ab3fc1862
--- /dev/null
+++ b/kayak_font/src/msdf/edge_segment/quadratic.rs
@@ -0,0 +1,130 @@
+use crate::msdf::{signed_distance::SignedDistance, vector::Vector2, EdgeColor};
+
+use super::{equation_solver, mix, non_zero_sign, EdgeSegment};
+
+pub fn direction(p0: Vector2, p1: Vector2, p2: Vector2, param: f64) -> Vector2 {
+    mix(p1 - p0, p2 - p1, param)
+}
+
+pub fn point(p0: Vector2, p1: Vector2, p2: Vector2, param: f64) -> Vector2 {
+    mix(mix(p0, p1, param), mix(p1, p2, param), param)
+}
+
+pub fn find_bounds(
+    p0: Vector2,
+    p1: Vector2,
+    p2: Vector2,
+    l: &mut f64,
+    b: &mut f64,
+    r: &mut f64,
+    t: &mut f64,
+) {
+    Vector2::point_bounds(p0, l, b, r, t);
+    Vector2::point_bounds(p2, l, b, r, t);
+    let bot = (p1 - p0) - (p2 - p1);
+    if bot.x != 0.0 {
+        let param = (p1.x - p0.x) / bot.x;
+        if param > 0.0 && param < 1.0 {
+            Vector2::point_bounds(point(p0, p1, p2, param), l, b, r, t);
+        }
+    }
+    if bot.y != 0.0 {
+        let param = (p1.y - p0.y) / bot.y;
+        if param > 0.0 && param < 1.0 {
+            Vector2::point_bounds(point(p0, p1, p2, param), l, b, r, t);
+        }
+    }
+}
+
+pub fn split_in_thirds(
+    p0: Vector2,
+    p1: Vector2,
+    p2: Vector2,
+    color: EdgeColor,
+) -> (EdgeSegment, EdgeSegment, EdgeSegment) {
+    (
+        EdgeSegment::new_quadratic(
+            p0,
+            mix(p0, p1, 1.0 / 3.0),
+            point(p0, p1, p2, 1.0 / 3.0),
+            color,
+        ),
+        EdgeSegment::new_quadratic(
+            point(p0, p1, p2, 1.0 / 3.0),
+            mix(mix(p0, p1, 5.0 / 9.0), mix(p1, p2, 4.0 / 9.0), 0.5),
+            point(p0, p1, p2, 2.0 / 3.0),
+            color,
+        ),
+        EdgeSegment::new_quadratic(
+            point(p0, p1, p2, 2.0 / 3.0),
+            mix(p1, p2, 2.0 / 3.0),
+            p2,
+            color,
+        ),
+    )
+}
+
+pub fn signed_distance(
+    p0: Vector2,
+    p1: Vector2,
+    p2: Vector2,
+    origin: Vector2,
+) -> (SignedDistance, f64) {
+    let qa = p0 - origin;
+    let ab = p1 - p0;
+    // let br = p0 + p2 - p1 - p1;
+    let br = p2 - p1 - ab;
+    let a = Vector2::dot_product(br, br);
+    let b = 3.0 * Vector2::dot_product(ab, br);
+    let c = 2.0 * Vector2::dot_product(ab, ab) + Vector2::dot_product(qa, br);
+    let d = Vector2::dot_product(qa, ab);
+
+    let (solutions, t) = equation_solver::solve_cubic(a, b, c, d);
+
+    let mut min_distance = non_zero_sign(Vector2::cross_product(ab, qa)) as f64 * qa.length();
+    let mut param = -Vector2::dot_product(qa, ab) / Vector2::dot_product(ab, ab);
+    {
+        let distance = non_zero_sign(Vector2::cross_product(p2 - p1, p2 - origin)) as f64
+            * (p2 - origin).length();
+        if distance.abs() < min_distance.abs() {
+            min_distance = distance;
+            param =
+                Vector2::dot_product(origin - p1, p2 - p1) / Vector2::dot_product(p2 - p1, p2 - p1);
+        }
+    }
+
+    for i in 0..solutions {
+        let ti = t[i as usize];
+
+        if ti > 0.0 && ti < 1.0 {
+            let endpoint = p0 + 2.0 * ti * ab + ti * ti * br;
+            let distance = non_zero_sign(Vector2::cross_product(p2 - p0, endpoint - origin)) as f64
+                * (endpoint - origin).length();
+            if distance.abs() <= min_distance.abs() {
+                min_distance = distance;
+                param = ti;
+            }
+        }
+    }
+
+    if param >= 0.0 && param <= 1.0 {
+        return (SignedDistance::new(min_distance, 0.0), param);
+    } else if param < 0.5 {
+        return (
+            SignedDistance::new(
+                min_distance,
+                (Vector2::dot_product(ab.normalize(false), qa.normalize(false))).abs(),
+            ),
+            param,
+        );
+    } else {
+        return (
+            SignedDistance::new(
+                min_distance,
+                (Vector2::dot_product((p2 - p1).normalize(false), (p2 - origin).normalize(false)))
+                    .abs(),
+            ),
+            param,
+        );
+    }
+}
diff --git a/kayak_font/src/msdf/gen.rs b/kayak_font/src/msdf/gen.rs
new file mode 100644
index 0000000000000000000000000000000000000000..61cc4f9f581e9af5213fdd022df8267dea1d053a
--- /dev/null
+++ b/kayak_font/src/msdf/gen.rs
@@ -0,0 +1,386 @@
+use crate::msdf::{
+    bitmap::{FloatRGB, FloatRGBBmp},
+    edge_point::EdgePoint,
+    shape::Shape,
+    signed_distance::SignedDistance,
+    vector::Vector2,
+    MultiDistance,
+};
+
+use super::EdgeColor;
+
+fn min<T: PartialOrd>(a: T, b: T) -> T {
+    if a > b {
+        b
+    } else {
+        a
+    }
+}
+
+fn max<T: PartialOrd>(a: T, b: T) -> T {
+    if a > b {
+        a
+    } else {
+        b
+    }
+}
+
+fn median<T: PartialOrd + Copy>(a: T, b: T, c: T) -> T {
+    max(min(a, b), min(max(a, b), c))
+}
+
+pub fn pixel_clash(a: FloatRGB, b: FloatRGB, threshold: f64) -> bool {
+    let mut a0 = a.r;
+    let mut a1 = a.g;
+    let mut a2 = a.b;
+    let mut b0 = b.r;
+    let mut b1 = b.g;
+    let mut b2 = b.b;
+
+    let mut tmp;
+    if (b0 - a0).abs() < (b1 - a1).abs() {
+        tmp = a0;
+        a0 = a1;
+        a1 = tmp;
+        tmp = b0;
+        b0 = b1;
+        b1 = tmp;
+    }
+
+    if (b1 - a1).abs() < (b2 - a2).abs() {
+        tmp = a1;
+        a1 = a2;
+        a2 = tmp;
+        tmp = b1;
+        b1 = b2;
+        b2 = tmp;
+        if (b0 - a0).abs() < (b1 - a1).abs() {
+            tmp = a0;
+            a1 = tmp;
+            tmp = b0;
+            b0 = b1;
+            b1 = tmp;
+        }
+    }
+
+    ((b1 - a1).abs() >= threshold as f32)
+        && !(b0 == b1 && b0 == b2)
+        && (a2 - 0.5).abs() >= (b2 - 0.5).abs()
+
+    // let a_calcd = if a.r > 0.5 { 1.0 } else { 0.0 }
+    //     + if a.g > 0.5 { 1.0 } else { 0.0 }
+    //     + if a.b > 0.5 { 1.0 } else { 0.0 };
+    // let b_calcd = if b.r > 0.5 { 1.0 } else { 0.0 }
+    //     + if b.g > 0.5 { 1.0 } else { 0.0 }
+    //     + if b.b > 0.5 { 1.0 } else { 0.0 };
+    // let a_in = a_calcd >= 2.0;
+    // let b_in = b_calcd >= 2.0;
+
+    // if a_in != b_in {
+    //     return false;
+    // }
+
+    // if (a.r > 0.5 && a.g > 0.5 && a.b > 0.5)
+    //     || (a.r < 0.5 && a.g < 0.5 && a.b < 0.5)
+    //     || (b.r > 0.5 && b.g > 0.5 && b.b > 0.5)
+    //     || (b.r < 0.5 && b.g < 0.5 && b.b < 0.5)
+    // {
+    //     return false;
+    // }
+
+    // let aa;
+    // let ab;
+    // let ba;
+    // let bb;
+    // let ac;
+    // let bc;
+
+    // if (a.r > 0.5) != (b.r > 0.5) && (a.r < 0.5) != (b.r < 0.5) {
+    //     aa = a.r;
+    //     ba = b.r;
+    //     if (a.g > 0.5) != (b.g > 0.5) && (a.g < 0.5) != (b.g < 0.5) {
+    //         ab = a.g;
+    //         bb = b.g;
+    //         ac = a.b;
+    //         bc = b.b;
+    //     } else if (a.b > 0.5) != (b.b > 0.5) && (a.b < 0.5) != (b.b < 0.5) {
+    //         ab = a.b;
+    //         bb = b.b;
+    //         ac = a.g;
+    //         bc = b.g;
+    //     } else {
+    //         return false;
+    //     }
+    // } else if (a.g > 0.5) != (b.g > 0.5)
+    //     && (a.g < 0.5) != (b.g < 0.5)
+    //     && (a.b > 0.5) != (b.b > 0.5)
+    //     && (a.b < 0.5) != (b.b < 0.5)
+    // {
+    //     aa = a.g;
+    //     ba = b.g;
+    //     ab = a.b;
+    //     bb = b.b;
+    //     ac = a.r;
+    //     bc = b.r;
+    // } else {
+    //     return false;
+    // }
+
+    // return ((aa - ba).abs() >= threshold as f32)
+    //     && ((ab - bb).abs() >= threshold as f32)
+    //     && (ac - 0.5).abs() >= (bc - 0.5).abs();
+}
+
+pub fn msdf_error_correction(output: &mut FloatRGBBmp, threshold: Vector2) {
+    let mut clashes: Vec<(usize, usize)> = Vec::new();
+    let w = output.width();
+    let h = output.height();
+    for y in 0..h {
+        for x in 0..w {
+            if (x > 0
+                && pixel_clash(
+                    output.get_pixel(x, y),
+                    output.get_pixel(x - 1, y),
+                    threshold.x,
+                ))
+                || (x < w - 1
+                    && pixel_clash(
+                        output.get_pixel(x, y),
+                        output.get_pixel(x + 1, y),
+                        threshold.x,
+                    ))
+                || (y > 0
+                    && pixel_clash(
+                        output.get_pixel(x, y),
+                        output.get_pixel(x, y - 1),
+                        threshold.y,
+                    ))
+                || (y < h - 1
+                    && pixel_clash(
+                        output.get_pixel(x, y),
+                        output.get_pixel(x, y + 1),
+                        threshold.y,
+                    ))
+            {
+                clashes.push((x, y));
+            }
+        }
+    }
+    let clash_count = clashes.len();
+    for i in 0..clash_count {
+        let clash = clashes[i];
+        let pixel = output.get_pixel(clash.0, clash.1);
+        let med = median(pixel.r, pixel.g, pixel.b);
+        output.set_pixel(clash.0, clash.1, FloatRGB::new(med, med, med));
+    }
+}
+
+pub fn generate_msdf(
+    output: &mut FloatRGBBmp,
+    shape: &Shape,
+    range: f64,
+    scale: Vector2,
+    translate: Vector2,
+    edge_threshold: f64,
+) {
+    let contours = &shape.contours;
+    let contour_count = contours.len();
+    let w = output.width();
+    let h = output.height();
+    let mut windings = Vec::with_capacity(contour_count);
+
+    for contour in contours {
+        windings.push(contour.winding());
+    }
+
+    let mut contour_sd = vec![MultiDistance::default(); contour_count];
+
+    for y in 0..h {
+        let row = if shape.inverse_y_axis { h - y - 1 } else { y };
+        for x in 0..w {
+            let p = (Vector2::new(x as f64 + 0.5, y as f64 + 0.5) / scale) - translate;
+            let mut sr = EdgePoint {
+                min_distance: SignedDistance::infinite(),
+                near_edge: None,
+                near_param: 0.0,
+            };
+            let mut sg = EdgePoint {
+                min_distance: SignedDistance::infinite(),
+                near_edge: None,
+                near_param: 0.0,
+            };
+            let mut sb = EdgePoint {
+                min_distance: SignedDistance::infinite(),
+                near_edge: None,
+                near_param: 0.0,
+            };
+
+            let mut d = SignedDistance::infinite().distance.abs();
+            let mut neg_dist = -SignedDistance::infinite().distance.abs();
+            let mut pos_dist = d;
+
+            let mut winding = 0;
+
+            for (n, contour) in contours.iter().enumerate() {
+                let edges = &contour.edges;
+                let mut r = EdgePoint {
+                    min_distance: SignedDistance::infinite(),
+                    near_edge: None,
+                    near_param: 0.0,
+                };
+                let mut g = EdgePoint {
+                    min_distance: SignedDistance::infinite(),
+                    near_edge: None,
+                    near_param: 0.0,
+                };
+                let mut b = EdgePoint {
+                    min_distance: SignedDistance::infinite(),
+                    near_edge: None,
+                    near_param: 0.0,
+                };
+                for edge in edges {
+                    let (distance, param) = edge.signed_distance(p);
+                    if edge.has_color(EdgeColor::RED) && distance.l(&r.min_distance) {
+                        r.min_distance = distance;
+                        r.near_edge = Some(*edge);
+                        r.near_param = param;
+                    }
+                    if edge.has_color(EdgeColor::GREEN) && distance.l(&g.min_distance) {
+                        g.min_distance = distance;
+                        g.near_edge = Some(*edge);
+                        g.near_param = param;
+                    }
+                    if edge.has_color(EdgeColor::BLUE) && distance.l(&b.min_distance) {
+                        b.min_distance = distance;
+                        b.near_edge = Some(*edge);
+                        b.near_param = param;
+                    }
+                }
+
+                if r.min_distance.l(&sr.min_distance) {
+                    sr = r;
+                }
+                if g.min_distance.l(&sg.min_distance) {
+                    sg = g;
+                }
+                if b.min_distance.l(&sb.min_distance) {
+                    sb = b;
+                }
+
+                let mut med_min_distance = median(
+                    r.min_distance.distance,
+                    g.min_distance.distance,
+                    b.min_distance.distance,
+                )
+                .abs();
+
+                if med_min_distance < d {
+                    d = med_min_distance;
+                    winding = -windings[n];
+                }
+
+                if let Some(near_edge) = &mut r.near_edge {
+                    near_edge.distance_to_pseudo_distance(&mut r.min_distance, p, r.near_param);
+                }
+                if let Some(near_edge) = &mut g.near_edge {
+                    near_edge.distance_to_pseudo_distance(&mut g.min_distance, p, g.near_param);
+                }
+                if let Some(near_edge) = &mut b.near_edge {
+                    near_edge.distance_to_pseudo_distance(&mut b.min_distance, p, b.near_param);
+                }
+
+                med_min_distance = median(
+                    r.min_distance.distance,
+                    g.min_distance.distance,
+                    b.min_distance.distance,
+                );
+                contour_sd[n].r = r.min_distance.distance;
+                contour_sd[n].g = g.min_distance.distance;
+                contour_sd[n].b = b.min_distance.distance;
+                contour_sd[n].med = med_min_distance;
+                if windings[n] > 0
+                    && med_min_distance >= 0.0
+                    && med_min_distance.abs() < pos_dist.abs()
+                {
+                    pos_dist = med_min_distance;
+                }
+                if windings[n] < 0
+                    && med_min_distance <= 0.0
+                    && med_min_distance.abs() < neg_dist.abs()
+                {
+                    neg_dist = med_min_distance;
+                }
+            }
+
+            if let Some(near_edge) = &mut sr.near_edge {
+                near_edge.distance_to_pseudo_distance(&mut sr.min_distance, p, sr.near_param);
+            }
+            if let Some(near_edge) = &mut sg.near_edge {
+                near_edge.distance_to_pseudo_distance(&mut sg.min_distance, p, sg.near_param);
+            }
+            if let Some(near_edge) = &mut sb.near_edge {
+                near_edge.distance_to_pseudo_distance(&mut sb.min_distance, p, sb.near_param);
+            }
+
+            let mut msd = MultiDistance::default();
+            msd.r = SignedDistance::infinite().distance;
+            msd.b = msd.r;
+            msd.g = msd.r;
+            msd.med = msd.r;
+            if pos_dist >= 0.0 && pos_dist.abs() <= neg_dist.abs() {
+                msd.med = SignedDistance::infinite().distance;
+                winding = 1;
+                for i in 0..contours.len() {
+                    if windings[i] > 0
+                        && contour_sd[i].med > msd.med
+                        && contour_sd[i].med.abs() < neg_dist.abs()
+                    {
+                        msd = contour_sd[i];
+                    }
+                }
+            } else if neg_dist <= 0.0 && neg_dist.abs() <= pos_dist.abs() {
+                msd.med = -SignedDistance::infinite().distance;
+                winding = -1;
+                for i in 0..contours.len() {
+                    if windings[i] < 0
+                        && contour_sd[i].med < msd.med
+                        && contour_sd[i].med.abs() < pos_dist.abs()
+                    {
+                        msd = contour_sd[i];
+                    }
+                }
+            }
+
+            for i in 0..contours.len() {
+                if windings[i] != winding && contour_sd[i].med.abs() < msd.med.abs() {
+                    msd = contour_sd[i];
+                }
+            }
+
+            if median(
+                sr.min_distance.distance,
+                sg.min_distance.distance,
+                sb.min_distance.distance,
+            ) == msd.med
+            {
+                msd.r = sr.min_distance.distance;
+                msd.g = sg.min_distance.distance;
+                msd.b = sb.min_distance.distance;
+            }
+
+            output.set_pixel(
+                x,
+                row,
+                FloatRGB {
+                    r: (msd.r / range + 0.5) as f32,
+                    g: (msd.g / range + 0.5) as f32,
+                    b: (msd.b / range + 0.5) as f32,
+                },
+            )
+        }
+
+        if edge_threshold > 0.0 {
+            msdf_error_correction(output, edge_threshold / (scale * range));
+        }
+    }
+}
diff --git a/kayak_font/src/msdf/mod.rs b/kayak_font/src/msdf/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a687ffecfdbf52b72f4129ad4be27da7cbbff080
--- /dev/null
+++ b/kayak_font/src/msdf/mod.rs
@@ -0,0 +1,33 @@
+use num_derive::FromPrimitive;
+
+pub mod bitmap;
+pub mod contour;
+pub mod edge_coloring;
+pub mod edge_point;
+pub mod edge_segment;
+pub mod gen;
+pub mod msdf_params;
+pub mod shape;
+pub mod signed_distance;
+pub mod ttf_parser;
+pub mod vector;
+
+#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive)]
+pub enum EdgeColor {
+    BLACK = 0,
+    RED = 1,
+    GREEN = 2,
+    YELLOW = 3,
+    BLUE = 4,
+    MAGENTA = 5,
+    CYAN = 6,
+    WHITE = 7,
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct MultiDistance {
+    pub b: f64,
+    pub g: f64,
+    pub med: f64,
+    pub r: f64,
+}
diff --git a/kayak_font/src/msdf/msdf_params.rs b/kayak_font/src/msdf/msdf_params.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cdc33e315eb398b7a853526754e7850d44150360
--- /dev/null
+++ b/kayak_font/src/msdf/msdf_params.rs
@@ -0,0 +1,35 @@
+#![allow(dead_code)]
+
+pub struct MsdfParams {
+    pub scale_x: f32,
+    pub scale_y: f32,
+    pub shape_scale: f32,
+    pub min_image_width: usize,
+    pub min_image_height: usize,
+    pub angle_threshold: f64,
+    pub px_range: f64,
+    pub edge_threshold: f64,
+    pub use_custom_image_size: bool,
+    pub custom_width: usize,
+    pub custom_height: usize,
+    pub custom_border: usize,
+}
+
+impl MsdfParams {
+    pub fn new() -> Self {
+        Self {
+            scale_x: 1.0,
+            scale_y: 1.0,
+            shape_scale: 1.0,
+            min_image_width: 5,
+            min_image_height: 5,
+            angle_threshold: 3.0,
+            px_range: 4.0,
+            edge_threshold: 1.00000001,
+            use_custom_image_size: false,
+            custom_width: 0,
+            custom_height: 0,
+            custom_border: 0,
+        }
+    }
+}
diff --git a/kayak_font/src/msdf/shape.rs b/kayak_font/src/msdf/shape.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5b4d34f3f636dd3bf207d820a3e892e3bacb83a9
--- /dev/null
+++ b/kayak_font/src/msdf/shape.rs
@@ -0,0 +1,59 @@
+#![allow(dead_code)]
+
+use crate::msdf::contour::Contour;
+
+#[derive(Debug, Default, Clone)]
+pub struct Shape {
+    pub contours: Vec<Contour>,
+    pub inverse_y_axis: bool,
+}
+
+impl Shape {
+    pub fn new() -> Self {
+        Self {
+            contours: Vec::new(),
+            inverse_y_axis: false,
+        }
+    }
+
+    pub fn normalized(&mut self) {
+        for contour in self.contours.iter_mut() {
+            let (e0, e1, e2) = contour.edges[0].split_in_thirds();
+            contour.edges.clear();
+            contour.edges.push(e0);
+            contour.edges.push(e1);
+            contour.edges.push(e2);
+        }
+    }
+
+    fn find_bounds(&mut self, left: &mut f64, bottom: &mut f64, right: &mut f64, top: &mut f64) {
+        for contour in self.contours.iter_mut() {
+            contour.find_bounds(left, bottom, right, top);
+        }
+    }
+
+    pub fn bound_miters(
+        &self,
+        l: &mut f64,
+        b: &mut f64,
+        r: &mut f64,
+        t: &mut f64,
+        border: f64,
+        miter_limit: f64,
+        polarity: i32,
+    ) {
+        for contour in self.contours.iter() {
+            contour.bound_miters(l, b, r, t, border, miter_limit, polarity);
+        }
+    }
+
+    pub fn get_bounds(&mut self) -> (f64, f64, f64, f64) {
+        const LARGE_VALUE: f64 = 1e240;
+        let mut left = -LARGE_VALUE;
+        let mut bottom = LARGE_VALUE;
+        let mut right = LARGE_VALUE;
+        let mut top = -LARGE_VALUE;
+        self.find_bounds(&mut left, &mut bottom, &mut right, &mut top);
+        return (left, bottom, right, top);
+    }
+}
diff --git a/kayak_font/src/msdf/signed_distance.rs b/kayak_font/src/msdf/signed_distance.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2ecab9115651ea9f064a805684c6be6cd71f2a13
--- /dev/null
+++ b/kayak_font/src/msdf/signed_distance.rs
@@ -0,0 +1,38 @@
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct SignedDistance {
+    pub distance: f64,
+    pub dot: f64,
+}
+
+impl SignedDistance {
+    pub fn infinite() -> Self {
+        Self {
+            distance: -1e240,
+            dot: 1.0,
+        }
+    }
+
+    pub fn new(distance: f64, dot: f64) -> Self {
+        Self { distance, dot }
+    }
+
+    // pub fn g(&self, other: &SignedDistance) -> bool {
+    //     self.distance.abs() > other.distance.abs()
+    //         || (self.distance.abs() == other.distance.abs() && self.dot > other.dot)
+    // }
+
+    // pub fn ge(&self, other: &SignedDistance) -> bool {
+    //     self.distance.abs() > other.distance.abs()
+    //         || (self.distance.abs() == other.distance.abs() && self.dot >= other.dot)
+    // }
+
+    pub fn l(&self, other: &SignedDistance) -> bool {
+        self.distance.abs() < other.distance.abs()
+            || (self.distance.abs() == other.distance.abs() && self.dot < other.dot)
+    }
+
+    // pub fn le(&self, other: &SignedDistance) -> bool {
+    //     self.distance.abs() < other.distance.abs()
+    //         || (self.distance.abs() == other.distance.abs() && self.dot <= other.dot)
+    // }
+}
diff --git a/kayak_font/src/msdf/ttf_parser.rs b/kayak_font/src/msdf/ttf_parser.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dee277309f9f0cb43caee3f505890d5e2cfbf396
--- /dev/null
+++ b/kayak_font/src/msdf/ttf_parser.rs
@@ -0,0 +1,110 @@
+use crate::msdf::{
+    contour::Contour, edge_segment::EdgeSegment, shape::Shape, vector::Vector2, EdgeColor,
+};
+
+#[derive(Debug, Default)]
+pub struct ContourBuilder {
+    pixel_scale: f64,
+    contour: Contour,
+    point: Vector2,
+}
+
+impl ContourBuilder {
+    pub fn open_at(x: f64, y: f64, pixel_scale: f64) -> Self {
+        Self {
+            contour: Contour::new(),
+            point: Vector2::new(x, y),
+            pixel_scale,
+        }
+    }
+
+    pub fn line_to(&mut self, x: f64, y: f64) {
+        let point = Vector2::new(x, y);
+        self.contour.add_edge(EdgeSegment::new_linear(
+            self.point * self.pixel_scale,
+            point * self.pixel_scale,
+            EdgeColor::WHITE,
+        ));
+        self.point = point;
+    }
+
+    pub fn quad_to(&mut self, cx: f64, cy: f64, x: f64, y: f64) {
+        let cpoint = Vector2::new(cx, cy);
+        let point = Vector2::new(x, y);
+        self.contour.add_edge(EdgeSegment::new_quadratic(
+            self.point * self.pixel_scale,
+            cpoint * self.pixel_scale,
+            point * self.pixel_scale,
+            EdgeColor::WHITE,
+        ));
+        self.point = point;
+    }
+
+    pub fn curve_to(&mut self, c1x: f64, c1y: f64, c2x: f64, c2y: f64, x: f64, y: f64) {
+        let c1point = Vector2::new(c1x, c1y);
+        let c2point = Vector2::new(c2x, c2y);
+        let point = Vector2::new(x, y);
+        self.contour.add_edge(EdgeSegment::new_cubic(
+            self.point * self.pixel_scale,
+            c1point * self.pixel_scale,
+            c2point * self.pixel_scale,
+            point * self.pixel_scale,
+            EdgeColor::WHITE,
+        ));
+        self.point = point;
+    }
+
+    pub fn close(self) -> Contour {
+        self.contour
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct ShapeBuilder {
+    pub pixel_scale: f64,
+    shape: Shape,
+    contour: Option<ContourBuilder>,
+}
+
+impl ShapeBuilder {
+    pub fn build(self) -> Shape {
+        self.shape
+    }
+}
+
+impl ttf_parser::OutlineBuilder for ShapeBuilder {
+    fn move_to(&mut self, x: f32, y: f32) {
+        if self.contour.is_some() {
+            panic!("Unexpected move_to");
+        }
+
+        self.contour = ContourBuilder::open_at(x as _, y as _, self.pixel_scale).into();
+    }
+
+    fn line_to(&mut self, x: f32, y: f32) {
+        self.contour
+            .as_mut()
+            .expect("Opened contour")
+            .line_to(x as _, y as _);
+    }
+
+    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
+        self.contour
+            .as_mut()
+            .expect("Opened contour")
+            .quad_to(x1 as _, y1 as _, x as _, y as _);
+    }
+
+    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
+        self.contour
+            .as_mut()
+            .expect("Opened contour")
+            .curve_to(x1 as _, y1 as _, x2 as _, y2 as _, x as _, y as _);
+    }
+
+    fn close(&mut self) {
+        self.shape
+            .contours
+            .push(self.contour.take().expect("Opened contour").close());
+    }
+}
diff --git a/kayak_font/src/msdf/vector.rs b/kayak_font/src/msdf/vector.rs
new file mode 100644
index 0000000000000000000000000000000000000000..34d7a29534dcbba343328e317b16dc29ac311b05
--- /dev/null
+++ b/kayak_font/src/msdf/vector.rs
@@ -0,0 +1,159 @@
+#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
+pub struct Vector2 {
+    pub x: f64,
+    pub y: f64,
+}
+
+impl Vector2 {
+    pub fn new(x: f64, y: f64) -> Self {
+        Self { x, y }
+    }
+
+    pub fn is_zero(&self) -> bool {
+        self.x == 0.0 && self.y == 0.0
+    }
+
+    pub fn get_ortho_normal(&self, polarity: bool, allow_zero: bool) -> Vector2 {
+        let len = self.length();
+        if len == 0.0 {
+            let allow_zero = if !allow_zero { 1.0 } else { 0.0 };
+            return if polarity {
+                Vector2::new(0.0, allow_zero)
+            } else {
+                Vector2::new(0.0, -allow_zero)
+            };
+        }
+        return if polarity {
+            Vector2::new(-self.y / len, self.x / len)
+        } else {
+            Vector2::new(self.y / len, -self.x / len)
+        };
+    }
+
+    pub fn get_orthogonal(&self, polarity: bool) -> Vector2 {
+        return if polarity {
+            Vector2::new(-self.y, self.x)
+        } else {
+            Vector2::new(self.y, -self.x)
+        };
+    }
+    pub fn dot_product(a: Vector2, b: Vector2) -> f64 {
+        return a.x * b.x + a.y * b.y;
+    }
+    pub fn cross_product(a: Vector2, b: Vector2) -> f64 {
+        return a.x * b.y - a.y * b.x;
+    }
+
+    pub fn normalize(&self, allow_zero: bool) -> Vector2 {
+        let len = self.length();
+        if len == 0.0 {
+            let allow_zero = if !allow_zero { 1.0 } else { 0.0 };
+            return Vector2::new(0.0, allow_zero);
+        }
+        return Vector2::new(self.x / len, self.y / len);
+    }
+
+    pub fn length(&self) -> f64 {
+        return (self.x * self.x + self.y * self.y).sqrt();
+    }
+
+    pub fn clamp(n: i32, b: i32) -> i32 {
+        if n > 0 {
+            return if n <= b { n } else { b };
+        }
+        return 0;
+    }
+
+    pub fn sign(n: f64) -> f64 {
+        return if n == 0.0 {
+            0.0
+        } else if n > 0.0 {
+            1.0
+        } else {
+            -1.0
+        };
+    }
+
+    pub fn shoelace(a: Vector2, b: Vector2) -> f64 {
+        return (b.x - a.x) * (a.y + b.y);
+    }
+
+    pub fn point_bounds(p: Vector2, l: &mut f64, b: &mut f64, r: &mut f64, t: &mut f64) {
+        if p.x < *l {
+            *l = p.x;
+        }
+        if p.y < *b {
+            *b = p.y;
+        }
+        if p.x > *r {
+            *r = p.x;
+        }
+        if p.y > *t {
+            *t = p.y;
+        }
+    }
+}
+
+impl std::ops::Sub for Vector2 {
+    type Output = Vector2;
+
+    fn sub(self, rhs: Self) -> Self::Output {
+        Vector2::new(self.x - rhs.x, self.y - rhs.y)
+    }
+}
+
+impl std::ops::Add for Vector2 {
+    type Output = Vector2;
+
+    fn add(self, rhs: Self) -> Self::Output {
+        Vector2::new(self.x + rhs.x, self.y + rhs.y)
+    }
+}
+
+impl std::ops::Mul for Vector2 {
+    type Output = Vector2;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Vector2::new(self.x * rhs.x, self.y * rhs.y)
+    }
+}
+
+impl std::ops::Div for Vector2 {
+    type Output = Vector2;
+
+    fn div(self, rhs: Self) -> Self::Output {
+        Vector2::new(self.x / rhs.x, self.y / rhs.y)
+    }
+}
+
+impl std::ops::Mul<f64> for Vector2 {
+    type Output = Vector2;
+
+    fn mul(self, rhs: f64) -> Self::Output {
+        Vector2::new(self.x * rhs, self.y * rhs)
+    }
+}
+
+impl std::ops::Div<f64> for Vector2 {
+    type Output = Vector2;
+
+    fn div(self, rhs: f64) -> Self::Output {
+        Vector2::new(self.x / rhs, self.y / rhs)
+    }
+}
+
+impl std::ops::Mul<Vector2> for f64 {
+    type Output = Vector2;
+
+    fn mul(self, rhs: Vector2) -> Self::Output {
+        Vector2::new(rhs.x * self, rhs.y * self)
+    }
+}
+
+impl std::ops::Div<Vector2> for f64 {
+    type Output = Vector2;
+
+    fn div(self, rhs: Vector2) -> Self::Output {
+        Vector2::new(rhs.x / self, rhs.y / self)
+    }
+}
diff --git a/kayak_font/src/sdf.rs b/kayak_font/src/sdf.rs
index ec6ed8e5ccb63a14dd280830516dd03a81f64805..dd928c47fa3fdf55faa45ef48163d27009e0b90f 100644
--- a/kayak_font/src/sdf.rs
+++ b/kayak_font/src/sdf.rs
@@ -1,7 +1,7 @@
 use crate::{atlas::Atlas, glyph::Glyph, metrics::Metrics};
 use nanoserde::DeJson;
 
-#[derive(DeJson, Debug, Clone, PartialEq)]
+#[derive(DeJson, Default, Debug, Clone, PartialEq)]
 pub struct Sdf {
     pub atlas: Atlas,
     metrics: Metrics,
@@ -9,7 +9,7 @@ pub struct Sdf {
     kerning: Vec<KerningData>,
 }
 
-#[derive(DeJson, Debug, Clone, Copy, PartialEq)]
+#[derive(DeJson, Default, Debug, Clone, Copy, PartialEq)]
 pub struct KerningData {
     pub unicode1: u32,
     pub unicode2: u32,
@@ -17,6 +17,15 @@ pub struct KerningData {
 }
 
 impl Sdf {
+    pub fn new() -> Self {
+        Self {
+            atlas: Atlas::default(),
+            metrics: Metrics::default(),
+            glyphs: Vec::default(),
+            kerning: Vec::default(),
+        }
+    }
+
     pub fn from_string(data: String) -> Sdf {
         let value: Sdf = match DeJson::deserialize_json(data.as_str()) {
             Ok(v) => v,
diff --git a/kayak_font/src/ttf/loader.rs b/kayak_font/src/ttf/loader.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ad1d68711a3e3589bd56afbe0730fb93b3efd42b
--- /dev/null
+++ b/kayak_font/src/ttf/loader.rs
@@ -0,0 +1,297 @@
+use bevy::{
+    asset::{AssetLoader, FileAssetIo, LoadContext, LoadedAsset},
+    render::render_resource::{Extent3d, TextureFormat},
+    utils::{BoxedFuture, HashMap},
+};
+use image::{EncodableLayout, RgbaImage};
+use nanoserde::DeJson;
+
+use crate::{
+    msdf::{self, bitmap::FloatRGBBmp, shape::Shape, ttf_parser::ShapeBuilder, vector::Vector2},
+    Glyph, ImageType, KayakFont, Rect, Sdf,
+};
+pub struct TTFLoader;
+
+#[derive(DeJson, Default, Debug, Clone)]
+pub struct KTTF {
+    file: String,
+    char_range_start: String,
+    char_range_end: String,
+}
+
+impl AssetLoader for TTFLoader {
+    fn load<'a>(
+        &'a self,
+        bytes: &'a [u8],
+        load_context: &'a mut LoadContext,
+    ) -> BoxedFuture<'a, Result<(), anyhow::Error>> {
+        Box::pin(async move {
+            let asset_io = load_context
+                .asset_io()
+                .downcast_ref::<FileAssetIo>()
+                .unwrap();
+            let kttf: KTTF =
+                nanoserde::DeJson::deserialize_json(std::str::from_utf8(bytes).unwrap()).unwrap();
+
+            let char_range_start =
+                u32::from_str_radix(kttf.char_range_start.trim_start_matches("0x"), 16)?;
+            let char_range_end =
+                u32::from_str_radix(kttf.char_range_end.trim_start_matches("0x"), 16)?;
+            let font_bytes = load_context.read_asset_bytes(kttf.file).await?;
+
+            let mut cache_path = std::path::PathBuf::from(load_context.path());
+            let file_name = load_context
+                .path()
+                .file_name()
+                .unwrap()
+                .to_str()
+                .unwrap()
+                .to_string();
+            cache_path.set_file_name(format!("{}-cached.png", file_name));
+            let cache_image = load_context.read_asset_bytes(&cache_path).await;
+
+            let font_range = char_range_start..char_range_end;
+            let char_count = font_range.len() as u32;
+
+            let size_x = 64usize;
+            let size_y = 128usize;
+            let face = ttf_parser::Face::parse(&font_bytes, 0).unwrap();
+            let image_height = size_y as u32 * char_count;
+            let mut image_builder: RgbaImage = image::ImageBuffer::new(size_x as u32, image_height);
+            let mut yy = 0u32;
+            let mut glyphs = vec![];
+
+            // Build char to glyph mapping..
+            let mut glyph_to_char: HashMap<ttf_parser::GlyphId, char> =
+                HashMap::with_capacity(face.number_of_glyphs() as usize);
+            let mut char_to_glyph: HashMap<char, ttf_parser::GlyphId> =
+                HashMap::with_capacity(face.number_of_glyphs() as usize);
+            if let Some(subtable) = face.tables().cmap {
+                for subtable in subtable.subtables {
+                    subtable.codepoints(|codepoint| {
+                        if let Some(mapping) = subtable.glyph_index(codepoint) {
+                            glyph_to_char
+                                .insert(mapping, unsafe { std::mem::transmute(codepoint) });
+                            char_to_glyph
+                                .insert(unsafe { std::mem::transmute(codepoint) }, mapping);
+                        }
+                    })
+                }
+            }
+
+            for char_u in font_range {
+                let c = char::from_u32(char_u).unwrap();
+                let glyph_id = *char_to_glyph.get(&c).unwrap();
+                let mut output = FloatRGBBmp::new(size_x, size_y);
+                let mut builder = ShapeBuilder::default();
+                let pixel_scale = size_x as f64 / face.units_per_em() as f64;
+                builder.pixel_scale = pixel_scale;
+                let _result = face.outline_glyph(glyph_id, &mut builder);
+
+                let char_bounds = face
+                    .glyph_bounding_box(glyph_id)
+                    .unwrap_or(ttf_parser::Rect {
+                        x_min: 0,
+                        x_max: size_x as i16,
+                        y_min: 0,
+                        y_max: size_y as i16,
+                    });
+
+                let mut shape = builder.build();
+                shape.inverse_y_axis = true;
+                // let (left, bottom, right, top) = shape.get_bounds();
+
+                let scale = Vector2::new(1.0, 1.0);
+                let px_range = 2.0;
+                let range = px_range / scale.x.min(scale.y);
+
+                let (translation, plane) =
+                    calculate_plane(&mut shape, pixel_scale as f32, 1.0, px_range as f32, 1.0);
+                let advance = face.glyph_hor_advance(glyph_id).unwrap_or(0) as f32 / size_x as f32;
+                let c = *glyph_to_char.get(&glyph_id).unwrap();
+                glyphs.push(Glyph {
+                    unicode: c,
+                    advance: advance * pixel_scale as f32,
+                    atlas_bounds: Some(Rect {
+                        left: 0.0,
+                        bottom: 0.0 as f32,
+                        right: size_x as f32,
+                        top: size_y as f32,
+                    }),
+                    plane_bounds: Some(plane),
+                });
+
+                // let frame = Vector2::new(size_x as f64, size_y as f64);
+
+                // dbg!((left, right, top, bottom));
+
+                // left = (left - (size_x as f64 / 8.0)).max(0.0);
+                // right = (right + (size_x as f64 / 8.0)).min(size_x as f64);
+                // top = (top + (size_y as f64 / 8.0)).min(size_y as f64);
+                // bottom = (bottom - (size_y as f64 / 8.0)).max(0.0);
+
+                // dbg!((left, right, top, bottom));
+
+                // let dims = Vector2::new(right - left, top - bottom);
+
+                // let translate = Vector2::new(-left + (frame.x - dims.x), (frame.y - (bottom + dims.y)) - 1.0);
+                if cache_image.is_err() {
+                    msdf::edge_coloring::simple(&mut shape, 3.0, 0);
+                    msdf::gen::generate_msdf(
+                        &mut output,
+                        &shape,
+                        range,
+                        scale,
+                        translation + Vector2::new(0.0, size_x as f64 * 1.25),
+                        1.11111111111111111,
+                    );
+
+                    // let left = (translation.x - char_bounds.x_min as f64 * pixel_scale).max(0.0).floor() as u32;
+                    let right =
+                        (translation.x + char_bounds.x_max as f64 * pixel_scale).floor() as u32;
+                    // let top = (translation.y - char_bounds.y_min as f64 * pixel_scale).max(0.0).floor() as u32;
+                    let bottom =
+                        (translation.y + char_bounds.y_max as f64 * pixel_scale).floor() as u32;
+
+                    for x in 0..(right + 2).min(64) {
+                        for y in 0..bottom + 48 {
+                            // for x in 0..size_x as u32 {
+                            //     for y  in 0..size_y as u32 {
+                            let pixel = output.get_pixel(x as usize, y as usize);
+                            image_builder.put_pixel(
+                                x,
+                                yy + y,
+                                image::Rgba([
+                                    (pixel.r * 255.0) as u8,
+                                    (pixel.g * 255.0) as u8,
+                                    (pixel.b * 255.0) as u8,
+                                    255,
+                                ]),
+                            );
+                        }
+                    }
+                }
+                // if c == '\"' {
+                //     image_builder.save("test.png").unwrap();
+                //     panic!("");
+                // }
+                yy += size_y as u32;
+            }
+
+            let image_bytes = if cache_image.is_err() {
+                image_builder
+                    .save(asset_io.root_path().join(cache_path))
+                    .unwrap();
+                image_builder.as_bytes().to_vec()
+            } else {
+                let cache_image = cache_image.unwrap();
+                let image = image::load_from_memory(&cache_image).unwrap();
+                image.as_bytes().to_vec()
+            };
+
+            let mut sdf = Sdf::default();
+            sdf.glyphs = glyphs;
+            sdf.atlas.font_size = size_x as f32;
+
+            let mut image = bevy::prelude::Image::new(
+                Extent3d {
+                    width: size_x as u32,
+                    height: image_height,
+                    depth_or_array_layers: 1,
+                },
+                bevy::render::render_resource::TextureDimension::D2,
+                image_bytes,
+                TextureFormat::Rgba8UnormSrgb,
+            );
+            image.reinterpret_stacked_2d_as_array(char_count);
+            let image_handle =
+                load_context.set_labeled_asset("font_image", LoadedAsset::new(image));
+
+            let font = KayakFont::new(sdf, ImageType::Array(image_handle));
+            load_context.set_default_asset(LoadedAsset::new(font));
+
+            Ok(())
+        })
+    }
+
+    fn extensions(&self) -> &[&str] {
+        &["kttf"]
+    }
+}
+
+fn calculate_plane(
+    shape: &mut Shape,
+    geometry_scale: f32,
+    scale: f32,
+    range: f32,
+    miter_limit: f32,
+) -> (Vector2, Rect) {
+    let bounds = shape.get_bounds();
+    let bounds = Rect {
+        left: bounds.0 as f32,
+        bottom: bounds.1 as f32,
+        right: bounds.2 as f32,
+        top: bounds.3 as f32,
+    };
+    let scale = scale * geometry_scale;
+    let range = range / geometry_scale;
+    let (_w, _h, translation_x, translation_y) =
+        if bounds.left < bounds.right && bounds.bottom < bounds.top {
+            let mut l = bounds.left as f64;
+            let mut b = bounds.bottom as f64;
+            let mut r = bounds.right as f64;
+            let mut t = bounds.top as f64;
+
+            l -= 0.5 * range as f64;
+            b -= 0.5 * range as f64;
+            r += 0.5 * range as f64;
+            t += 0.5 * range as f64;
+
+            if miter_limit > 0.0 {
+                shape.bound_miters(
+                    &mut l,
+                    &mut b,
+                    &mut r,
+                    &mut t,
+                    0.5 * range as f64,
+                    miter_limit as f64,
+                    1,
+                );
+            }
+
+            let w = scale as f64 * (r - l);
+            let h = scale as f64 * (t - b);
+            let box_w = w.ceil() as i32 + 1;
+            let box_h = h.ceil() as i32 + 1;
+            (
+                box_w,
+                box_h,
+                -l + 0.5 * (box_w as f64 - w) / scale as f64,
+                -b + 0.5 * (box_h as f64 - h) / scale as f64,
+            )
+        } else {
+            (0, 0, 0.0, 0.0)
+        };
+
+    // let mut l = 0.0;
+    // let mut r = 0.0;
+    // let mut b = 0.0;
+    // let mut t = 0.0;
+    // if w > 0 && h > 0 {
+    //     let inv_box_scale = 1.0 / scale as f64;
+    //     l = geometry_scale as f64 * (-translation_x + 0.5 * inv_box_scale);
+    //     b = geometry_scale as f64 * (-translation_y + 0.5 * inv_box_scale);
+    //     r = geometry_scale as f64 * (-translation_x + (w as f64 - 0.5) * inv_box_scale);
+    //     t = geometry_scale as f64 * (-translation_y + (h as f64 - 0.5) * inv_box_scale);
+    // }
+
+    (
+        Vector2::new(translation_x, translation_y) * geometry_scale as f64,
+        Rect {
+            left: 0.0,                  // l as f32,
+            bottom: 0.0,                // b as f32,
+            right: 0.0,                 // r as f32,
+            top: 24.0 * geometry_scale, // t as f32,
+        },
+    )
+}
diff --git a/kayak_font/src/ttf/mod.rs b/kayak_font/src/ttf/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8340811cbc45b0bac7e66fef0f51cf568e0d9d40
--- /dev/null
+++ b/kayak_font/src/ttf/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod loader;
diff --git a/kayak_font/test.png b/kayak_font/test.png
new file mode 100644
index 0000000000000000000000000000000000000000..91dc5f73aa63baa355b04aa2015970123c2f33cf
Binary files /dev/null and b/kayak_font/test.png differ