use crate::ecs::text::resources::GlyphInfo;
use nalgebra_glm::{vec2, vec4};
use std::collections::HashMap;
const SDF_PADDING: usize = 8;
const SDF_SCALE: f32 = 96.0;
pub const SDF_SPREAD: f32 = 8.0;
pub struct FontAtlas {
pub texture: wgpu::Texture,
pub texture_view: wgpu::TextureView,
pub texture_index: u32,
pub glyphs: HashMap<char, GlyphInfo>,
pub kerning: HashMap<(char, char), f32>,
pub width: u32,
pub height: u32,
pub font_size: f32,
pub sdf_range: f32,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum EdgeColor {
Red,
Green,
Blue,
}
#[derive(Clone)]
enum EdgeSegment {
Line {
start: [f32; 2],
end: [f32; 2],
},
QuadBezier {
start: [f32; 2],
control: [f32; 2],
end: [f32; 2],
},
CubicBezier {
start: [f32; 2],
control1: [f32; 2],
control2: [f32; 2],
end: [f32; 2],
},
}
#[derive(Clone)]
struct Edge {
segment: EdgeSegment,
color: EdgeColor,
}
struct Contour {
edges: Vec<Edge>,
}
struct GlyphShape {
contours: Vec<Contour>,
}
struct ShapeBuilder {
contours: Vec<Contour>,
current_edges: Vec<EdgeSegment>,
current_point: [f32; 2],
contour_start: [f32; 2],
}
impl ShapeBuilder {
fn new() -> Self {
Self {
contours: Vec::new(),
current_edges: Vec::new(),
current_point: [0.0, 0.0],
contour_start: [0.0, 0.0],
}
}
fn into_shape(self) -> GlyphShape {
GlyphShape {
contours: self.contours,
}
}
}
impl ttf_parser::OutlineBuilder for ShapeBuilder {
fn move_to(&mut self, x: f32, y: f32) {
if !self.current_edges.is_empty() {
let edges = std::mem::take(&mut self.current_edges)
.into_iter()
.map(|segment| Edge {
segment,
color: EdgeColor::Red,
})
.collect();
self.contours.push(Contour { edges });
}
self.current_point = [x, y];
self.contour_start = [x, y];
}
fn line_to(&mut self, x: f32, y: f32) {
let start = self.current_point;
let end = [x, y];
if (start[0] - end[0]).abs() > f32::EPSILON || (start[1] - end[1]).abs() > f32::EPSILON {
self.current_edges.push(EdgeSegment::Line { start, end });
}
self.current_point = end;
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
let start = self.current_point;
self.current_edges.push(EdgeSegment::QuadBezier {
start,
control: [x1, y1],
end: [x, y],
});
self.current_point = [x, y];
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
let start = self.current_point;
self.current_edges.push(EdgeSegment::CubicBezier {
start,
control1: [x1, y1],
control2: [x2, y2],
end: [x, y],
});
self.current_point = [x, y];
}
fn close(&mut self) {
let start = self.current_point;
let end = self.contour_start;
if (start[0] - end[0]).abs() > f32::EPSILON || (start[1] - end[1]).abs() > f32::EPSILON {
self.current_edges.push(EdgeSegment::Line { start, end });
}
self.current_point = end;
if !self.current_edges.is_empty() {
let edges = std::mem::take(&mut self.current_edges)
.into_iter()
.map(|segment| Edge {
segment,
color: EdgeColor::Red,
})
.collect();
self.contours.push(Contour { edges });
}
}
}
fn edge_direction_at_start(segment: &EdgeSegment) -> [f32; 2] {
match segment {
EdgeSegment::Line { start, end } => [end[0] - start[0], end[1] - start[1]],
EdgeSegment::QuadBezier {
start,
control,
end,
} => {
let direction = [control[0] - start[0], control[1] - start[1]];
if direction[0].abs() > f32::EPSILON || direction[1].abs() > f32::EPSILON {
direction
} else {
[end[0] - start[0], end[1] - start[1]]
}
}
EdgeSegment::CubicBezier {
start,
control1,
control2,
end,
} => {
let direction = [control1[0] - start[0], control1[1] - start[1]];
if direction[0].abs() > f32::EPSILON || direction[1].abs() > f32::EPSILON {
direction
} else {
let direction = [control2[0] - start[0], control2[1] - start[1]];
if direction[0].abs() > f32::EPSILON || direction[1].abs() > f32::EPSILON {
direction
} else {
[end[0] - start[0], end[1] - start[1]]
}
}
}
}
}
fn edge_direction_at_end(segment: &EdgeSegment) -> [f32; 2] {
match segment {
EdgeSegment::Line { start, end } => [end[0] - start[0], end[1] - start[1]],
EdgeSegment::QuadBezier {
start,
control,
end,
} => {
let direction = [end[0] - control[0], end[1] - control[1]];
if direction[0].abs() > f32::EPSILON || direction[1].abs() > f32::EPSILON {
direction
} else {
[end[0] - start[0], end[1] - start[1]]
}
}
EdgeSegment::CubicBezier {
start,
control1,
control2,
end,
} => {
let direction = [end[0] - control2[0], end[1] - control2[1]];
if direction[0].abs() > f32::EPSILON || direction[1].abs() > f32::EPSILON {
direction
} else {
let direction = [end[0] - control1[0], end[1] - control1[1]];
if direction[0].abs() > f32::EPSILON || direction[1].abs() > f32::EPSILON {
direction
} else {
[end[0] - start[0], end[1] - start[1]]
}
}
}
}
}
fn normalize(v: [f32; 2]) -> [f32; 2] {
let length = (v[0] * v[0] + v[1] * v[1]).sqrt();
if length > f32::EPSILON {
[v[0] / length, v[1] / length]
} else {
[0.0, 0.0]
}
}
fn cross_2d(a: [f32; 2], b: [f32; 2]) -> f32 {
a[0] * b[1] - a[1] * b[0]
}
fn dot_2d(a: [f32; 2], b: [f32; 2]) -> f32 {
a[0] * b[0] + a[1] * b[1]
}
fn color_edges(shape: &mut GlyphShape) {
const CROSS_THRESHOLD: f32 = 0.1411;
let colors = [EdgeColor::Red, EdgeColor::Green, EdgeColor::Blue];
for contour in &mut shape.contours {
if contour.edges.is_empty() {
continue;
}
if contour.edges.len() == 1 {
contour.edges[0].color = EdgeColor::Red;
let segment_clone = contour.edges[0].segment.clone();
contour.edges.push(Edge {
segment: segment_clone.clone(),
color: EdgeColor::Green,
});
contour.edges.push(Edge {
segment: segment_clone,
color: EdgeColor::Blue,
});
continue;
}
let edge_count = contour.edges.len();
let mut corners = Vec::new();
for edge_index in 0..edge_count {
let prev_index = if edge_index == 0 {
edge_count - 1
} else {
edge_index - 1
};
let prev_dir = normalize(edge_direction_at_end(&contour.edges[prev_index].segment));
let curr_dir = normalize(edge_direction_at_start(&contour.edges[edge_index].segment));
let cross = cross_2d(prev_dir, curr_dir);
let dot = dot_2d(prev_dir, curr_dir);
if dot <= 0.0 || cross.abs() > CROSS_THRESHOLD {
corners.push(edge_index);
}
}
if corners.is_empty() {
for (color_index, edge) in contour.edges.iter_mut().enumerate() {
edge.color = colors[color_index % 3];
}
} else {
let mut color_index = 0;
let first_corner = corners[0];
for edge_index in 0..edge_count {
let actual_index = (first_corner + edge_index) % edge_count;
if corners.contains(&actual_index) && edge_index > 0 {
color_index += 1;
}
contour.edges[actual_index].color = colors[color_index % 3];
}
if corners.len() >= 2 {
let last_color = contour.edges[corners[corners.len() - 1]].color;
let first_color = contour.edges[corners[0]].color;
if last_color == first_color {
let last_corner = corners[corners.len() - 1];
let replacement = match last_color {
EdgeColor::Red => EdgeColor::Blue,
EdgeColor::Green => EdgeColor::Red,
EdgeColor::Blue => EdgeColor::Green,
};
for edge_index in last_corner..edge_count {
contour.edges[edge_index].color = replacement;
}
for edge_index in 0..corners[0] {
contour.edges[edge_index].color = replacement;
}
}
}
}
}
}
fn signed_distance_to_line(point: [f32; 2], start: [f32; 2], end: [f32; 2]) -> f32 {
let direction = [end[0] - start[0], end[1] - start[1]];
let to_point = [point[0] - start[0], point[1] - start[1]];
let length_sq = direction[0] * direction[0] + direction[1] * direction[1];
if length_sq < f32::EPSILON {
return (to_point[0] * to_point[0] + to_point[1] * to_point[1]).sqrt();
}
let parameter = (to_point[0] * direction[0] + to_point[1] * direction[1]) / length_sq;
let clamped = parameter.clamp(0.0, 1.0);
let closest = [
start[0] + clamped * direction[0],
start[1] + clamped * direction[1],
];
let diff = [point[0] - closest[0], point[1] - closest[1]];
let dist = (diff[0] * diff[0] + diff[1] * diff[1]).sqrt();
let sign = cross_2d(direction, to_point);
if sign < 0.0 { -dist } else { dist }
}
fn eval_quad_bezier(start: [f32; 2], control: [f32; 2], end: [f32; 2], parameter: f32) -> [f32; 2] {
let one_minus_t = 1.0 - parameter;
[
one_minus_t * one_minus_t * start[0]
+ 2.0 * one_minus_t * parameter * control[0]
+ parameter * parameter * end[0],
one_minus_t * one_minus_t * start[1]
+ 2.0 * one_minus_t * parameter * control[1]
+ parameter * parameter * end[1],
]
}
fn eval_quad_bezier_derivative(
start: [f32; 2],
control: [f32; 2],
end: [f32; 2],
parameter: f32,
) -> [f32; 2] {
[
2.0 * (1.0 - parameter) * (control[0] - start[0]) + 2.0 * parameter * (end[0] - control[0]),
2.0 * (1.0 - parameter) * (control[1] - start[1]) + 2.0 * parameter * (end[1] - control[1]),
]
}
fn signed_distance_to_quad(
point: [f32; 2],
start: [f32; 2],
control: [f32; 2],
end: [f32; 2],
) -> f32 {
let steps = 8;
let mut best_parameter = 0.0f32;
let mut best_dist_sq = f32::MAX;
for step in 0..=steps {
let parameter = step as f32 / steps as f32;
let curve_point = eval_quad_bezier(start, control, end, parameter);
let diff = [point[0] - curve_point[0], point[1] - curve_point[1]];
let dist_sq = diff[0] * diff[0] + diff[1] * diff[1];
if dist_sq < best_dist_sq {
best_dist_sq = dist_sq;
best_parameter = parameter;
}
}
for _ in 0..4 {
let curve_point = eval_quad_bezier(start, control, end, best_parameter);
let derivative = eval_quad_bezier_derivative(start, control, end, best_parameter);
let diff = [curve_point[0] - point[0], curve_point[1] - point[1]];
let numerator = diff[0] * derivative[0] + diff[1] * derivative[1];
let second_deriv = [
2.0 * (end[0] - 2.0 * control[0] + start[0]),
2.0 * (end[1] - 2.0 * control[1] + start[1]),
];
let denominator = derivative[0] * derivative[0]
+ derivative[1] * derivative[1]
+ diff[0] * second_deriv[0]
+ diff[1] * second_deriv[1];
if denominator.abs() < f32::EPSILON {
break;
}
best_parameter -= numerator / denominator;
best_parameter = best_parameter.clamp(0.0, 1.0);
}
let dist_at_start = {
let diff = [point[0] - start[0], point[1] - start[1]];
diff[0] * diff[0] + diff[1] * diff[1]
};
let dist_at_end = {
let diff = [point[0] - end[0], point[1] - end[1]];
diff[0] * diff[0] + diff[1] * diff[1]
};
let dist_at_best = {
let curve_point = eval_quad_bezier(start, control, end, best_parameter);
let diff = [point[0] - curve_point[0], point[1] - curve_point[1]];
diff[0] * diff[0] + diff[1] * diff[1]
};
if dist_at_start < dist_at_best && dist_at_start < dist_at_end {
best_parameter = 0.0;
} else if dist_at_end < dist_at_best {
best_parameter = 1.0;
}
let closest = eval_quad_bezier(start, control, end, best_parameter);
let derivative = eval_quad_bezier_derivative(start, control, end, best_parameter);
let diff = [point[0] - closest[0], point[1] - closest[1]];
let dist = (diff[0] * diff[0] + diff[1] * diff[1]).sqrt();
let sign = cross_2d(derivative, diff);
if sign < 0.0 { -dist } else { dist }
}
fn eval_cubic_bezier(
start: [f32; 2],
control1: [f32; 2],
control2: [f32; 2],
end: [f32; 2],
parameter: f32,
) -> [f32; 2] {
let one_minus_t = 1.0 - parameter;
let one_minus_t_sq = one_minus_t * one_minus_t;
let parameter_sq = parameter * parameter;
[
one_minus_t_sq * one_minus_t * start[0]
+ 3.0 * one_minus_t_sq * parameter * control1[0]
+ 3.0 * one_minus_t * parameter_sq * control2[0]
+ parameter_sq * parameter * end[0],
one_minus_t_sq * one_minus_t * start[1]
+ 3.0 * one_minus_t_sq * parameter * control1[1]
+ 3.0 * one_minus_t * parameter_sq * control2[1]
+ parameter_sq * parameter * end[1],
]
}
fn eval_cubic_bezier_derivative(
start: [f32; 2],
control1: [f32; 2],
control2: [f32; 2],
end: [f32; 2],
parameter: f32,
) -> [f32; 2] {
let one_minus_t = 1.0 - parameter;
[
3.0 * one_minus_t * one_minus_t * (control1[0] - start[0])
+ 6.0 * one_minus_t * parameter * (control2[0] - control1[0])
+ 3.0 * parameter * parameter * (end[0] - control2[0]),
3.0 * one_minus_t * one_minus_t * (control1[1] - start[1])
+ 6.0 * one_minus_t * parameter * (control2[1] - control1[1])
+ 3.0 * parameter * parameter * (end[1] - control2[1]),
]
}
fn signed_distance_to_cubic(
point: [f32; 2],
start: [f32; 2],
control1: [f32; 2],
control2: [f32; 2],
end: [f32; 2],
) -> f32 {
let steps = 12;
let mut best_parameter = 0.0f32;
let mut best_dist_sq = f32::MAX;
for step in 0..=steps {
let parameter = step as f32 / steps as f32;
let curve_point = eval_cubic_bezier(start, control1, control2, end, parameter);
let diff = [point[0] - curve_point[0], point[1] - curve_point[1]];
let dist_sq = diff[0] * diff[0] + diff[1] * diff[1];
if dist_sq < best_dist_sq {
best_dist_sq = dist_sq;
best_parameter = parameter;
}
}
for _ in 0..5 {
let curve_point = eval_cubic_bezier(start, control1, control2, end, best_parameter);
let derivative =
eval_cubic_bezier_derivative(start, control1, control2, end, best_parameter);
let diff = [curve_point[0] - point[0], curve_point[1] - point[1]];
let numerator = diff[0] * derivative[0] + diff[1] * derivative[1];
let one_minus_t = 1.0 - best_parameter;
let second_deriv = [
6.0 * one_minus_t * (control2[0] - 2.0 * control1[0] + start[0])
+ 6.0 * best_parameter * (end[0] - 2.0 * control2[0] + control1[0]),
6.0 * one_minus_t * (control2[1] - 2.0 * control1[1] + start[1])
+ 6.0 * best_parameter * (end[1] - 2.0 * control2[1] + control1[1]),
];
let denominator = derivative[0] * derivative[0]
+ derivative[1] * derivative[1]
+ diff[0] * second_deriv[0]
+ diff[1] * second_deriv[1];
if denominator.abs() < f32::EPSILON {
break;
}
best_parameter -= numerator / denominator;
best_parameter = best_parameter.clamp(0.0, 1.0);
}
let dist_at_start = {
let diff = [point[0] - start[0], point[1] - start[1]];
diff[0] * diff[0] + diff[1] * diff[1]
};
let dist_at_end = {
let diff = [point[0] - end[0], point[1] - end[1]];
diff[0] * diff[0] + diff[1] * diff[1]
};
let dist_at_best = {
let curve_point = eval_cubic_bezier(start, control1, control2, end, best_parameter);
let diff = [point[0] - curve_point[0], point[1] - curve_point[1]];
diff[0] * diff[0] + diff[1] * diff[1]
};
if dist_at_start < dist_at_best && dist_at_start < dist_at_end {
best_parameter = 0.0;
} else if dist_at_end < dist_at_best {
best_parameter = 1.0;
}
let closest = eval_cubic_bezier(start, control1, control2, end, best_parameter);
let derivative = eval_cubic_bezier_derivative(start, control1, control2, end, best_parameter);
let diff = [point[0] - closest[0], point[1] - closest[1]];
let dist = (diff[0] * diff[0] + diff[1] * diff[1]).sqrt();
let sign = cross_2d(derivative, diff);
if sign < 0.0 { -dist } else { dist }
}
fn signed_distance_to_edge(point: [f32; 2], segment: &EdgeSegment) -> f32 {
match segment {
EdgeSegment::Line { start, end } => signed_distance_to_line(point, *start, *end),
EdgeSegment::QuadBezier {
start,
control,
end,
} => signed_distance_to_quad(point, *start, *control, *end),
EdgeSegment::CubicBezier {
start,
control1,
control2,
end,
} => signed_distance_to_cubic(point, *start, *control1, *control2, *end),
}
}
fn line_winding(point: [f32; 2], start: [f32; 2], end: [f32; 2]) -> i32 {
if (start[1] <= point[1]) != (end[1] <= point[1]) {
let parameter = (point[1] - start[1]) / (end[1] - start[1]);
let x_at_crossing = start[0] + parameter * (end[0] - start[0]);
if x_at_crossing > point[0] {
if end[1] > start[1] {
return 1;
} else {
return -1;
}
}
}
0
}
fn quad_winding(point: [f32; 2], start: [f32; 2], control: [f32; 2], end: [f32; 2]) -> i32 {
let a = start[1] - 2.0 * control[1] + end[1];
let b = 2.0 * (control[1] - start[1]);
let c = start[1] - point[1];
let mut winding = 0i32;
if a.abs() < f32::EPSILON {
if b.abs() < f32::EPSILON {
return 0;
}
let parameter = -c / b;
if parameter > 0.0 && parameter <= 1.0 {
let x = eval_quad_bezier(start, control, end, parameter)[0];
if x > point[0] {
let dy = b + 2.0 * a * parameter;
if dy > 0.0 {
winding += 1;
} else if dy < 0.0 {
winding -= 1;
}
}
}
return winding;
}
let discriminant = b * b - 4.0 * a * c;
if discriminant < 0.0 {
return 0;
}
let sqrt_disc = discriminant.sqrt();
let roots = [(-b - sqrt_disc) / (2.0 * a), (-b + sqrt_disc) / (2.0 * a)];
for parameter in roots {
if parameter > 0.0 && parameter <= 1.0 {
let x = eval_quad_bezier(start, control, end, parameter)[0];
if x > point[0] {
let dy = b + 2.0 * a * parameter;
if dy > 0.0 {
winding += 1;
} else if dy < 0.0 {
winding -= 1;
}
}
}
}
winding
}
fn cubic_winding(
point: [f32; 2],
start: [f32; 2],
control1: [f32; 2],
control2: [f32; 2],
end: [f32; 2],
) -> i32 {
let a = -start[1] + 3.0 * control1[1] - 3.0 * control2[1] + end[1];
let b = 3.0 * (start[1] - 2.0 * control1[1] + control2[1]);
let c = 3.0 * (control1[1] - start[1]);
let d = start[1] - point[1];
let (roots, root_count) = solve_cubic(a, b, c, d);
let mut winding = 0i32;
for ¶meter in &roots[..root_count] {
if parameter > 0.0 && parameter <= 1.0 {
let curve_point = eval_cubic_bezier(start, control1, control2, end, parameter);
if curve_point[0] > point[0] {
let dy = eval_cubic_bezier_derivative(start, control1, control2, end, parameter)[1];
if dy > 0.0 {
winding += 1;
} else if dy < 0.0 {
winding -= 1;
}
}
}
}
winding
}
fn solve_cubic(a: f32, b: f32, c: f32, d: f32) -> ([f32; 3], usize) {
if a.abs() < f32::EPSILON {
if b.abs() < f32::EPSILON {
if c.abs() < f32::EPSILON {
return ([0.0; 3], 0);
}
return ([-d / c, 0.0, 0.0], 1);
}
let disc = c * c - 4.0 * b * d;
if disc < 0.0 {
return ([0.0; 3], 0);
}
let sqrt_disc = disc.sqrt();
return (
[
(-c - sqrt_disc) / (2.0 * b),
(-c + sqrt_disc) / (2.0 * b),
0.0,
],
2,
);
}
let ba = b / a;
let ca = c / a;
let da = d / a;
let p = (3.0 * ca - ba * ba) / 3.0;
let q = (2.0 * ba * ba * ba - 9.0 * ba * ca + 27.0 * da) / 27.0;
let discriminant = q * q / 4.0 + p * p * p / 27.0;
let offset = -ba / 3.0;
if discriminant > f32::EPSILON {
let sqrt_disc = discriminant.sqrt();
let u = (-q / 2.0 + sqrt_disc).cbrt();
let v = (-q / 2.0 - sqrt_disc).cbrt();
([u + v + offset, 0.0, 0.0], 1)
} else if discriminant.abs() <= f32::EPSILON {
let u = if q.abs() < f32::EPSILON {
0.0
} else {
(-q / 2.0).cbrt()
};
([2.0 * u + offset, -u + offset, 0.0], 2)
} else {
let r = (-p * p * p / 27.0).sqrt();
let phi = (-q / (2.0 * r)).clamp(-1.0, 1.0).acos();
let cube_root_r = r.cbrt();
(
[
2.0 * cube_root_r * (phi / 3.0).cos() + offset,
2.0 * cube_root_r * ((phi + 2.0 * std::f32::consts::PI) / 3.0).cos() + offset,
2.0 * cube_root_r * ((phi + 4.0 * std::f32::consts::PI) / 3.0).cos() + offset,
],
3,
)
}
}
fn edge_winding(point: [f32; 2], segment: &EdgeSegment) -> i32 {
match segment {
EdgeSegment::Line { start, end } => line_winding(point, *start, *end),
EdgeSegment::QuadBezier {
start,
control,
end,
} => quad_winding(point, *start, *control, *end),
EdgeSegment::CubicBezier {
start,
control1,
control2,
end,
} => cubic_winding(point, *start, *control1, *control2, *end),
}
}
fn compute_winding_number(point: [f32; 2], shape: &GlyphShape) -> i32 {
let mut winding = 0i32;
for contour in &shape.contours {
for edge in &contour.edges {
winding += edge_winding(point, &edge.segment);
}
}
winding
}
fn compute_msdf_pixel(point: [f32; 2], shape: &GlyphShape) -> [f32; 3] {
let winding = compute_winding_number(point, shape);
let sign = if winding != 0 { 1.0f32 } else { -1.0f32 };
let mut min_red = f32::MAX;
let mut min_green = f32::MAX;
let mut min_blue = f32::MAX;
for contour in &shape.contours {
for edge in &contour.edges {
let abs_dist = signed_distance_to_edge(point, &edge.segment).abs();
match edge.color {
EdgeColor::Red => {
if abs_dist < min_red {
min_red = abs_dist;
}
}
EdgeColor::Green => {
if abs_dist < min_green {
min_green = abs_dist;
}
}
EdgeColor::Blue => {
if abs_dist < min_blue {
min_blue = abs_dist;
}
}
}
}
}
[sign * min_red, sign * min_green, sign * min_blue]
}
fn generate_msdf_bitmap(
shape: &GlyphShape,
width: usize,
height: usize,
translate_x: f32,
translate_y: f32,
scale: f32,
spread: f32,
) -> Vec<[u8; 4]> {
let mut pixels = vec![[0u8; 4]; width * height];
for pixel_y in 0..height {
for pixel_x in 0..width {
let shape_x = (pixel_x as f32 - translate_x) / scale;
let shape_y = ((height - 1 - pixel_y) as f32 - translate_y) / scale;
let distances = compute_msdf_pixel([shape_x, shape_y], shape);
let pixel_index = pixel_y * width + pixel_x;
pixels[pixel_index] = [
((0.5 + distances[0] / spread).clamp(0.0, 1.0) * 255.0) as u8,
((0.5 + distances[1] / spread).clamp(0.0, 1.0) * 255.0) as u8,
((0.5 + distances[2] / spread).clamp(0.0, 1.0) * 255.0) as u8,
255,
];
}
}
pixels
}
pub struct AtlasPacker {
width: u32,
height: u32,
current_x: u32,
current_y: u32,
row_height: u32,
}
impl AtlasPacker {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
current_x: 0,
current_y: 0,
row_height: 0,
}
}
pub fn pack(&mut self, glyph_width: u32, glyph_height: u32) -> Option<(u32, u32)> {
if self.current_x + glyph_width > self.width {
self.current_x = 0;
self.current_y += self.row_height;
self.row_height = 0;
}
if self.current_y + glyph_height > self.height {
return None;
}
let position = (self.current_x, self.current_y);
self.current_x += glyph_width;
self.row_height = self.row_height.max(glyph_height);
Some(position)
}
}
pub fn generate_font_atlas_msdf(
device: &wgpu::Device,
queue: &wgpu::Queue,
font_data: &[u8],
font_size: f32,
) -> Result<FontAtlas, Box<dyn std::error::Error>> {
let face = ttf_parser::Face::parse(font_data, 0)?;
let units_per_em = face.units_per_em() as f32;
let scale = SDF_SCALE / units_per_em;
let spread = SDF_SPREAD / scale;
let atlas_width: u32 = 2048;
let atlas_height: u32 = 2048;
let mut atlas_data = vec![[0u8; 4]; (atlas_width * atlas_height) as usize];
let mut packer = AtlasPacker::new(atlas_width, atlas_height);
let mut glyphs = HashMap::new();
let chars_to_render: Vec<char> = (32u8..127u8).map(|c| c as char).collect();
for &ch in &chars_to_render {
if let Some(glyph_id) = face.glyph_index(ch) {
let mut builder = ShapeBuilder::new();
let bbox = face.outline_glyph(glyph_id, &mut builder);
if let Some(bbox) = bbox {
let mut shape = builder.into_shape();
if shape.contours.is_empty() {
if ch == ' ' {
let advance = face.glyph_hor_advance(glyph_id).unwrap_or(0) as f32;
let scale_factor = font_size / units_per_em;
glyphs.insert(
ch,
GlyphInfo {
uv_rect: vec4(0.0, 0.0, 0.0, 0.0),
size: vec2(0.0, 0.0),
bearing: vec2(0.0, 0.0),
advance: advance * scale_factor,
},
);
}
continue;
}
color_edges(&mut shape);
let glyph_width = (bbox.x_max - bbox.x_min) as f32;
let glyph_height = (bbox.y_max - bbox.y_min) as f32;
let pixel_width = (glyph_width * scale).ceil() as usize + SDF_PADDING * 2;
let pixel_height = (glyph_height * scale).ceil() as usize + SDF_PADDING * 2;
if let Some((atlas_x, atlas_y)) =
packer.pack(pixel_width as u32, pixel_height as u32)
{
let translate_x = SDF_PADDING as f32 - bbox.x_min as f32 * scale;
let translate_y = SDF_PADDING as f32 - bbox.y_min as f32 * scale;
let msdf_bitmap = generate_msdf_bitmap(
&shape,
pixel_width,
pixel_height,
translate_x,
translate_y,
scale,
spread,
);
for src_y in 0..pixel_height {
for src_x in 0..pixel_width {
let src_idx = src_y * pixel_width + src_x;
let dst_x = atlas_x as usize + src_x;
let dst_y = atlas_y as usize + src_y;
let dst_idx = dst_y * atlas_width as usize + dst_x;
if dst_idx < atlas_data.len() {
atlas_data[dst_idx] = msdf_bitmap[src_idx];
}
}
}
let scale_factor = font_size / units_per_em;
let advance = face.glyph_hor_advance(glyph_id).unwrap_or(0) as f32;
glyphs.insert(
ch,
GlyphInfo {
uv_rect: vec4(
atlas_x as f32 / atlas_width as f32,
atlas_y as f32 / atlas_height as f32,
pixel_width as f32 / atlas_width as f32,
pixel_height as f32 / atlas_height as f32,
),
size: vec2(
pixel_width as f32 * (font_size / SDF_SCALE),
pixel_height as f32 * (font_size / SDF_SCALE),
),
bearing: vec2(
(bbox.x_min as f32 * scale_factor)
- (SDF_PADDING as f32 * font_size / SDF_SCALE),
(bbox.y_max as f32 * scale_factor)
+ (SDF_PADDING as f32 * font_size / SDF_SCALE),
),
advance: advance * scale_factor,
},
);
}
} else if ch == ' ' {
let advance = face.glyph_hor_advance(glyph_id).unwrap_or(0) as f32;
let scale_factor = font_size / units_per_em;
glyphs.insert(
ch,
GlyphInfo {
uv_rect: vec4(0.0, 0.0, 0.0, 0.0),
size: vec2(0.0, 0.0),
bearing: vec2(0.0, 0.0),
advance: advance * scale_factor,
},
);
}
}
}
let mut kerning = HashMap::new();
let scale_factor = font_size / units_per_em;
if let Some(kern_table) = face.tables().kern {
for &left_char in &chars_to_render {
for &right_char in &chars_to_render {
if let Some(left_id) = face.glyph_index(left_char)
&& let Some(right_id) = face.glyph_index(right_char)
{
let kern_value: i16 = kern_table
.subtables
.into_iter()
.filter(|subtable| subtable.horizontal && !subtable.variable)
.find_map(|subtable| subtable.glyphs_kerning(left_id, right_id))
.unwrap_or(0);
if kern_value != 0 {
kerning.insert((left_char, right_char), kern_value as f32 * scale_factor);
}
}
}
}
}
let flat_data: Vec<u8> = atlas_data
.iter()
.flat_map(|pixel| pixel.iter().copied())
.collect();
let size = wgpu::Extent3d {
width: atlas_width,
height: atlas_height,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Font Atlas MSDF"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&flat_data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(atlas_width * 4),
rows_per_image: Some(atlas_height),
},
size,
);
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Ok(FontAtlas {
texture,
texture_view,
texture_index: 0,
glyphs,
kerning,
width: atlas_width,
height: atlas_height,
font_size,
sdf_range: SDF_SPREAD,
})
}
pub fn generate_font_atlas_bitmap(
device: &wgpu::Device,
queue: &wgpu::Queue,
font_data: &[u8],
font_size: f32,
scale_factor: f32,
subpixel: bool,
) -> Result<FontAtlas, Box<dyn std::error::Error>> {
use swash::FontRef;
use swash::scale::{Render, ScaleContext, Source};
use swash::zeno::Format;
let font_ref = FontRef::from_index(font_data, 0).ok_or("Failed to parse font")?;
let mut context = ScaleContext::new();
let charmap = font_ref.charmap();
let glyph_metrics = font_ref.glyph_metrics(&[]).scale(font_size * scale_factor);
let raster_size = font_size * scale_factor;
let base_dim = if raster_size <= 24.0 { 512 } else { 1024 };
let atlas_dim = ((base_dim as f32 * scale_factor).ceil() as u32).min(4096);
let atlas_width: u32 = atlas_dim;
let atlas_height: u32 = atlas_dim;
let mut atlas_data = vec![0u8; (atlas_width * atlas_height * 4) as usize];
let mut packer = AtlasPacker::new(atlas_width, atlas_height);
let mut glyphs = HashMap::new();
let chars_to_render: Vec<char> = (32u8..127u8).map(|character| character as char).collect();
let format = if subpixel {
Format::Subpixel
} else {
Format::Alpha
};
let mut scaler = context
.builder(font_ref)
.size(raster_size)
.hint(true)
.build();
for &character in &chars_to_render {
let glyph_id = charmap.map(character);
let advance = glyph_metrics.advance_width(glyph_id);
let image = Render::new(&[Source::ColorOutline(0), Source::Outline])
.format(format)
.render(&mut scaler, glyph_id);
let Some(image) = image else {
glyphs.insert(
character,
GlyphInfo {
uv_rect: vec4(0.0, 0.0, 0.0, 0.0),
size: vec2(0.0, 0.0),
bearing: vec2(0.0, 0.0),
advance,
},
);
continue;
};
let glyph_width = image.placement.width;
let glyph_height = image.placement.height;
if glyph_width == 0 || glyph_height == 0 {
glyphs.insert(
character,
GlyphInfo {
uv_rect: vec4(0.0, 0.0, 0.0, 0.0),
size: vec2(0.0, 0.0),
bearing: vec2(0.0, 0.0),
advance,
},
);
continue;
}
let Some((pack_x, pack_y)) = packer.pack(glyph_width + 1, glyph_height + 1) else {
continue;
};
for row in 0..glyph_height as usize {
for col in 0..glyph_width as usize {
let destination_x = pack_x as usize + col;
let destination_y = pack_y as usize + row;
let destination_index = (destination_y * atlas_width as usize + destination_x) * 4;
if subpixel {
let source_index = (row * glyph_width as usize + col) * 4;
let red = image.data[source_index];
let green = image.data[source_index + 1];
let blue = image.data[source_index + 2];
atlas_data[destination_index] = red;
atlas_data[destination_index + 1] = green;
atlas_data[destination_index + 2] = blue;
atlas_data[destination_index + 3] = red.max(green).max(blue);
} else {
let source_index = row * glyph_width as usize + col;
let coverage = image.data[source_index];
atlas_data[destination_index] = 255;
atlas_data[destination_index + 1] = 255;
atlas_data[destination_index + 2] = 255;
atlas_data[destination_index + 3] = coverage;
}
}
}
glyphs.insert(
character,
GlyphInfo {
uv_rect: vec4(
pack_x as f32 / atlas_width as f32,
pack_y as f32 / atlas_height as f32,
glyph_width as f32 / atlas_width as f32,
glyph_height as f32 / atlas_height as f32,
),
size: vec2(glyph_width as f32, glyph_height as f32),
bearing: vec2(image.placement.left as f32, image.placement.top as f32),
advance,
},
);
}
let face = ttf_parser::Face::parse(font_data, 0)?;
let units_per_em = face.units_per_em() as f32;
let kern_scale = raster_size / units_per_em;
let mut kerning = HashMap::new();
if let Some(kern_table) = face.tables().kern {
for &left_char in &chars_to_render {
for &right_char in &chars_to_render {
if let Some(left_id) = face.glyph_index(left_char)
&& let Some(right_id) = face.glyph_index(right_char)
{
let kern_value: i16 = kern_table
.subtables
.into_iter()
.filter(|subtable| subtable.horizontal && !subtable.variable)
.find_map(|subtable| subtable.glyphs_kerning(left_id, right_id))
.unwrap_or(0);
if kern_value != 0 {
kerning.insert((left_char, right_char), kern_value as f32 * kern_scale);
}
}
}
}
}
let size = wgpu::Extent3d {
width: atlas_width,
height: atlas_height,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Font Atlas Bitmap"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&atlas_data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(atlas_width * 4),
rows_per_image: Some(atlas_height),
},
size,
);
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Ok(FontAtlas {
texture,
texture_view,
texture_index: 0,
glyphs,
kerning,
width: atlas_width,
height: atlas_height,
font_size: raster_size,
sdf_range: 0.0,
})
}
pub const DEFAULT_FONT_DATA: &[u8] = include_bytes!("fonts/Roboto-Regular.ttf");
pub const DEFAULT_MONO_FONT_DATA: &[u8] = include_bytes!("fonts/JetBrainsMono-Regular.ttf");
pub const BITMAP_ROBOTO_RANGE: std::ops::Range<usize> = 0..2;
pub const BITMAP_MONO_RANGE: std::ops::Range<usize> = 2..4;