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