use std::cell::RefCell;
use std::collections::HashMap;
use fontdue::{Font, FontResult, FontSettings, Metrics};
use macroquad::prelude::{Color, draw_texture_ex, DrawTextureParams, FilterMode, Image, TextDimensions, vec2};
use crate::atlas::Atlas;
pub(crate) mod atlas;
pub type ScalingMode = FilterMode;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum DrawFrom {
BottomLeft,
TopLeft,
}
impl Default for DrawFrom {
fn default() -> Self {
Self::TopLeft
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct CharacterInfo {
pub id: u64,
pub offset_x: f32,
pub offset_y: f32,
pub advance: f32,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TextParams<'a> {
pub text: &'a str,
pub x: f32,
pub y: f32,
pub size: u16,
pub color: Color,
pub draw: DrawFrom,
}
impl<'a> Default for TextParams<'a> {
fn default() -> Self {
Self {
text: "",
x: 0.0,
y: 0.0,
size: 22,
color: Color::from_rgba(255, 255, 255, 255),
draw: DrawFrom::TopLeft,
}
}
}
pub struct Fonts {
fonts: Vec<Font>,
atlas: RefCell<Atlas>,
chars: RefCell<HashMap<(char, u16), CharacterInfo>>,
}
impl Default for Fonts {
fn default() -> Self {
Self::new(ScalingMode::Linear)
}
}
impl Fonts {
pub fn new(mode: ScalingMode) -> Self {
Self {
fonts: Vec::default(),
atlas: RefCell::new(Atlas::new(mode)),
chars: RefCell::default(),
}
}
pub fn fonts(&self) -> &Vec<Font> {
&self.fonts
}
pub fn cache_glyph(&self, c: char, size: u16) {
if self.chars.borrow().contains_key(&(c, size)) {
return;
}
let mut cache = self.chars.borrow_mut();
let mut atlas = self.atlas.borrow_mut();
let (matrix, bitmap) = self.rasterize(c, size as f32);
let (width, height) = (matrix.width as u16, matrix.height as u16);
let id = atlas.new_unique_id();
let bytes = bitmap
.iter()
.flat_map(|coverage| vec![255, 255, 255, *coverage])
.collect::<Vec<_>>();
atlas.cache_sprite(id, Image { width, height, bytes });
let info = CharacterInfo {
id,
offset_x: matrix.xmin as f32,
offset_y: matrix.ymin as f32,
advance: matrix.advance_width,
};
cache.insert((c, size), info);
}
pub fn load_font_from_bytes_with_scale(&mut self, bytes: &[u8], scale: f32) -> FontResult<()> {
let settings = FontSettings { collection_index: 0, scale };
let font = Font::from_bytes(bytes, settings)?;
self.fonts.push(font);
Ok(())
}
pub fn load_font_from_bytes(&mut self, bytes: &[u8]) -> FontResult<()> {
self.load_font_from_bytes_with_scale(bytes, 100.0)
}
pub fn contains(&self, c: char) -> bool {
self.lookup_glyph_index(c).1 != 0
}
pub fn lookup_glyph_index(&self, c: char) -> (usize, u16) {
self.fonts.iter()
.map(|font| font.lookup_glyph_index(c))
.enumerate()
.find(|(_, glyph_idx)| *glyph_idx != 0)
.unwrap_or_default()
}
pub fn rasterize(&self, c: char, px: f32) -> (Metrics, Vec<u8>) {
let (font_idx, glyph_idx) = self.lookup_glyph_index(c);
self.fonts.get(font_idx)
.map(|it| it.rasterize_indexed(glyph_idx, px))
.unwrap_or_default()
}
pub fn measure_text(&self, text: &str, size: u16) -> TextDimensions {
let mut width = 0f32;
let mut min_y = f32::MAX;
let mut max_y = f32::MIN;
for c in text.chars() {
self.cache_glyph(c, size);
let info = self.chars.borrow()[&(c, size)];
let glyph = self.atlas.borrow().get(info.id).unwrap().rect;
width += info.advance;
if min_y > info.offset_y {
min_y = info.offset_y;
}
if max_y < glyph.h + info.offset_y {
max_y = glyph.h + info.offset_y;
}
}
TextDimensions {
width,
height: max_y - min_y,
offset_y: max_y,
}
}
pub fn draw_text(&self, text: &str, x: f32, y: f32, size: u16, color: Color) {
self.draw_text_ex(&TextParams {
text,
x,
y,
size,
color,
draw: Default::default(),
})
}
pub fn draw_text_ex(&self, params: &TextParams) {
let mut total_width = 0f32;
for c in params.text.chars() {
self.cache_glyph(c, params.size);
let mut atlas = self.atlas.borrow_mut();
let info = &self.chars.borrow()[&(c, params.size)];
let glyph = atlas.get(info.id).unwrap().rect;
let mut y = 0.0 - glyph.h - info.offset_y + params.y;
if let DrawFrom::TopLeft = params.draw {
y += params.size as f32;
}
draw_texture_ex(
atlas.texture(),
info.offset_x + total_width + params.x,
y,
params.color,
DrawTextureParams {
dest_size: Some(vec2(glyph.w, glyph.h)),
source: Some(glyph),
..Default::default()
},
);
total_width += info.advance;
}
}
}