use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BookFont {
pub font_id: String,
pub size_px: f32,
}
#[derive(Debug, Default)]
pub struct BookFontRegistry {
pub fonts: HashMap<String, Vec<u8>>,
}
impl BookFontRegistry {
pub fn register(&mut self, id: impl Into<String>, ttf: Vec<u8>) {
self.fonts.insert(id.into(), ttf);
}
pub fn get(&self, id: &str) -> Option<&[u8]> {
self.fonts.get(id).map(Vec::as_slice)
}
}
#[derive(Debug, Clone)]
pub struct GlyphInfo {
pub u0: f32,
pub v0: f32,
pub u1: f32,
pub v1: f32,
pub width: u32,
pub height: u32,
pub xoff: f32,
pub yoff: f32,
pub advance: f32,
}
pub struct FontAtlas {
pub pixels: Vec<u8>,
pub atlas_size: u32,
pub glyphs: HashMap<char, GlyphInfo>,
pub line_height: f32,
}
#[cfg(feature = "fonts")]
const ATLAS_CHARS: &str = " !\"#$%&'()*+,-./0123456789:;<=>?@\
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`\
abcdefghijklmnopqrstuvwxyz{|}~\
ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞß\
àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ";
impl FontAtlas {
pub fn build(font_data: &[u8], size_px: f32) -> Option<FontAtlas> {
Self::build_impl(font_data, size_px)
}
#[cfg(feature = "fonts")]
fn build_impl(font_data: &[u8], size_px: f32) -> Option<FontAtlas> {
let font = ::fontdue::Font::from_bytes(
font_data,
::fontdue::FontSettings {
scale: size_px,
..Default::default()
},
)
.ok()?;
const ATLAS_W: u32 = 512;
const ATLAS_H: u32 = 512;
let mut pixels = vec![0u8; (ATLAS_W * ATLAS_H * 4) as usize];
let mut glyphs = HashMap::new();
let row_h = size_px.ceil() as u32 + 2;
let mut cx: u32 = 1;
let mut cy: u32 = 1;
for ch in ATLAS_CHARS.chars() {
let (metrics, bitmap) = font.rasterize(ch, size_px);
let gw = metrics.width as u32;
let gh = metrics.height as u32;
if gw == 0 || gh == 0 {
glyphs.insert(
ch,
GlyphInfo {
u0: 0.0,
v0: 0.0,
u1: 0.0,
v1: 0.0,
width: 0,
height: 0,
xoff: metrics.xmin as f32,
yoff: metrics.ymin as f32,
advance: metrics.advance_width,
},
);
continue;
}
if cx + gw + 1 >= ATLAS_W {
cx = 1;
cy += row_h;
}
if cy + gh + 1 >= ATLAS_H {
break;
}
for row in 0..gh {
for col in 0..gw {
let alpha = bitmap[(row * gw + col) as usize];
let idx = ((cy + row) * ATLAS_W + (cx + col)) as usize * 4;
pixels[idx] = 255;
pixels[idx + 1] = 255;
pixels[idx + 2] = 255;
pixels[idx + 3] = alpha;
}
}
glyphs.insert(
ch,
GlyphInfo {
u0: cx as f32 / ATLAS_W as f32,
v0: cy as f32 / ATLAS_H as f32,
u1: (cx + gw) as f32 / ATLAS_W as f32,
v1: (cy + gh) as f32 / ATLAS_H as f32,
width: gw,
height: gh,
xoff: metrics.xmin as f32,
yoff: metrics.ymin as f32,
advance: metrics.advance_width,
},
);
cx += gw + 1;
}
Some(FontAtlas {
pixels,
atlas_size: ATLAS_W,
glyphs,
line_height: size_px * 1.2,
})
}
#[cfg(not(feature = "fonts"))]
fn build_impl(_font_data: &[u8], _size_px: f32) -> Option<FontAtlas> {
None
}
pub fn measure_width(&self, text: &str) -> f32 {
text.chars()
.map(|c| self.glyphs.get(&c).map(|g| g.advance).unwrap_or(0.0))
.sum()
}
}