Skip to content
Snippets Groups Projects
Unverified Commit 41c63963 authored by John's avatar John Committed by GitHub
Browse files

Merge pull request #176 from StarArawn/native-msdf

Native msdf
parents 650613e9 8f82c67d
No related branches found
No related tags found
No related merge requests found
#[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)
// }
}
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());
}
}
#[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)
}
}
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,
......
use bevy::{
asset::{AssetLoader, FileAssetIo, LoadContext, LoadedAsset},
render::render_resource::{Extent3d, TextureFormat},
utils::{BoxedFuture, HashMap},
};
use image::{EncodableLayout, RgbaImage};
use nanoserde::DeJson;
use crate::{
msdf::{self, bitmap::FloatRGBBmp, shape::Shape, ttf_parser::ShapeBuilder, vector::Vector2},
Glyph, ImageType, KayakFont, Rect, Sdf,
};
pub struct TTFLoader;
#[derive(DeJson, Default, Debug, Clone)]
pub struct KTTF {
file: String,
char_range_start: String,
char_range_end: String,
}
impl AssetLoader for TTFLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<(), anyhow::Error>> {
Box::pin(async move {
let asset_io = load_context
.asset_io()
.downcast_ref::<FileAssetIo>()
.unwrap();
let kttf: KTTF =
nanoserde::DeJson::deserialize_json(std::str::from_utf8(bytes).unwrap()).unwrap();
let char_range_start =
u32::from_str_radix(kttf.char_range_start.trim_start_matches("0x"), 16)?;
let char_range_end =
u32::from_str_radix(kttf.char_range_end.trim_start_matches("0x"), 16)?;
let font_bytes = load_context.read_asset_bytes(kttf.file).await?;
let mut cache_path = std::path::PathBuf::from(load_context.path());
let file_name = load_context
.path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
cache_path.set_file_name(format!("{}-cached.png", file_name));
let cache_image = load_context.read_asset_bytes(&cache_path).await;
let font_range = char_range_start..char_range_end;
let char_count = font_range.len() as u32;
let size_x = 64usize;
let size_y = 128usize;
let face = ttf_parser::Face::parse(&font_bytes, 0).unwrap();
let image_height = size_y as u32 * char_count;
let mut image_builder: RgbaImage = image::ImageBuffer::new(size_x as u32, image_height);
let mut yy = 0u32;
let mut glyphs = vec![];
// Build char to glyph mapping..
let mut glyph_to_char: HashMap<ttf_parser::GlyphId, char> =
HashMap::with_capacity(face.number_of_glyphs() as usize);
let mut char_to_glyph: HashMap<char, ttf_parser::GlyphId> =
HashMap::with_capacity(face.number_of_glyphs() as usize);
if let Some(subtable) = face.tables().cmap {
for subtable in subtable.subtables {
subtable.codepoints(|codepoint| {
if let Some(mapping) = subtable.glyph_index(codepoint) {
glyph_to_char
.insert(mapping, unsafe { std::mem::transmute(codepoint) });
char_to_glyph
.insert(unsafe { std::mem::transmute(codepoint) }, mapping);
}
})
}
}
for char_u in font_range {
let c = char::from_u32(char_u).unwrap();
let glyph_id = *char_to_glyph.get(&c).unwrap();
let mut output = FloatRGBBmp::new(size_x, size_y);
let mut builder = ShapeBuilder::default();
let pixel_scale = size_x as f64 / face.units_per_em() as f64;
builder.pixel_scale = pixel_scale;
let _result = face.outline_glyph(glyph_id, &mut builder);
let char_bounds = face
.glyph_bounding_box(glyph_id)
.unwrap_or(ttf_parser::Rect {
x_min: 0,
x_max: size_x as i16,
y_min: 0,
y_max: size_y as i16,
});
let mut shape = builder.build();
shape.inverse_y_axis = true;
// let (left, bottom, right, top) = shape.get_bounds();
let scale = Vector2::new(1.0, 1.0);
let px_range = 2.0;
let range = px_range / scale.x.min(scale.y);
let (translation, plane) =
calculate_plane(&mut shape, pixel_scale as f32, 1.0, px_range as f32, 1.0);
let advance = face.glyph_hor_advance(glyph_id).unwrap_or(0) as f32 / size_x as f32;
let c = *glyph_to_char.get(&glyph_id).unwrap();
glyphs.push(Glyph {
unicode: c,
advance: advance * pixel_scale as f32,
atlas_bounds: Some(Rect {
left: 0.0,
bottom: 0.0 as f32,
right: size_x as f32,
top: size_y as f32,
}),
plane_bounds: Some(plane),
});
// let frame = Vector2::new(size_x as f64, size_y as f64);
// dbg!((left, right, top, bottom));
// left = (left - (size_x as f64 / 8.0)).max(0.0);
// right = (right + (size_x as f64 / 8.0)).min(size_x as f64);
// top = (top + (size_y as f64 / 8.0)).min(size_y as f64);
// bottom = (bottom - (size_y as f64 / 8.0)).max(0.0);
// dbg!((left, right, top, bottom));
// let dims = Vector2::new(right - left, top - bottom);
// let translate = Vector2::new(-left + (frame.x - dims.x), (frame.y - (bottom + dims.y)) - 1.0);
if cache_image.is_err() {
msdf::edge_coloring::simple(&mut shape, 3.0, 0);
msdf::gen::generate_msdf(
&mut output,
&shape,
range,
scale,
translation + Vector2::new(0.0, size_x as f64 * 1.25),
1.11111111111111111,
);
// let left = (translation.x - char_bounds.x_min as f64 * pixel_scale).max(0.0).floor() as u32;
let right =
(translation.x + char_bounds.x_max as f64 * pixel_scale).floor() as u32;
// let top = (translation.y - char_bounds.y_min as f64 * pixel_scale).max(0.0).floor() as u32;
let bottom =
(translation.y + char_bounds.y_max as f64 * pixel_scale).floor() as u32;
for x in 0..(right + 2).min(64) {
for y in 0..bottom + 48 {
// for x in 0..size_x as u32 {
// for y in 0..size_y as u32 {
let pixel = output.get_pixel(x as usize, y as usize);
image_builder.put_pixel(
x,
yy + y,
image::Rgba([
(pixel.r * 255.0) as u8,
(pixel.g * 255.0) as u8,
(pixel.b * 255.0) as u8,
255,
]),
);
}
}
}
// if c == '\"' {
// image_builder.save("test.png").unwrap();
// panic!("");
// }
yy += size_y as u32;
}
let image_bytes = if cache_image.is_err() {
image_builder
.save(asset_io.root_path().join(cache_path))
.unwrap();
image_builder.as_bytes().to_vec()
} else {
let cache_image = cache_image.unwrap();
let image = image::load_from_memory(&cache_image).unwrap();
image.as_bytes().to_vec()
};
let mut sdf = Sdf::default();
sdf.glyphs = glyphs;
sdf.atlas.font_size = size_x as f32;
let mut image = bevy::prelude::Image::new(
Extent3d {
width: size_x as u32,
height: image_height,
depth_or_array_layers: 1,
},
bevy::render::render_resource::TextureDimension::D2,
image_bytes,
TextureFormat::Rgba8UnormSrgb,
);
image.reinterpret_stacked_2d_as_array(char_count);
let image_handle =
load_context.set_labeled_asset("font_image", LoadedAsset::new(image));
let font = KayakFont::new(sdf, ImageType::Array(image_handle));
load_context.set_default_asset(LoadedAsset::new(font));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
&["kttf"]
}
}
fn calculate_plane(
shape: &mut Shape,
geometry_scale: f32,
scale: f32,
range: f32,
miter_limit: f32,
) -> (Vector2, Rect) {
let bounds = shape.get_bounds();
let bounds = Rect {
left: bounds.0 as f32,
bottom: bounds.1 as f32,
right: bounds.2 as f32,
top: bounds.3 as f32,
};
let scale = scale * geometry_scale;
let range = range / geometry_scale;
let (_w, _h, translation_x, translation_y) =
if bounds.left < bounds.right && bounds.bottom < bounds.top {
let mut l = bounds.left as f64;
let mut b = bounds.bottom as f64;
let mut r = bounds.right as f64;
let mut t = bounds.top as f64;
l -= 0.5 * range as f64;
b -= 0.5 * range as f64;
r += 0.5 * range as f64;
t += 0.5 * range as f64;
if miter_limit > 0.0 {
shape.bound_miters(
&mut l,
&mut b,
&mut r,
&mut t,
0.5 * range as f64,
miter_limit as f64,
1,
);
}
let w = scale as f64 * (r - l);
let h = scale as f64 * (t - b);
let box_w = w.ceil() as i32 + 1;
let box_h = h.ceil() as i32 + 1;
(
box_w,
box_h,
-l + 0.5 * (box_w as f64 - w) / scale as f64,
-b + 0.5 * (box_h as f64 - h) / scale as f64,
)
} else {
(0, 0, 0.0, 0.0)
};
// let mut l = 0.0;
// let mut r = 0.0;
// let mut b = 0.0;
// let mut t = 0.0;
// if w > 0 && h > 0 {
// let inv_box_scale = 1.0 / scale as f64;
// l = geometry_scale as f64 * (-translation_x + 0.5 * inv_box_scale);
// b = geometry_scale as f64 * (-translation_y + 0.5 * inv_box_scale);
// r = geometry_scale as f64 * (-translation_x + (w as f64 - 0.5) * inv_box_scale);
// t = geometry_scale as f64 * (-translation_y + (h as f64 - 0.5) * inv_box_scale);
// }
(
Vector2::new(translation_x, translation_y) * geometry_scale as f64,
Rect {
left: 0.0, // l as f32,
bottom: 0.0, // b as f32,
right: 0.0, // r as f32,
top: 24.0 * geometry_scale, // t as f32,
},
)
}
pub(crate) mod loader;
kayak_font/test.png

31.3 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment