use glyph_brush::GlyphPositioner;
use glyph_brush::{self, Layout, SectionText, VariedSection};
pub use glyph_brush::{rusttype::Scale, FontId, HorizontalAlign as Align};
use mint;
use std::borrow::Cow;
use std::cell::RefCell;
use std::f32;
use std::fmt;
use std::io::Read;
use std::path;
use super::*;
pub const DEFAULT_FONT_SCALE: f32 = 16.0;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Font {
font_id: FontId,
}
#[derive(Clone, Debug)]
pub struct TextFragment {
pub text: String,
pub color: Option<Color>,
pub font: Option<Font>,
pub scale: Option<Scale>,
}
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(mut self, color: Color) -> TextFragment {
self.color = Some(color);
self
}
pub fn font(mut self, font: Font) -> TextFragment {
self.font = Some(font);
self
}
pub fn scale(mut self, scale: Scale) -> TextFragment {
self.scale = Some(scale);
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(Scale::uniform(scale))
}
}
#[derive(Clone, Debug)]
struct CachedMetrics {
string: Option<String>,
width: Option<u32>,
height: Option<u32>,
}
impl Default for CachedMetrics {
fn default() -> CachedMetrics {
CachedMetrics {
string: None,
width: None,
height: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Text {
fragments: Vec<TextFragment>,
blend_mode: Option<BlendMode>,
bounds: Point2,
layout: Layout<glyph_brush::BuiltInLineBreaker>,
font_id: FontId,
font_scale: Scale,
cached_metrics: RefCell<CachedMetrics>,
}
impl Default for Text {
fn default() -> Self {
Text {
fragments: Vec::new(),
blend_mode: None,
bounds: Point2::new(f32::INFINITY, f32::INFINITY),
layout: Layout::default(),
font_id: FontId::default(),
font_scale: Scale::uniform(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] {
&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: Scale) -> &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>,
) -> VariedSection {
let sections: Vec<SectionText> = self
.fragments
.iter()
.map(|fragment| {
let color = fragment.color.or(color).unwrap_or(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);
SectionText {
text: &fragment.text,
color: <[f32; 4]>::from(color),
font_id,
scale,
}
})
.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);
VariedSection {
screen_position: relative_dest,
bounds: (self.bounds.x, self.bounds.y),
layout: self.layout,
text: sections,
..Default::default()
}
}
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_dimensions(&self, context: &mut Context) -> (u32, u32) {
let mut max_width = 0;
let mut max_height = 0;
{
let varied_section = self.generate_varied_section(Point2::new(0.0, 0.0), None);
use glyph_brush::GlyphCruncher;
let glyphs = context.gfx_context.glyph_brush.glyphs(varied_section);
for positioned_glyph in glyphs {
if let Some(rect) = positioned_glyph.pixel_bounding_box() {
let font = positioned_glyph.font().expect("Glyph doesn't have a font");
let v_metrics = font.v_metrics(positioned_glyph.scale());
let max_y = positioned_glyph.position().y + positioned_glyph.scale().y
- v_metrics.ascent;
let max_y = max_y.ceil() as u32;
max_width = std::cmp::max(max_width, rect.max.x as u32);
max_height = std::cmp::max(max_height, max_y);
}
}
}
let (width, height) = (max_width, max_height);
if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
metrics.width = Some(width);
metrics.height = Some(height);
}
(width, height)
}
pub fn dimensions(&self, context: &mut Context) -> (u32, u32) {
if let Ok(metrics) = self.cached_metrics.try_borrow() {
if let (Some(width), Some(height)) = (metrics.width, metrics.height) {
return (width, height);
}
}
self.calculate_dimensions(context)
}
pub fn width(&self, context: &mut Context) -> u32 {
self.dimensions(context).0
}
pub fn height(&self, context: &mut Context) -> u32 {
self.dimensions(context).1
}
}
impl Drawable for Text {
fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
queue_text(ctx, self, Point2::new(0.0, 0.0), Some(param.color));
draw_queued_text(ctx, param)
}
fn dimensions(&self, ctx: &mut Context) -> Option<Rect> {
let (w, h) = self.dimensions(ctx);
Some(Rect {
w: w as _,
h: h as _,
x: 0.0,
y: 0.0,
})
}
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 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 v = bytes.to_vec();
let font_id = context.gfx_context.glyph_brush.add_font_bytes(v);
Ok(Font { font_id })
}
pub(crate) fn default_font_bytes() -> &'static [u8] {
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/resources/DejaVuSerif.ttf"
))
}
}
impl Default for Font {
fn default() -> Self {
Font { font_id: FontId(0) }
}
}
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.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, VariedSection<'a>>>,
G: GlyphPositioner,
{
let brush = &mut context.gfx_context.glyph_brush;
match custom_layout {
Some(layout) => brush.queue_custom_layout(section, layout),
None => brush.queue(section),
}
}
pub fn draw_queued_text<D>(ctx: &mut Context, param: D) -> GameResult
where
D: Into<DrawParam>,
{
let param: DrawParam = param.into();
let screen_rect = screen_coordinates(ctx);
let (screen_w, screen_h) = (screen_rect.w, screen_rect.h);
let gb = &mut ctx.gfx_context.glyph_brush;
let encoder = &mut ctx.gfx_context.encoder;
let gc = &ctx.gfx_context.glyph_cache.texture_handle;
let backend = &ctx.gfx_context.backend_spec;
let action = gb.process_queued(
(screen_w as u32, screen_h as u32),
|rect, tex_data| update_texture::<GlBackendSpec>(backend, encoder, gc, rect, tex_data),
to_vertex,
);
match action {
Ok(glyph_brush::BrushAction::ReDraw) => {
let s = ctx.gfx_context.glyph_state.clone();
draw(ctx, &*s.borrow(), param)?;
}
Ok(glyph_brush::BrushAction::Draw(drawparams)) => {
let spritebatch = ctx.gfx_context.glyph_state.clone();
let spritebatch = &mut *spritebatch.borrow_mut();
spritebatch.clear();
for p in &drawparams {
let _ = spritebatch.add(*p);
}
draw(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, new_width as u16, new_height as u16, &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
.resize_texture(new_width, new_height);
}
}
Ok(())
}
fn update_texture<B>(
backend: &B,
encoder: &mut gfx::Encoder<B::Resources, B::CommandBuffer>,
texture: &gfx::handle::RawTexture<B::Resources>,
rect: glyph_brush::rusttype::Rect<u32>,
tex_data: &[u8],
) where
B: BackendSpec,
{
let offset = [rect.min.x as u16, rect.min.y as u16];
let size = [rect.width() as u16, rect.height() as u16];
let info = texture::ImageInfoCommon {
xoffset: offset[0],
yoffset: offset[1],
zoffset: 0,
width: size[0],
height: size[1],
depth: 0,
format: (),
mipmap: 0,
};
let tex_data_chunks: Vec<[u8; 4]> = tex_data.iter().map(|c| [255, 255, 255, *c]).collect();
let typed_tex = backend.raw_to_typed_texture(texture.clone());
encoder
.update_texture::<<super::BuggoSurfaceFormat as gfx::format::Formatted>::Surface, super::BuggoSurfaceFormat>(
&typed_tex, None, info, &tex_data_chunks,
)
.unwrap();
}
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 as f32, v.pixel_coords.min.y as f32);
DrawParam::default()
.src(src_rect)
.dest(dest_pt)
.color(v.color.into())
}
#[cfg(test)]
mod tests {
}