use std::cell::RefCell;
use std::fs;
use std::path::Path;
use glyph_brush::rusttype::{Rect, Scale};
use glyph_brush::{BrushAction, BrushError, FontId, GlyphCruncher, GlyphVertex, Section};
use crate::error::Result;
use crate::graphics::opengl::GLDevice;
use crate::graphics::{
self, ActiveTexture, DrawParams, Drawable, Rectangle, Texture, TextureFormat,
};
use crate::Context;
#[derive(Clone)]
pub(crate) struct FontQuad {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
u1: f32,
v1: f32,
u2: f32,
v2: f32,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Font {
id: FontId,
}
impl Font {
pub fn new<P>(ctx: &mut Context, path: P) -> Result<Font>
where
P: AsRef<Path>,
{
let font_bytes = fs::read(path)?;
let id = ctx.graphics.font_cache.add_font_bytes(font_bytes);
Ok(Font { id })
}
pub fn from_file_data(ctx: &mut Context, data: &'static [u8]) -> Font {
let id = ctx.graphics.font_cache.add_font_bytes(data);
Font { id }
}
#[deprecated(
since = "0.2.14",
note = "Renamed to `from_file_data` for consistency with Texture and Sound."
)]
#[allow(missing_docs)]
#[inline]
pub fn from_data(ctx: &mut Context, data: &'static [u8]) -> Font {
Font::from_file_data(ctx, data)
}
}
impl Default for Font {
fn default() -> Font {
Font {
id: FontId::default(),
}
}
}
pub struct Text {
content: String,
font: Font,
size: Scale,
quads: RefCell<Vec<FontQuad>>,
}
impl Text {
pub fn new<S>(content: S, font: Font, size: f32) -> Text
where
S: Into<String>,
{
let content = content.into();
Text {
content,
font,
size: Scale::uniform(size),
quads: RefCell::new(Vec::new()),
}
}
pub fn content(&self) -> &str {
&self.content
}
pub fn set_content<S>(&mut self, content: S)
where
S: Into<String>,
{
self.content = content.into();
}
pub fn get_bounds(&self, ctx: &mut Context) -> Option<Rectangle> {
ctx.graphics
.font_cache
.pixel_bounds(self.build_section())
.map(|r| {
let x = r.min.x as f32;
let y = r.min.y as f32;
let width = r.width() as f32;
let height = r.height() as f32;
Rectangle::new(x, y, width, height)
})
}
pub fn font(&self) -> &Font {
&self.font
}
pub fn set_font(&mut self, font: Font) {
self.font = font;
}
pub fn size(&self) -> f32 {
self.size.x
}
pub fn set_size(&mut self, size: f32) {
self.size = Scale::uniform(size);
}
fn build_section(&self) -> Section {
Section {
text: &self.content,
scale: self.size,
font_id: self.font.id,
..Section::default()
}
}
fn check_for_update(&self, ctx: &mut Context) {
ctx.graphics.font_cache.queue(self.build_section());
let screen_dimensions = (
graphics::get_internal_width(ctx) as u32,
graphics::get_internal_height(ctx) as u32,
);
let texture_ref = &mut ctx.graphics.font_cache_texture;
let device_ref = &mut ctx.gl;
let action = loop {
let attempted_action = ctx.graphics.font_cache.process_queued(
screen_dimensions,
|rect, data| update_texture(device_ref, texture_ref, rect, data),
|v| glyph_to_quad(&v),
);
match attempted_action {
Ok(action) => break action,
Err(BrushError::TextureTooSmall { suggested, .. }) => {
let (width, height) = suggested;
*texture_ref = Texture::from_handle(device_ref.new_texture(
width as i32,
height as i32,
TextureFormat::Rgba,
));
ctx.graphics.font_cache.resize_texture(width, height);
}
}
};
if let BrushAction::Draw(new_quads) = action {
*self.quads.borrow_mut() = new_quads;
}
}
}
impl Drawable for Text {
fn draw<P>(&self, ctx: &mut Context, params: P)
where
P: Into<DrawParams>,
{
let params = params.into();
self.check_for_update(ctx);
graphics::set_texture_ex(ctx, ActiveTexture::FontCache);
for quad in self.quads.borrow().iter() {
graphics::push_quad(
ctx, quad.x1, quad.y1, quad.x2, quad.y2, quad.u1, quad.v1, quad.u2, quad.v2,
¶ms,
);
}
}
}
fn update_texture(gl: &mut GLDevice, texture: &Texture, rect: Rect<u32>, data: &[u8]) {
let mut padded_data = Vec::with_capacity(data.len() * 4);
for a in data {
padded_data.push(255);
padded_data.push(255);
padded_data.push(255);
padded_data.push(*a);
}
gl.set_texture_data(
&texture.handle,
&padded_data,
rect.min.x as i32,
rect.min.y as i32,
rect.width() as i32,
rect.height() as i32,
TextureFormat::Rgba,
);
}
fn glyph_to_quad(v: &GlyphVertex) -> FontQuad {
FontQuad {
x1: v.pixel_coords.min.x as f32,
y1: v.pixel_coords.min.y as f32,
x2: v.pixel_coords.max.x as f32,
y2: v.pixel_coords.max.y as f32,
u1: v.tex_coords.min.x as f32,
v1: v.tex_coords.min.y as f32,
u2: v.tex_coords.max.x as f32,
v2: v.tex_coords.max.y as f32,
}
}