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..763bfe467b4043db60d0e896c2d30503a95bdb98 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::{KayakFont, Sdf, ImageType};
 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..d2097167428163504acda3866bb5f5990e5238f0 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::{KayakFont, Sdf, ImageType};
 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..f1ab7d4cb885800bc065240efdb192301848cbf2 100644
--- a/kayak_font/src/lib.rs
+++ b/kayak_font/src/lib.rs
@@ -5,6 +5,8 @@ mod layout;
 mod metrics;
 mod sdf;
 mod utility;
+mod ttf;
+mod msdf;
 
 pub use atlas::*;
 pub use font::*;
@@ -18,14 +20,14 @@ pub mod bevy;
 
 #[cfg(test)]
 mod tests {
-    use crate::{Alignment, KayakFont, Sdf, TextProperties};
+    use crate::{Alignment, KayakFont, Sdf, TextProperties, ImageType};
 
     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..672d37764a8ef6b3afe76af83425cb4a8f082bc3
--- /dev/null
+++ b/kayak_font/src/msdf/contour.rs
@@ -0,0 +1,189 @@
+#![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;
+    }
+}
\ No newline at end of file
diff --git a/kayak_font/src/msdf/edge_coloring.rs b/kayak_font/src/msdf/edge_coloring.rs
new file mode 100644
index 0000000000000000000000000000000000000000..46436d7a593cd33d62bc0f8adbc3b8916d150e2f
--- /dev/null
+++ b/kayak_font/src/msdf/edge_coloring.rs
@@ -0,0 +1,296 @@
+#![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..c59c37522b86b44d9ed5b0c174e969e91dae134d
--- /dev/null
+++ b/kayak_font/src/msdf/edge_segment/cubic.rs
@@ -0,0 +1,204 @@
+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..de0d53ef611ecdb390d68424dbf1f3728f82c66e
--- /dev/null
+++ b/kayak_font/src/msdf/edge_segment/mod.rs
@@ -0,0 +1,184 @@
+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..54fe2bebc97c837ad54c25e9eebb1d2040c20faa
--- /dev/null
+++ b/kayak_font/src/msdf/gen.rs
@@ -0,0 +1,384 @@
+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..62f18a483e194fcae1cf1b8bbc3f4f179b63633f
--- /dev/null
+++ b/kayak_font/src/msdf/shape.rs
@@ -0,0 +1,56 @@
+#![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..81080d691315a56db76c6f604a7f77f87ffde059
--- /dev/null
+++ b/kayak_font/src/ttf/loader.rs
@@ -0,0 +1,284 @@
+use bevy::{
+    asset::{AssetLoader, LoadContext, LoadedAsset, FileAssetIo},
+    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 {
+                        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: 4.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