use crate::ecs::text::components::{
TextAlignment, TextMesh, TextProperties, TextVertex, VerticalAlignment,
};
use crate::ecs::text::resources::FontAtlasData;
use nalgebra_glm::vec2;
struct LineMeshParams<'a> {
line: &'a str,
atlas: &'a FontAtlasData,
x_offset: f32,
y_offset: f32,
scale: f32,
letter_spacing: f32,
monospace_width: Option<f32>,
vertices: &'a mut Vec<TextVertex>,
indices: &'a mut Vec<u32>,
character_index: &'a mut u32,
}
pub fn generate_text_mesh(
text: &str,
atlas: &FontAtlasData,
properties: &TextProperties,
) -> TextMesh {
let mut vertices = Vec::new();
let mut indices = Vec::new();
const PIXELS_TO_WORLD: f32 = 0.01;
let scale = properties.font_size / atlas.font_size * PIXELS_TO_WORLD;
let lines = layout_text(text);
let line_height = properties.font_size * properties.line_height * PIXELS_TO_WORLD;
let total_height = lines.len() as f32 * line_height;
let max_width = lines
.iter()
.map(|line| {
calculate_line_width(
line,
atlas,
scale,
properties.letter_spacing,
properties.monospace_width,
)
})
.fold(0.0f32, |a, b| a.max(b));
let mut y_offset = match properties.vertical_alignment {
VerticalAlignment::Top => -properties.font_size * 0.8 * PIXELS_TO_WORLD,
VerticalAlignment::Middle => -total_height * 0.5,
VerticalAlignment::Bottom => -total_height,
VerticalAlignment::Baseline => -properties.font_size * 0.8 * PIXELS_TO_WORLD,
};
let mut character_index = 0u32;
let use_anchor = properties.anchor_character.is_some();
for line in &lines {
let x_offset = if use_anchor {
0.0
} else {
let line_width = calculate_line_width(
line,
atlas,
scale,
properties.letter_spacing,
properties.monospace_width,
);
match properties.alignment {
TextAlignment::Left => 0.0,
TextAlignment::Center => -line_width * 0.5,
TextAlignment::Right => -line_width,
}
};
generate_line_mesh(LineMeshParams {
line,
atlas,
x_offset,
y_offset,
scale,
letter_spacing: properties.letter_spacing,
monospace_width: properties.monospace_width,
vertices: &mut vertices,
indices: &mut indices,
character_index: &mut character_index,
});
y_offset -= line_height;
}
if let Some(anchor_index) = properties.anchor_character {
let anchor_offset = calculate_anchor_offset(
text,
anchor_index,
atlas,
scale,
properties.letter_spacing,
properties.monospace_width,
);
for vertex in &mut vertices {
vertex.position.x -= anchor_offset;
}
}
TextMesh {
vertices,
indices,
bounds: vec2(max_width, total_height),
}
}
fn calculate_anchor_offset(
text: &str,
anchor_index: usize,
atlas: &FontAtlasData,
scale: f32,
letter_spacing: f32,
monospace_width: Option<f32>,
) -> f32 {
const PIXELS_TO_WORLD: f32 = 0.01;
let fixed_advance = monospace_width.map(|w| w * PIXELS_TO_WORLD);
let mut current_index = 0usize;
let mut cursor_x = 0.0f32;
let mut prev_char: Option<char> = None;
for character in text.chars() {
if character == '\n' {
prev_char = None;
continue;
}
if current_index == anchor_index {
if let Some(glyph) = atlas.glyphs.get(&character) {
if fixed_advance.is_none()
&& let Some(previous) = prev_char
&& let Some(&kern) = atlas.kerning.get(&(previous, character))
{
cursor_x += kern * scale;
}
let char_center_offset = if let Some(advance) = fixed_advance {
(advance - glyph.size.x * scale) * 0.5
} else {
0.0
};
let char_left = cursor_x + glyph.bearing.x * scale + char_center_offset;
let char_width = glyph.size.x * scale;
return char_left + char_width * 0.5;
}
return cursor_x;
}
if let Some(glyph) = atlas.glyphs.get(&character) {
if fixed_advance.is_none()
&& let Some(previous) = prev_char
&& let Some(&kern) = atlas.kerning.get(&(previous, character))
{
cursor_x += kern * scale;
}
let advance =
fixed_advance.unwrap_or(glyph.advance * scale + letter_spacing * PIXELS_TO_WORLD);
cursor_x += advance;
}
prev_char = Some(character);
current_index += 1;
}
cursor_x
}
fn layout_text(text: &str) -> Vec<String> {
text.lines().map(|s| s.to_string()).collect()
}
fn calculate_line_width(
line: &str,
atlas: &FontAtlasData,
scale: f32,
letter_spacing: f32,
monospace_width: Option<f32>,
) -> f32 {
const PIXELS_TO_WORLD: f32 = 0.01;
let mut width = 0.0;
let char_count = line.chars().count();
if let Some(fixed_width) = monospace_width {
let fixed_advance = fixed_width * PIXELS_TO_WORLD;
return char_count as f32 * fixed_advance;
}
let mut prev_char: Option<char> = None;
for (index, character) in line.chars().enumerate() {
if let Some(glyph) = atlas.glyphs.get(&character) {
if let Some(previous) = prev_char
&& let Some(&kern) = atlas.kerning.get(&(previous, character))
{
width += kern * scale;
}
width += glyph.advance * scale;
if index < char_count - 1 {
width += letter_spacing * PIXELS_TO_WORLD;
}
}
prev_char = Some(character);
}
width
}
fn generate_line_mesh(params: LineMeshParams) {
const PIXELS_TO_WORLD: f32 = 0.01;
let mut cursor_x = params.x_offset;
let fixed_advance = params.monospace_width.map(|w| w * PIXELS_TO_WORLD);
let mut prev_char: Option<char> = None;
for character in params.line.chars() {
if let Some(glyph) = params.atlas.glyphs.get(&character) {
if fixed_advance.is_none()
&& let Some(previous) = prev_char
&& let Some(&kern) = params.atlas.kerning.get(&(previous, character))
{
cursor_x += kern * params.scale;
}
let vertex_offset = params.vertices.len() as u32;
let char_center_offset = if let Some(advance) = fixed_advance {
(advance - glyph.size.x * params.scale) * 0.5
} else {
0.0
};
let x = cursor_x + glyph.bearing.x * params.scale + char_center_offset;
let w = glyph.size.x * params.scale;
let h = glyph.size.y * params.scale;
let top = params.y_offset + glyph.bearing.y * params.scale;
let bottom = top - h;
let current_char_index = *params.character_index;
params.vertices.push(TextVertex {
position: nalgebra_glm::vec3(x, top, 0.0),
tex_coords: nalgebra_glm::vec2(glyph.uv_rect.x, glyph.uv_rect.y),
character_index: current_char_index,
_padding: 0,
});
params.vertices.push(TextVertex {
position: nalgebra_glm::vec3(x + w, top, 0.0),
tex_coords: nalgebra_glm::vec2(glyph.uv_rect.x + glyph.uv_rect.z, glyph.uv_rect.y),
character_index: current_char_index,
_padding: 0,
});
params.vertices.push(TextVertex {
position: nalgebra_glm::vec3(x + w, bottom, 0.0),
tex_coords: nalgebra_glm::vec2(
glyph.uv_rect.x + glyph.uv_rect.z,
glyph.uv_rect.y + glyph.uv_rect.w,
),
character_index: current_char_index,
_padding: 0,
});
params.vertices.push(TextVertex {
position: nalgebra_glm::vec3(x, bottom, 0.0),
tex_coords: nalgebra_glm::vec2(glyph.uv_rect.x, glyph.uv_rect.y + glyph.uv_rect.w),
character_index: current_char_index,
_padding: 0,
});
params.indices.extend_from_slice(&[
vertex_offset,
vertex_offset + 1,
vertex_offset + 2,
vertex_offset,
vertex_offset + 2,
vertex_offset + 3,
]);
let advance = fixed_advance
.unwrap_or(glyph.advance * params.scale + params.letter_spacing * PIXELS_TO_WORLD);
cursor_x += advance;
} else if let Some(advance) = fixed_advance {
cursor_x += advance;
}
prev_char = Some(character);
*params.character_index += 1;
}
}