use std::collections::HashMap;
use crate::{
color::Color,
get_context, get_quad_context,
math::{vec3, Rect},
texture::Image,
};
use crate::color::WHITE;
use glam::vec2;
use std::cell::RefCell;
use std::rc::Rc;
pub(crate) mod atlas;
use atlas::Atlas;
#[derive(Debug)]
pub(crate) struct CharacterInfo {
pub offset_x: i32,
pub offset_y: i32,
pub advance: f32,
pub sprite: u64,
}
pub(crate) struct FontInternal {
font: fontdue::Font,
atlas: Rc<RefCell<Atlas>>,
characters: HashMap<(char, u16), CharacterInfo>,
}
impl std::fmt::Debug for FontInternal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Font")
.field("font", &"fontdue::Font")
.finish()
}
}
#[derive(Debug, Clone)]
pub struct FontError(pub &'static str);
impl From<&'static str> for FontError {
fn from(s: &'static str) -> Self {
Self(s)
}
}
impl std::fmt::Display for FontError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "font error: {}", self.0)
}
}
impl std::error::Error for FontError {}
impl FontInternal {
pub(crate) fn load_from_bytes(
atlas: Rc<RefCell<Atlas>>,
bytes: &[u8],
) -> Result<FontInternal, FontError> {
Ok(FontInternal {
font: fontdue::Font::from_bytes(&bytes[..], fontdue::FontSettings::default())?,
characters: HashMap::new(),
atlas,
})
}
pub(crate) fn ascent(&self, font_size: f32) -> f32 {
self.font.horizontal_line_metrics(font_size).unwrap().ascent
}
pub(crate) fn descent(&self, font_size: f32) -> f32 {
self.font
.horizontal_line_metrics(font_size)
.unwrap()
.descent
}
pub(crate) fn cache_glyph(&mut self, character: char, size: u16) {
if self.characters.contains_key(&(character, size)) {
return;
}
let (metrics, bitmap) = self.font.rasterize(character, size as f32);
if metrics.advance_height != 0.0 {
panic!("Vertical fonts are not supported");
}
let (width, height) = (metrics.width as u16, metrics.height as u16);
let sprite = self.atlas.borrow_mut().new_unique_id();
self.atlas.borrow_mut().cache_sprite(
sprite,
Image {
bytes: bitmap
.iter()
.flat_map(|coverage| vec![255, 255, 255, *coverage])
.collect(),
width,
height,
},
);
let advance = metrics.advance_width;
let (offset_x, offset_y) = (metrics.xmin, metrics.ymin);
let character_info = CharacterInfo {
advance,
offset_x,
offset_y,
sprite,
};
self.characters.insert((character, size), character_info);
}
pub(crate) fn get(&self, character: char, size: u16) -> Option<&CharacterInfo> {
self.characters.get(&(character, size))
}
pub(crate) fn measure_text(
&mut self,
text: &str,
font_size: u16,
font_scale_x: f32,
font_scale_y: f32,
) -> TextDimensions {
let dpi_scaling = get_quad_context().dpi_scale();
let font_size = (font_size as f32 * dpi_scaling).ceil() as u16;
for character in text.chars() {
if self.characters.contains_key(&(character, font_size)) == false {
self.cache_glyph(character, font_size);
}
}
let mut width = 0.;
let mut min_y = std::f32::MAX;
let mut max_y = -std::f32::MAX;
let atlas = self.atlas.borrow();
for character in text.chars() {
if let Some(font_data) = self.characters.get(&(character, font_size)) {
let glyph = atlas.get(font_data.sprite).unwrap().rect;
width += font_data.advance * font_scale_x;
if min_y > font_data.offset_y as f32 * font_scale_y {
min_y = font_data.offset_y as f32 * font_scale_y;
}
if max_y < glyph.h as f32 * font_scale_y + font_data.offset_y as f32 * font_scale_y
{
max_y =
glyph.h as f32 * font_scale_y + font_data.offset_y as f32 * font_scale_y;
}
}
}
let height = max_y - min_y;
TextDimensions {
width: width / dpi_scaling,
height: height / dpi_scaling,
offset_y: max_y,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Font(usize);
impl Default for Font {
fn default() -> Font {
Font(0)
}
}
impl Font {
pub fn ascii_character_list() -> Vec<char> {
(0..255).filter_map(::std::char::from_u32).collect()
}
pub fn latin_character_list() -> Vec<char> {
"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890!@#$%^&*(){}[].,:"
.chars()
.collect()
}
pub fn populate_font_cache(&self, characters: &[char], size: u16) {
let font = get_context().fonts_storage.get_font_mut(*self);
for character in characters {
font.cache_glyph(*character, size);
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct TextParams {
pub font: Font,
pub font_size: u16,
pub font_scale: f32,
pub font_scale_aspect: f32,
pub rotation: f32,
pub color: Color,
}
impl Default for TextParams {
fn default() -> TextParams {
TextParams {
font: Font(0),
font_size: 20,
font_scale: 1.0,
font_scale_aspect: 1.0,
color: WHITE,
rotation: 0.0,
}
}
}
pub async fn load_ttf_font(path: &str) -> Result<Font, FontError> {
let bytes = crate::file::load_file(path).await.unwrap();
load_ttf_font_from_bytes(&bytes[..])
}
pub fn load_ttf_font_from_bytes(bytes: &[u8]) -> Result<Font, FontError> {
let context = get_context();
let atlas = Rc::new(RefCell::new(Atlas::new(
get_quad_context(),
miniquad::FilterMode::Linear,
)));
let font = context
.fonts_storage
.make_font(FontInternal::load_from_bytes(atlas.clone(), bytes)?);
font.populate_font_cache(&Font::ascii_character_list(), 15);
Ok(font)
}
pub fn draw_text(text: &str, x: f32, y: f32, font_size: f32, color: Color) {
draw_text_ex(
text,
x,
y,
TextParams {
font_size: font_size as u16,
font_scale: 1.0,
color,
..Default::default()
},
)
}
pub fn draw_text_ex(text: &str, x: f32, y: f32, params: TextParams) {
let font = get_context().fonts_storage.get_font_mut(params.font);
let font_scale_x = params.font_scale * params.font_scale_aspect;
let font_scale_y = params.font_scale;
let dpi_scaling = get_quad_context().dpi_scale();
let font_size = (params.font_size as f32 * dpi_scaling).ceil() as u16;
let mut total_width = 0.;
for character in text.chars() {
if !font.characters.contains_key(&(character, font_size)) {
font.cache_glyph(character, font_size);
}
let mut atlas = font.atlas.borrow_mut();
let font_data = &font.characters[&(character, font_size)];
let glyph = atlas.get(font_data.sprite).unwrap().rect;
let angle_rad = params.rotation;
let left_coord = (font_data.offset_x as f32 * font_scale_x + total_width) * angle_rad.cos()
+ (glyph.h as f32 * font_scale_y + font_data.offset_y as f32 * font_scale_y)
* angle_rad.sin();
let top_coord = (font_data.offset_x as f32 * font_scale_x + total_width) * angle_rad.sin()
+ (0.0 - glyph.h as f32 * font_scale_y - font_data.offset_y as f32 * font_scale_y)
* angle_rad.cos();
total_width += font_data.advance * font_scale_x;
let dest = Rect::new(
left_coord / dpi_scaling as f32 + x,
top_coord / dpi_scaling as f32 + y,
glyph.w as f32 / dpi_scaling as f32 * font_scale_x,
glyph.h as f32 / dpi_scaling as f32 * font_scale_y,
);
let source = Rect::new(
glyph.x as f32,
glyph.y as f32,
glyph.w as f32,
glyph.h as f32,
);
crate::texture::draw_texture_ex(
atlas.texture(),
dest.x,
dest.y,
params.color,
crate::texture::DrawTextureParams {
dest_size: Some(vec2(dest.w, dest.h)),
source: Some(source),
rotation: angle_rad,
pivot: Option::Some(vec2(dest.x, dest.y)),
..Default::default()
},
);
}
}
pub fn get_text_center(
text: &str,
font: Option<Font>,
font_size: u16,
font_scale: f32,
rotation: f32,
) -> crate::Vec2 {
let measure = measure_text(text, font, font_size, font_scale);
let x_center = measure.width / 2.0 * rotation.cos() + measure.height / 2.0 * rotation.sin();
let y_center = measure.width / 2.0 * rotation.sin() - measure.height / 2.0 * rotation.cos();
crate::Vec2::new(x_center, y_center)
}
#[derive(Debug, Clone, Copy)]
pub struct TextDimensions {
pub width: f32,
pub height: f32,
pub offset_y: f32,
}
pub fn measure_text(
text: &str,
font: Option<Font>,
font_size: u16,
font_scale: f32,
) -> TextDimensions {
let font = get_context()
.fonts_storage
.get_font_mut(font.unwrap_or(Font::default()));
font.measure_text(text, font_size, font_scale, font_scale)
}
pub(crate) struct FontsStorage {
fonts: Vec<FontInternal>,
}
impl FontsStorage {
pub(crate) fn new(ctx: &mut miniquad::Context) -> FontsStorage {
let atlas = Rc::new(RefCell::new(Atlas::new(ctx, miniquad::FilterMode::Linear)));
let default_font =
FontInternal::load_from_bytes(atlas, include_bytes!("ProggyClean.ttf")).unwrap();
FontsStorage {
fonts: vec![default_font],
}
}
fn make_font(&mut self, font_internal: FontInternal) -> Font {
self.fonts.push(font_internal);
Font(self.fonts.len() - 1)
}
fn get_font_mut(&mut self, font: Font) -> &mut FontInternal {
&mut self.fonts[font.0]
}
}
pub fn camera_font_scale(world_font_size: f32) -> (u16, f32, f32) {
let context = get_context();
let (scr_w, scr_h) = get_quad_context().screen_size();
let cam_space = context
.projection_matrix()
.inverse()
.transform_vector3(vec3(2., 2., 0.));
let (cam_w, cam_h) = (cam_space.x.abs(), cam_space.y.abs());
let screen_font_size = world_font_size * scr_h / cam_h;
let font_size = screen_font_size as u16;
(font_size, cam_h / scr_h, scr_h / scr_w * cam_w / cam_h)
}