#![deny(unsafe_code)]
use std::{cell::RefCell, collections::HashMap, ops::Deref, path::Path};
use fontdue::{FontResult, FontSettings};
use macroquad::prelude::{
draw_texture_ex, vec2, Color, DrawTextureParams, FilterMode, Image, TextDimensions,
};
use crate::{
atlas::Atlas,
misc::{read_file, IoError, IoErrorKind, IoResult},
};
pub(crate) mod atlas;
pub(crate) mod misc;
pub type ScalingMode = FilterMode;
pub type FontdueFont = fontdue::Font;
#[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(crate) 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,
}
}
}
#[derive(Debug)]
pub struct Font<'a> {
pub name: &'a str,
font: FontdueFont,
atlas: RefCell<Atlas>,
chars: RefCell<HashMap<(char, u16), CharacterInfo>>,
}
impl<'a> Deref for Font<'a> {
type Target = FontdueFont;
fn deref(&self) -> &Self::Target {
&self.font
}
}
impl<'a> Font<'a> {
fn new(name: &'a str, font: FontdueFont, mode: ScalingMode) -> Self {
Self {
name,
font,
atlas: RefCell::new(Atlas::new(mode)),
chars: RefCell::default(),
}
}
pub fn contains(&self, c: char) -> bool {
self.lookup_glyph_index(c) != 0
}
fn _cache_glyph(&self, c: char, size: u16) -> CharacterInfo {
let (matrix, bitmap) = self.rasterize(c, size as f32);
let (width, height) = (matrix.width as u16, matrix.height as u16);
let id = self.atlas.borrow_mut().new_unique_id();
let bytes = bitmap
.iter()
.flat_map(|coverage| vec![255, 255, 255, *coverage])
.collect::<Vec<_>>();
self.atlas.borrow_mut().cache_sprite(
id,
Image {
width,
height,
bytes,
},
);
CharacterInfo {
id,
offset_x: matrix.xmin as f32,
offset_y: matrix.ymin as f32,
advance: matrix.advance_width,
}
}
pub fn cache_glyph(&self, c: char, size: u16) {
if !self.chars.borrow().contains_key(&(c, size)) {
let info = self._cache_glyph(c, size);
self.chars.borrow_mut().insert((c, size), info);
}
}
pub fn recache_glyphs(&self) {
for ((c, size), info) in self.chars.borrow_mut().iter_mut() {
*info = self._cache_glyph(*c, *size);
}
}
}
#[derive(Debug)]
pub struct Fonts<'a> {
fonts: Vec<Font<'a>>,
index_by_name: HashMap<&'a str, usize>,
default_sm: ScalingMode,
}
impl<'a> Default for Fonts<'a> {
fn default() -> Self {
Self::new(ScalingMode::Linear)
}
}
impl<'a> Fonts<'a> {
pub fn new(default_sm: ScalingMode) -> Self {
Self {
fonts: Vec::default(),
index_by_name: HashMap::default(),
default_sm,
}
}
pub fn fonts(&self) -> &Vec<Font> {
&self.fonts
}
pub fn cache_glyph(&self, c: char, size: u16) {
for font in self.fonts.iter() {
font.cache_glyph(c, size);
}
}
pub fn load_font_from_bytes_with_scale(
&mut self,
name: &'a str,
bytes: &[u8],
scale: f32,
) -> FontResult<()> {
let settings = FontSettings {
collection_index: 0,
scale,
};
let font = FontdueFont::from_bytes(bytes, settings)?;
self.index_by_name.insert(name, self.fonts.len());
self.fonts.push(Font::new(name, font, self.default_sm));
Ok(())
}
pub fn load_font_from_bytes(&mut self, name: &'a str, bytes: &[u8]) -> FontResult<()> {
self.load_font_from_bytes_with_scale(name, bytes, 100.0)
}
pub fn load_font_from_file(&mut self, name: &'a str, path: impl AsRef<Path>) -> IoResult<()> {
self.load_font_from_file_with_scale(name, path, 100.0)
}
pub fn load_font_from_file_with_scale(
&mut self,
name: &'a str,
path: impl AsRef<Path>,
scale: f32,
) -> IoResult<()> {
let bytes = read_file(path)?;
self
.load_font_from_bytes_with_scale(name, &bytes, scale)
.map_err(|err| IoError::new(IoErrorKind::InvalidData, err))
}
pub fn unload_font_by_index(&mut self, index: usize) {
if self.fonts.len() <= index {
return;
}
self.fonts.remove(index);
self.index_by_name.clear();
for (index, font) in self.fonts.iter().enumerate() {
self.index_by_name.insert(font.name, index);
}
}
pub fn unload_font_by_name(&mut self, name: &str) {
self.unload_font_by_index(self.get_index_by_name(name).unwrap_or(self.fonts.len()));
}
pub fn get_font_by_index(&self, index: usize) -> Option<&Font> {
self.fonts.get(index)
}
pub fn get_index_by_char(&self, c: char) -> Option<usize> {
self.fonts.iter().position(|it| it.contains(c))
}
pub fn get_index_by_name(&self, name: &str) -> Option<usize> {
self.index_by_name.get(name).copied()
}
pub fn get_font_by_name(&self, name: &str) -> Option<&Font> {
self.get_font_by_index(self.get_index_by_name(name)?)
}
pub fn get_font_by_char(&self, c: char) -> Option<&Font> {
self.get_font_by_index(self.get_index_by_char(c)?)
}
pub fn get_font_by_char_or_panic(&self, c: char) -> &Font {
self
.get_font_by_char(c)
.or_else(|| self.fonts.first())
.expect("There is no font currently loaded")
}
pub fn contains(&self, c: char) -> bool {
self.fonts.iter().any(|f| f.contains(c))
}
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() {
let font = self.get_font_by_char_or_panic(c);
font.cache_glyph(c, size);
let info = font.chars.borrow()[&(c, size)];
let glyph = font.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) -> TextDimensions {
self.draw_text_ex(&TextParams {
text,
x,
y,
size,
color,
draw: Default::default(),
})
}
pub fn draw_text_ex(&self, params: &TextParams) -> TextDimensions {
let mut total_width = 0f32;
for c in params.text.chars() {
let font = self.get_font_by_char_or_panic(c);
font.cache_glyph(c, params.size);
}
for c in params.text.chars() {
let font = self.get_font_by_char_or_panic(c);
let mut atlas = font.atlas.borrow_mut();
let info = &font.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;
}
self.measure_text(params.text, params.size)
}
}