use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::{
console::*,
math::{AABB2, *},
ogl::{array::*, buffer::*, program::*, texture::*, OglIncomplete, *},
*,
};
use glow::*;
pub use hiero_pack::{self, *};
pub mod default_font;
pub use default_font::*;
type RcRefCell<T> = Rc<RefCell<T>>;
static DEFAULT_WHITE_SPACE_LEN: f32 = 24.0;
static CHARACTER_BUFFER_LEN: usize = 256;
static DEFAULT_PROGRAM: &str = r"
#ifndef HEADER
#version 300 es
precision mediump float;
#endif
#ifndef UNIFORMS
uniform sampler2D page;
uniform vec4 text_color;
uniform mat4 projection;
uniform mat4 model;
#endif
#ifndef VERTEX_ATTRIBUTES
layout(location = 1) in vec2 vert_in;
layout(location = 2) in vec2 uv_in;
#endif
#ifndef VERTEX_SHADER
out vec2 tex_coord;
void main(){
tex_coord = uv_in;
gl_Position = projection*model*vec4(vert_in.xy,0.0,1.0);
}
#endif
#ifndef FRAGMENT_SHADER
in vec2 tex_coord;
out vec4 color;
void main(){
vec4 page = texture(page,tex_coord);
float dist = page.w;
vec2 grad = vec2( dFdx(dist), dFdy(dist));
float grad_mag = length(grad)*1.0;
color = vec4(1.)*smoothstep(0.5-grad_mag,0.5+grad_mag,dist);
}
#endif
";
struct WriterState {
pen_x: f32,
pen_y: f32,
comps_filled: usize,
chars_filled: usize,
vert_len: usize,
text_len: usize,
}
impl WriterState {
fn advance_pen(&mut self, dx: f32, dy: f32) {
self.pen_x += dx;
self.pen_y += dy;
}
fn push_glyph(
&mut self,
verts: &mut [f32],
uvs: &mut [f32],
xoff: f32,
yoff: f32,
glyph_bounds: AABB2<f32>,
) {
TextWriter::set_glyph(
verts,
uvs,
AABB2::from_point_and_lengths(
[self.pen_x + xoff, self.pen_y + yoff],
glyph_bounds.dims(),
),
glyph_bounds,
self.comps_filled,
);
self.count_glyph();
}
fn count_glyph(&mut self) {
self.chars_filled += 1;
self.comps_filled += 12;
}
fn buffers_are_full(&self, char_index: usize) -> bool {
self.comps_filled >= self.vert_len || char_index >= self.text_len - 1
}
fn flush(
&mut self,
gl: &GlowGL,
vert_buffer: &mut dyn HasBufferObj,
uv_buffer: &mut dyn HasBufferObj,
) {
uv_buffer.update();
vert_buffer.update();
unsafe {
gl.draw_arrays(TRIANGLES, 0, 6 * self.chars_filled as i32);
}
self.comps_filled = 0;
self.chars_filled = 0;
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
pub enum FontKind {
Default,
}
pub struct TextWriter {
gl: GlowGL,
text_geometry: OglArray,
renderer: OglProg,
_atlas_table: HashMap<String, RcRefCell<HieroAtlas>>,
atlas: Option<HieroAtlas>,
projection_mat_loc: Option<UniformLocation>,
model_loc: Option<UniformLocation>,
page_loc: Option<UniformLocation>,
page_texture: Option<OglTexture>,
whitespace_len: Option<f32>,
page_history: [usize; 4],
page_index: usize,
horizontal_scale_factor: f32,
global_dy_t: Option<f32>,
global_dy_b: Option<f32>,
}
impl TextWriter {
pub fn new(gl: &GlowGL) -> OglIncomplete<Self> {
let renderer = match OglProg::compile_program(gl, DEFAULT_PROGRAM) {
Ok(prog) => prog,
Err(comp_err) => {
if let CompilationError::ShaderError {
ogl_error,
faulty_source,
} = comp_err
{
console_log!("ogl_error:\n{}\nsource:{}\n", ogl_error, faulty_source);
}
panic!("shader compiler error");
}
};
let vert_data: Vec<f32> = vec![0.; CHARACTER_BUFFER_LEN * 6 * 2];
let uv_data = vert_data.clone();
let array = OglArray::new(gl).init(vec![
BufferPair::new(
"verts",
OglBuf::new(gl)
.with_usage(DYNAMIC_DRAW)
.with_data(vert_data)
.with_index(1)
.with_num_comps(2)
.build()
.into(),
),
BufferPair::new(
"uvs",
OglBuf::new(gl)
.with_usage(DYNAMIC_DRAW)
.with_data(uv_data)
.with_index(2)
.with_num_comps(2)
.build()
.into(),
),
]);
unsafe {
renderer.bind(true);
let proj_loc = gl.get_uniform_location(renderer.prog(), "projection");
let page_loc = gl.get_uniform_location(renderer.prog(), "page");
let model_loc = gl.get_uniform_location(renderer.prog(), "model");
OglIncomplete::new(Self {
gl: gl.clone(),
renderer,
_atlas_table: HashMap::new(),
atlas: None,
model_loc,
projection_mat_loc: proj_loc,
text_geometry: array,
page_loc,
page_texture: None,
whitespace_len: None,
page_history: [99999; 4],
page_index: 0,
horizontal_scale_factor: 1.0,
global_dy_b: None,
global_dy_t: None,
})
}
}
pub fn horizontal_scaling_factor(&self) -> &f32 {
&self.horizontal_scale_factor
}
pub fn horizontal_scaling_factor_mut(&mut self) -> &mut f32 {
&mut self.horizontal_scale_factor
}
pub fn set_scaling_factor(&mut self, sf: f32) -> f32 {
let pf = self.horizontal_scale_factor;
self.horizontal_scale_factor = sf;
pf
}
pub fn calc_text_aabb(&self, text: &str, x0: f32, y0: f32, size: f32) -> AABB2<f32> {
if !text.is_empty() {
let src_bb = self.calculate_bounding_box(x0, y0, text);
AABB2::from_point_and_lengths(
[src_bb.x(), src_bb.y()],
[src_bb.w() * self.horizontal_scale_factor, size],
)
} else {
AABB2::from_point_and_lengths([x0, y0], [0., 0.])
}
}
pub fn calc_text_aabb_preserved(&self, text: &str, x0: f32, y0: f32, size: f32) -> AABB2<f32> {
if !text.is_empty() {
let src_bb = self.calculate_bounding_box(x0, y0, text);
let aspect_ratio = src_bb.w() / src_bb.h();
let width = aspect_ratio * size;
AABB2::from_point_and_lengths([src_bb.x(), src_bb.y()], [width, size])
} else {
AABB2::from_point_and_lengths([x0, y0], [0., 0.])
}
}
pub fn draw_text_line<T: Into<Option<(u32, u32)>>>(
&mut self,
text: &str,
x0: f32,
y0: f32,
size: f32,
screen_bounds: T,
) {
if text.is_empty() {
return;
}
let screen_bounds = screen_bounds.into();
let gl = self.gl.clone();
let (screen_w, screen_h) = screen_bounds.unwrap_or((800, 600));
let proj_mat = calc_ortho_window_f32(screen_w as f32, screen_h as f32);
let src_bb = self.calculate_bounding_box(x0, y0, text);
let resize_matrix = resize_region(
src_bb,
AABB2::from_point_and_lengths(
[x0, y0],
[src_bb.w() * self.horizontal_scale_factor, size],
),
);
self.draw(&gl, text, x0, y0, proj_mat, resize_matrix);
}
pub fn draw_text_line_preserved(
&mut self,
text: &str,
x0: f32,
y0: f32,
size: f32,
screen_bounds: Option<(u32, u32)>,
) {
if text.is_empty() {
return;
}
let gl = self.gl.clone();
let (screen_w, screen_h) = screen_bounds.unwrap_or((800, 600));
let proj_mat = calc_ortho_window_f32(screen_w as f32, screen_h as f32);
let src_bb = self.calculate_bounding_box(x0, y0, text);
let aspect_ratio = src_bb.w() / src_bb.h();
let resize_matrix = resize_region(
src_bb,
AABB2::from_point_and_lengths([x0, y0], [aspect_ratio * size, size]),
);
self.draw(&gl, text, x0, y0, proj_mat, resize_matrix);
}
fn draw(
&mut self,
gl: &GlowGL,
text: &str,
x0: f32,
y0: f32,
proj_mat: Mat4<f32>,
resize_matrix: Mat4<f32>,
) {
let whitespace = self.whitespace();
self.renderer.bind(true);
self.text_geometry.bind(true);
if let Some(texture) = self.page_texture.as_ref() {
texture.bind(0, self.page_loc.as_ref())
}
let (uv_buffer_ptr, vert_buffer_ptr) = {
(
self.text_geometry.get_mut("uvs").unwrap() as *mut dyn HasBufferObj,
self.text_geometry.get_mut("verts").unwrap() as *mut dyn HasBufferObj,
)
};
let (uv_buffer, vert_buffer) = unsafe { (&mut *uv_buffer_ptr, &mut *vert_buffer_ptr) };
let mut writer_state = WriterState {
pen_x: x0,
pen_y: y0,
comps_filled: 0,
chars_filled: 0,
vert_len: Self::to_float_slice(vert_buffer.raw_bytes_mut()).len(),
text_len: text.len(),
};
let first_char = text.chars().next().unwrap();
let first_page = self.atlas.as_ref().and_then(|atlas| {
atlas
.bitmap_table
.get(&first_char)
.map(|bitmap| bitmap.page)
});
if let Some(index) = first_page {
if self.cur_page() != index as usize {
self.decode_page(index as usize);
self.new_page(index as usize);
}
}
unsafe {
gl.enable(glow::BLEND);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
gl.uniform_matrix_4_f32_slice(self.projection_mat_loc.as_ref(), true, proj_mat.as_slice());
gl.uniform_matrix_4_f32_slice(self.model_loc.as_ref(), true, resize_matrix.as_slice());
}
for (k, character) in text.char_indices() {
let bitmap = self
.atlas
.as_ref()
.and_then(|atlas| atlas.bitmap_table.get(&character))
.copied();
let adv = bitmap.map_or((whitespace, 0.), |bitmap| {
let new_page = bitmap.page as usize;
let cur_page = self.cur_page();
if new_page != cur_page {
writer_state.flush(gl, vert_buffer, uv_buffer);
self.decode_page(new_page);
}
let hiero_bounds = AABB2::from_point_and_lengths(
[bitmap.x as f32, bitmap.y as f32],
[bitmap.width as f32, bitmap.height as f32],
);
let uvs = Self::to_float_slice(uv_buffer.raw_bytes_mut());
let verts = Self::to_float_slice(vert_buffer.raw_bytes_mut());
writer_state.push_glyph(
verts,
uvs,
bitmap.xoffset as f32,
bitmap.yoffset as f32,
hiero_bounds,
);
self.new_page(new_page);
(bitmap.xadvance as f32, 0.0)
});
writer_state.advance_pen(adv.0, adv.1);
if writer_state.buffers_are_full(k) {
writer_state.flush(gl, vert_buffer, uv_buffer);
}
}
unsafe {
gl.disable(glow::BLEND);
}
}
fn decode_page(&mut self, new_page: usize) {
if let Some(atlas) = self.atlas.as_ref() {
atlas
.try_unpack_page(new_page)
.map(|page| {
let pixel_slice = &page.pixels()[..];
self.page_texture.as_ref().map(|page_texture| {
page_texture.copy_image(512, 512, pixel_slice);
})
})
.map_err(|err| {
panic!("Error: {}", err);
})
.unwrap();
}
}
fn calculate_bounding_box(&self, x0: f32, y0: f32, text: &str) -> AABB2<f32> {
self.calculate_bounding_box_iter(x0, y0, text.chars())
}
fn calculate_global_bounding_box(&mut self) {
let char_iter = self
.atlas
.as_ref()
.unwrap()
.bitmap_table
.iter()
.map(|(&k, _)| k);
let aabb = self.calculate_bounding_box_iter(0.0, 0.0, char_iter);
self.global_dy_t = Some(aabb.y());
self.global_dy_b = Some(aabb.y() + aabb.h());
}
fn calculate_bounding_box_iter(
&self,
x0: f32,
y0: f32,
char_iter: impl Iterator<Item = char>,
) -> AABB2<f32> {
let mut minx = std::f32::INFINITY;
let mut miny = self
.global_dy_t
.map(|dy| y0 + dy)
.unwrap_or(std::f32::INFINITY);
let mut maxx = std::f32::NEG_INFINITY;
let mut maxy = self
.global_dy_b
.map(|dy| y0 + dy)
.unwrap_or(std::f32::NEG_INFINITY);
let mut pen_x = x0;
let pen_y = y0;
let whitespace = self.whitespace();
if let Some(atlas) = self.atlas.as_ref() {
char_iter.for_each(|c| {
let x_adv = atlas.bitmap_table.get(&c).map_or_else(
|| whitespace,
|bitmap| {
let is_ws = c.is_whitespace() as u8 as f32;
let not_ws = 1.0 - is_ws;
let xoff = bitmap.xoffset as f32;
let yoff = bitmap.yoffset as f32;
let x = xoff + pen_x;
let y = yoff + pen_y;
let w = not_ws * (bitmap.width as f32) + is_ws * (bitmap.xadvance as f32);
let h = not_ws * (bitmap.height as f32) + is_ws * (bitmap.xadvance as f32);
[(x, y), (x + w, y), (x, y + h), (x + w, y + h)]
.iter()
.for_each(|&(x, y)| {
minx = minx.min(x);
maxx = maxx.max(x);
miny = miny.min(y);
maxy = maxy.max(y);
});
bitmap.xadvance as f32
},
);
pen_x += x_adv;
});
}
minx = minx.min(pen_x);
maxx = maxx.max(pen_x);
miny = miny.min(pen_y);
maxy = maxy.max(pen_y);
AABB2::from_segment([minx, miny], [maxx, maxy])
}
#[allow(clippy::identity_op)]
fn set_glyph(vert: &mut [f32], uvs: &mut [f32], vb: AABB2<f32>, hb: AABB2<f32>, offset: usize) {
const INV_PAGE_DIM: f32 = 1.0 / 512.;
let get_pos = |rel_index: usize| -> usize { (2 * rel_index) + offset };
let mut uv_helper = |rel_index: usize, x, y| {
uvs[get_pos(rel_index) + 0] = x * INV_PAGE_DIM;
uvs[get_pos(rel_index) + 1] = y * INV_PAGE_DIM;
};
let mut vert_helper = |rel_index: usize, x, y| {
vert[get_pos(rel_index) + 0] = x;
vert[get_pos(rel_index) + 1] = y;
};
#[rustfmt::skip]
let uvs = [
(hb.x() ,hb.y() ),
(hb.x() + hb.w(),hb.y() ),
(hb.x() + hb.w(),hb.y() + hb.h() ),
(hb.x() ,hb.y() ),
(hb.x() + hb.w(),hb.y() + hb.h() ),
(hb.x() ,hb.y() + hb.h() ),
];
#[rustfmt::skip]
let verts = [
(vb.x() , vb.y() ),
(vb.x() + vb.w(), vb.y() ),
(vb.x() + vb.w(), vb.y() + vb.h()),
(vb.x() , vb.y() ),
(vb.x() + vb.w(), vb.y() + vb.h()),
(vb.x() , vb.y() + vb.h()),
];
for (k, (&(vx, vy), (uvx, uvy))) in verts.iter().zip(uvs.iter()).enumerate() {
uv_helper(k, uvx, uvy);
vert_helper(k, vx, vy);
}
}
fn to_float_slice(slice: &mut [u8]) -> &mut [f32] {
let bytes = slice.len();
unsafe {
std::slice::from_raw_parts_mut(
slice.as_mut_ptr() as *mut f32,
bytes / std::mem::size_of::<f32>(),
)
}
}
fn whitespace(&self) -> f32 {
self.whitespace_len.unwrap_or(DEFAULT_WHITE_SPACE_LEN)
}
#[allow(dead_code)]
fn prev_page(&self) -> usize {
let len = self.page_history.len();
self.page_history[(self.page_index + len - 1) % len]
}
fn cur_page(&self) -> usize {
self.page_history[self.page_index]
}
fn new_page(&mut self, page: usize) {
let new_index = (self.page_index + 1) % self.page_history.len();
self.page_history[new_index] = page;
self.page_index = new_index;
}
}
pub trait HasTextWriterBuilder {
type WriterType;
fn with_atlas(self, atlus: HieroAtlas) -> Self;
fn build(self) -> Self::WriterType;
}
impl HasTextWriterBuilder for OglIncomplete<TextWriter> {
type WriterType = TextWriter;
fn with_atlas(mut self, atlus: HieroAtlas) -> Self {
let gl = &self.inner.gl;
self.inner.page_texture = atlus.try_unpack_page(0).ok().map(|page| {
let info = page.info();
TextureObj::<u8>::builder(gl)
.with_width(info.width)
.with_height(info.height)
.with_format(glow::RGBA)
.build()
.into()
});
self.inner.atlas = Some(atlus);
self
}
fn build(mut self) -> Self::WriterType {
self.inner.calculate_global_bounding_box();
self.inner
}
}