use glyph_brush::GlyphPositioner;
use glyph_brush::{self, FontId, Layout, Section, Text as GbText};
pub use glyph_brush::{ab_glyph::PxScale, GlyphBrush, HorizontalAlign as Align};
use std::borrow::Cow;
use std::cell::RefCell;
use std::convert::TryFrom;
use std::fmt;
use std::io::Read;
use std::path;
use std::rc::Rc;
use super::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Font {
font_id: FontId,
}
#[derive(Clone, Debug)]
pub struct FontCache {
glyph_brush: Rc<RefCell<GlyphBrush<DrawParam>>>,
}
impl FontCache {
pub fn dimensions(&self, text: &Text) -> Rect {
text.calculate_dimensions(&mut self.glyph_brush.borrow_mut())
}
}
#[derive(Clone, Debug)]
pub struct TextFragment {
pub text: String,
pub color: Option<Color>,
pub font: Option<Font>,
pub scale: Option<PxScale>,
}
impl Default for TextFragment {
fn default() -> Self {
TextFragment {
text: "".into(),
color: None,
font: None,
scale: None,
}
}
}
impl TextFragment {
pub fn new<T: Into<Self>>(text: T) -> Self {
text.into()
}
pub fn color<C: Into<Color>>(mut self, color: C) -> TextFragment {
self.color = Some(color.into());
self
}
pub fn font(mut self, font: Font) -> TextFragment {
self.font = Some(font);
self
}
pub fn scale<S: Into<PxScale>>(mut self, scale: S) -> TextFragment {
self.scale = Some(scale.into());
self
}
}
impl<'a> From<&'a str> for TextFragment {
fn from(text: &'a str) -> TextFragment {
TextFragment {
text: text.to_owned(),
..Default::default()
}
}
}
impl From<char> for TextFragment {
fn from(ch: char) -> TextFragment {
TextFragment {
text: ch.to_string(),
..Default::default()
}
}
}
impl From<String> for TextFragment {
fn from(text: String) -> TextFragment {
TextFragment {
text,
..Default::default()
}
}
}
impl<T> From<(T, Font, f32)> for TextFragment
where
T: Into<TextFragment>,
{
fn from((text, font, scale): (T, Font, f32)) -> TextFragment {
text.into().font(font).scale(PxScale::from(scale))
}
}
#[derive(Clone, Debug, Default)]
struct CachedMetrics {
string: Option<String>,
width: Option<f32>,
height: Option<f32>,
glyph_positions: Vec<mint::Point2<f32>>,
}
#[derive(Debug, Clone)]
pub struct Text {
fragments: Vec<TextFragment>,
blend_mode: Option<BlendMode>,
filter_mode: FilterMode,
bounds: Point2,
layout: Layout<glyph_brush::BuiltInLineBreaker>,
font_id: FontId,
font_scale: PxScale,
cached_metrics: RefCell<CachedMetrics>,
}
impl Default for Text {
fn default() -> Self {
Text {
fragments: Vec::new(),
blend_mode: None,
filter_mode: FilterMode::Linear,
bounds: Point2::new(f32::INFINITY, f32::INFINITY),
layout: Layout::default(),
font_id: FontId::default(),
font_scale: PxScale::from(Font::DEFAULT_FONT_SCALE),
cached_metrics: RefCell::new(CachedMetrics::default()),
}
}
}
impl Text {
pub fn new<F>(fragment: F) -> Text
where
F: Into<TextFragment>,
{
let mut text = Text::default();
let _ = text.add(fragment);
text
}
pub fn add<F>(&mut self, fragment: F) -> &mut Text
where
F: Into<TextFragment>,
{
self.fragments.push(fragment.into());
self.invalidate_cached_metrics();
self
}
pub fn fragments(&self) -> &[TextFragment] {
&self.fragments
}
pub fn fragments_mut(&mut self) -> &mut [TextFragment] {
self.invalidate_cached_metrics();
&mut self.fragments
}
pub fn set_bounds<P>(&mut self, bounds: P, alignment: Align) -> &mut Text
where
P: Into<mint::Point2<f32>>,
{
self.bounds = Point2::from(bounds.into());
if self.bounds.x == f32::INFINITY {
self.layout = Layout::default();
} else {
self.layout = self.layout.h_align(alignment);
}
self.invalidate_cached_metrics();
self
}
pub fn set_font(&mut self, font: Font, font_scale: PxScale) -> &mut Text {
self.font_id = font.font_id;
self.font_scale = font_scale;
self.invalidate_cached_metrics();
self
}
fn generate_varied_section(&self, relative_dest: Point2, color: Option<Color>) -> Section {
let sections: Vec<GbText> = self
.fragments
.iter()
.map(|fragment| {
let color = fragment.color.or(color).unwrap_or(Color::WHITE);
let font_id = fragment
.font
.map(|font| font.font_id)
.unwrap_or(self.font_id);
let scale = fragment.scale.unwrap_or(self.font_scale);
GbText::default()
.with_text(&fragment.text)
.with_font_id(font_id)
.with_scale(scale)
.with_color(<[f32; 4]>::from(color))
})
.collect();
let relative_dest_x = {
let mut dest_x = relative_dest.x;
if self.bounds.x != f32::INFINITY {
use glyph_brush::Layout::Wrap;
match self.layout {
Wrap {
h_align: Align::Center,
..
} => dest_x += self.bounds.x * 0.5,
Wrap {
h_align: Align::Right,
..
} => dest_x += self.bounds.x,
_ => (),
}
}
dest_x
};
let relative_dest = (relative_dest_x, relative_dest.y);
Section {
screen_position: relative_dest,
bounds: (self.bounds.x, self.bounds.y),
layout: self.layout,
text: sections,
}
}
fn invalidate_cached_metrics(&mut self) {
if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
*metrics = CachedMetrics::default();
return;
}
warn!("Cached metrics RefCell has been poisoned.");
self.cached_metrics = RefCell::new(CachedMetrics::default());
}
pub fn contents(&self) -> String {
if let Ok(metrics) = self.cached_metrics.try_borrow() {
if let Some(ref string) = metrics.string {
return string.clone();
}
}
let string_accm: String = self
.fragments
.iter()
.map(|frag| frag.text.as_str())
.collect();
if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
metrics.string = Some(string_accm.clone());
}
string_accm
}
fn calculate_glyph_positions(
&self,
gb: &mut GlyphBrush<DrawParam>,
) -> std::cell::Ref<Vec<mint::Point2<f32>>> {
if let Ok(metrics) = self.cached_metrics.try_borrow() {
if !metrics.glyph_positions.is_empty() {
return std::cell::Ref::map(metrics, |metrics| &metrics.glyph_positions);
}
}
let glyph_positions: Vec<mint::Point2<f32>> = {
let varied_section = self.generate_varied_section(Point2::new(0.0, 0.0), None);
use glyph_brush::GlyphCruncher;
gb.glyphs(varied_section)
.map(|glyph| glyph.glyph.position)
.map(|pos| mint::Point2 { x: pos.x, y: pos.y })
.collect()
};
if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
metrics.glyph_positions = glyph_positions;
} else {
panic!();
}
if let Ok(metrics) = self.cached_metrics.try_borrow() {
std::cell::Ref::map(metrics, |metrics| &metrics.glyph_positions)
} else {
panic!()
}
}
pub fn glyph_positions(&self, context: &Context) -> std::cell::Ref<Vec<mint::Point2<f32>>> {
self.calculate_glyph_positions(&mut context.gfx_context.glyph_brush.borrow_mut())
}
fn calculate_dimensions(&self, gb: &mut GlyphBrush<DrawParam>) -> Rect {
if let Ok(metrics) = self.cached_metrics.try_borrow() {
if let (Some(width), Some(height)) = (metrics.width, metrics.height) {
return Rect {
x: 0.0,
y: 0.0,
w: width,
h: height,
};
}
}
let mut max_width = 0.0;
let mut max_height = 0.0;
{
let varied_section = self.generate_varied_section(Point2::new(0.0, 0.0), None);
use glyph_brush::GlyphCruncher;
if let Some(bounds) = gb.glyph_bounds(varied_section) {
max_width = bounds.width().ceil();
max_height = bounds.height().ceil();
}
}
if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
metrics.width = Some(max_width);
metrics.height = Some(max_height);
}
Rect {
x: 0.0,
y: 0.0,
w: max_width,
h: max_height,
}
}
pub fn dimensions(&self, context: &Context) -> Rect {
self.calculate_dimensions(&mut context.gfx_context.glyph_brush.borrow_mut())
}
pub fn width(&self, context: &Context) -> f32 {
self.dimensions(context).w
}
pub fn height(&self, context: &Context) -> f32 {
self.dimensions(context).h
}
}
impl Drawable for Text {
fn draw(
&self,
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
param: DrawParam,
) -> GameResult {
queue_text(ctx, self, Point2::new(0.0, 0.0), Some(param.color));
draw_queued_text(ctx, quad_ctx, param, self.blend_mode, self.filter_mode)
}
fn dimensions(&self, ctx: &mut Context) -> Option<Rect> {
Some(self.dimensions(ctx))
}
fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
self.blend_mode = mode;
}
fn blend_mode(&self) -> Option<BlendMode> {
self.blend_mode
}
}
impl Font {
pub const DEFAULT_FONT_SCALE: f32 = 16.0;
pub fn new<P>(context: &mut Context, path: P) -> GameResult<Font>
where
P: AsRef<path::Path> + fmt::Debug,
{
use crate::filesystem;
let mut stream = filesystem::open(context, path.as_ref())?;
let mut buf = Vec::new();
let _ = stream.read_to_end(&mut buf)?;
Font::new_glyph_font_bytes(context, &buf)
}
pub fn new_glyph_font_bytes(context: &mut Context, bytes: &[u8]) -> GameResult<Self> {
let font = glyph_brush::ab_glyph::FontArc::try_from_vec(bytes.to_vec()).unwrap();
let font_id = context.gfx_context.glyph_brush.borrow_mut().add_font(font);
Ok(Font { font_id })
}
pub(crate) fn default_font_bytes() -> &'static [u8] {
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/resources/LiberationMono-Regular.ttf"
))
}
}
impl Default for Font {
fn default() -> Self {
Font { font_id: FontId(0) }
}
}
pub fn font_cache(context: &Context) -> FontCache {
FontCache {
glyph_brush: context.gfx_context.glyph_brush.clone(),
}
}
pub fn queue_text<P>(context: &mut Context, batch: &Text, relative_dest: P, color: Option<Color>)
where
P: Into<mint::Point2<f32>>,
{
let p = Point2::from(relative_dest.into());
let varied_section = batch.generate_varied_section(p, color);
context
.gfx_context
.glyph_brush
.borrow_mut()
.queue(varied_section);
}
pub fn queue_text_raw<'a, S, G>(context: &mut Context, section: S, custom_layout: Option<&G>)
where
S: Into<Cow<'a, Section<'a>>>,
G: GlyphPositioner,
{
let brush = &mut context.gfx_context.glyph_brush.borrow_mut();
match custom_layout {
Some(layout) => brush.queue_custom_layout(section, layout),
None => brush.queue(section),
}
}
pub fn draw_queued_text<D>(
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
param: D,
blend: Option<BlendMode>,
filter: FilterMode,
) -> GameResult
where
D: Into<DrawParam>,
{
let param: DrawParam = param.into();
let gb = &mut ctx.gfx_context.glyph_brush;
let gc = &ctx.gfx_context.glyph_cache.texture;
let action = gb.borrow_mut().process_queued(
|rect, tex_data| {
let mut tex_data_chunks: Vec<u8> = vec![255; tex_data.len() * 4];
for i in 0..tex_data.len() {
tex_data_chunks[i * 4 + 3] = tex_data[i];
}
update_texture(quad_ctx, gc, rect, &tex_data_chunks[..])
},
to_vertex,
);
match action {
Ok(glyph_brush::BrushAction::ReDraw) => {
let spritebatch = ctx.gfx_context.glyph_state.clone();
let spritebatch = &mut *spritebatch.borrow_mut();
spritebatch.set_blend_mode(blend);
spritebatch.set_filter(filter);
draw(ctx, quad_ctx, &*spritebatch, param)?;
}
Ok(glyph_brush::BrushAction::Draw(drawparams)) => {
let spritebatch = ctx.gfx_context.glyph_state.clone();
let spritebatch = &mut *spritebatch.borrow_mut();
spritebatch.clear();
spritebatch.set_blend_mode(blend);
spritebatch.set_filter(filter);
for p in &drawparams {
let _ = spritebatch.add(*p);
}
draw(ctx, quad_ctx, &*spritebatch, param)?;
}
Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
let (new_width, new_height) = suggested;
let data = vec![255; 4 * new_width as usize * new_height as usize];
let new_glyph_cache = Image::from_rgba8(
ctx,
quad_ctx,
u16::try_from(new_width).unwrap(),
u16::try_from(new_height).unwrap(),
&data,
)?;
ctx.gfx_context.glyph_cache = new_glyph_cache.clone();
let spritebatch = ctx.gfx_context.glyph_state.clone();
let spritebatch = &mut *spritebatch.borrow_mut();
let _ = spritebatch.set_image(new_glyph_cache);
ctx.gfx_context
.glyph_brush
.borrow_mut()
.resize_texture(new_width, new_height);
}
}
Ok(())
}
fn update_texture(
ctx: &mut miniquad::Context,
texture: &miniquad::Texture,
rect: glyph_brush::Rectangle<u32>,
tex_data: &[u8],
) {
let offset = [
i32::try_from(rect.min[0]).unwrap(),
i32::try_from(rect.min[1]).unwrap(),
];
let size = [
i32::try_from(rect.width()).unwrap(),
i32::try_from(rect.height()).unwrap(),
];
texture.update_texture_part(ctx, offset[0], offset[1], size[0], size[1], tex_data);
}
fn to_vertex(v: glyph_brush::GlyphVertex) -> DrawParam {
let src_rect = Rect {
x: v.tex_coords.min.x,
y: v.tex_coords.min.y,
w: v.tex_coords.max.x - v.tex_coords.min.x,
h: v.tex_coords.max.y - v.tex_coords.min.y,
};
let dest_pt = Point2::new(v.pixel_coords.min.x, v.pixel_coords.min.y);
DrawParam::default()
.src(src_rect)
.dest(dest_pt)
.color(v.extra.color.into())
}