pub use fontstash::{self, Align, FonsQuad, FontStash};
use std::os::raw::{c_int, c_uchar, c_void};
use crate::gfx::{self as rg, BakedResource};
#[derive(Debug)]
pub struct FontTexture {
inner: Box<FontTextureImpl>,
}
impl std::ops::Deref for FontTexture {
type Target = FontTextureImpl;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for FontTexture {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl FontTexture {
pub fn new(w: u32, h: u32) -> Self {
let mut inner = Box::new(FontTextureImpl {
stash: FontStash::uninitialized(),
img: Default::default(),
w,
h,
is_dirty: false,
tex_data: Vec::with_capacity((w * h) as usize),
});
let inner_ptr = inner.as_ref() as *const _ as *mut FontTextureImpl;
inner.stash.init_mut(w, h, inner_ptr);
fontstash::set_error_callback(
inner.stash().raw(),
fons_error_callback,
inner_ptr as *mut _,
);
return FontTexture { inner };
unsafe extern "C" fn fons_error_callback(
_uptr: *mut c_void,
error_code: c_int,
_val: c_int,
) {
match fontstash::ErrorCode::from_u32(error_code as u32) {
Some(error) => {
log::warn!("fons error: {:?}", error);
}
None => {
log::warn!("fons error error: given broken erroor code");
}
}
}
}
}
#[derive(Debug)]
pub struct FontTextureImpl {
stash: fontstash::FontStash,
img: rg::Image,
w: u32,
h: u32,
tex_data: Vec<u8>,
is_dirty: bool,
}
impl Drop for FontTextureImpl {
fn drop(&mut self) {
log::trace!("FontTextureImpl::drop");
if self.img.id != 0 {
log::trace!("==> destroy GPU font texture");
rg::Image::destroy(self.img);
}
}
}
impl std::ops::Deref for FontTextureImpl {
type Target = FontStash;
fn deref(&self) -> &Self::Target {
&self.stash
}
}
impl std::ops::DerefMut for FontTextureImpl {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.stash
}
}
impl FontTextureImpl {
pub fn img(&self) -> rg::Image {
self.img
}
pub fn cpu_texture(&self) -> (&Vec<u8>, [u32; 2]) {
(&self.tex_data, [self.w, self.h])
}
pub fn stash(&self) -> &FontStash {
&self.stash
}
pub fn text_bounds_multiline(
&self,
text: &str,
pos: impl Into<[f32; 2]>,
fontsize: f32,
line_spacing: f32,
) -> [f32; 4] {
let mut lines = text.lines();
self.stash.set_size(fontsize);
let [x, y, mut w, mut h] = {
let [x1, y1, x2, y2] = self
.stash
.text_bounds_oneline(pos.into(), lines.next().unwrap());
[x1, y1, x2 - x1, y2 - y1]
};
for line in lines {
if line.is_empty() {
h += fontsize + line_spacing;
} else {
let [x1, _y1, x2, _y2] = self.stash.text_bounds_oneline([0.0, 0.0], line);
if x2 - x1 > w {
w = x2 - x1;
}
h += fontsize + line_spacing;
}
}
[x, y, w, h]
}
pub fn text_size_multiline(&self, text: &str, fontsize: f32, line_spacing: f32) -> [f32; 2] {
let mut lines = text.lines();
self.stash.set_size(fontsize);
let [mut w, mut h] = self.stash.text_size_oneline(lines.next().unwrap());
for line in lines {
if line.is_empty() {
h += fontsize + line_spacing;
continue;
} else {
let [w2, _h2] = self.stash.text_size_oneline(line);
if w2 > w {
w = w2
}
h += fontsize + line_spacing;
}
}
[w, h]
}
}
unsafe impl fontstash::Renderer for FontTextureImpl {
unsafe extern "C" fn create(uptr: *mut c_void, width: c_int, height: c_int) -> c_int {
let me = &mut *(uptr as *const _ as *mut Self);
if me.img.id != 0 {
log::trace!("FontTextureImpl::create -- dispose old image");
rg::Image::destroy(me.img);
}
log::trace!("FontTextureImpl::create [{}, {}]", width, height);
me.img = rg::Image::create(&rg::ImageDesc {
type_: rg::ImageType::Dim2.to_ffi(),
width,
height,
usage: rg::ResourceUsage::Dynamic.to_ffi(),
..Default::default()
});
me.w = width as u32;
me.h = height as u32;
me.is_dirty = true;
true as c_int }
unsafe extern "C" fn resize(uptr: *mut c_void, width: c_int, height: c_int) -> c_int {
log::trace!("FontTextureImpl::resize");
Self::create(uptr, width, height);
true as c_int }
unsafe extern "C" fn expand(uptr: *mut c_void) -> c_int {
log::trace!("FontTextureImpl::expand");
let me = &mut *(uptr as *const _ as *mut Self);
if let Err(why) = me.stash.expand_atlas(me.w * 2, me.h * 2) {
log::warn!("fontstash: error on resize: {:?}", why);
false as c_int } else {
true as c_int }
}
unsafe extern "C" fn update(
uptr: *mut c_void,
_rect: *mut c_int,
_data: *const c_uchar,
) -> c_int {
let me = &mut *(uptr as *const _ as *mut Self);
me.is_dirty = true;
true as c_int }
}
impl FontTextureImpl {
fn update_cpu_image(&mut self) {
let tex_data = &mut self.tex_data;
tex_data.clear();
self.stash.with_pixels(|pixels, w, h| {
log::trace!("FontTextureImpl: [{}, {}] update CPU texture", w, h);
let area = (w * h) as usize;
for i in 0..area {
tex_data.push(255);
tex_data.push(255);
tex_data.push(255);
tex_data.push(pixels[i]);
}
});
}
pub unsafe fn maybe_update_image(&mut self) {
if !self.is_dirty {
return;
}
self.is_dirty = false;
self.update_cpu_image();
rg::update_image(self.img, &{
let mut data = rg::ImageData::default();
data.subimage[0][0] = self.tex_data.as_slice().into();
data
});
}
}