Skip to content

Commit

Permalink
Initial integration: rewrote Font::TTFFont and Text.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ratysz committed Apr 25, 2018
1 parent 99225cf commit 89d2ba7
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 98 deletions.
2 changes: 1 addition & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ impl Context {
let audio_context = audio::AudioContext::new()?;
let event_context = sdl_context.event()?;
let timer_context = timer::TimeContext::new();
let font = graphics::Font::default_font()?;
let backend_spec = graphics::GlBackendSpec::from(conf.backend);
let graphics_context = graphics::GraphicsContext::new(
&video,
Expand All @@ -111,6 +110,7 @@ impl Context {
backend_spec,
debug_id,
)?;
let font = graphics::Font::default_font(&graphics_context.factory)?;
let gamepad_context = input::GamepadContext::new(&sdl_context)?;
let mouse_context = mouse::MouseContext::new();

Expand Down
178 changes: 81 additions & 97 deletions src/graphics/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::path;
use std::collections::BTreeMap;
use std::io::Read;

use gfx_device_gl::{Factory, Resources};
use gfx_glyph::{GlyphBrush, GlyphBrushBuilder};
use rusttype;
use image;
use image::RgbaImage;
Expand All @@ -12,7 +14,7 @@ use super::*;

/// A font that defines the shape of characters drawn on the screen.
/// Can be created from a .ttf file or from an image (bitmap fonts).
#[derive(Clone)]
//#[derive(Clone)]
pub enum Font {
/// A truetype font
TTFFont {
Expand All @@ -22,6 +24,8 @@ pub enum Font {
points: u32,
/// Scale information for the font
scale: rusttype::Scale,
/// `gfx_glyph` glyph cache
glyph_brush: GlyphBrush<'static, Resources, Factory>,
},
/// A bitmap font where letter widths are infered
BitmapFontVariant(BitmapFont),
Expand Down Expand Up @@ -84,11 +88,17 @@ impl Font {

// Get the proper DPI to scale font size accordingly
let (_diag_dpi, x_dpi, y_dpi) = context.gfx_context.dpi;
Font::from_bytes(&name, &buf, points, (x_dpi, y_dpi))
Font::from_bytes(&context.gfx_context.factory, &name, &buf, points, (x_dpi, y_dpi))
}

/// Loads a new TTF font from data copied out of the given buffer.
pub fn from_bytes(name: &str, bytes: &[u8], points: u32, dpi: (f32, f32)) -> GameResult<Font> {
pub fn from_bytes(
gfx_factory: &Factory,
name: &str,
bytes: &[u8],
points: u32,
dpi: (f32, f32)
) -> GameResult<Font> {
let font_collection_err = &|_| GameError::ResourceLoadError(format!(
"Could not load font collection for \
font {:?}",
Expand All @@ -106,11 +116,14 @@ impl Font {
let (x_dpi, y_dpi) = dpi;
// println!("DPI: {}, {}", x_dpi, y_dpi);
let scale = display_independent_scale(points, x_dpi, y_dpi);
let glyph_brush = GlyphBrushBuilder::using_font_bytes(bytes.to_vec())
.build(gfx_factory.clone());

Ok(Font::TTFFont {
font,
points,
scale,
glyph_brush,
})
}

Expand Down Expand Up @@ -208,15 +221,15 @@ impl Font {

/// Returns a baked-in default font: currently DejaVuSerif.ttf
/// Note it does create a new `Font` object with every call.
pub fn default_font() -> GameResult<Self> {
pub fn default_font(gfx_factory: &Factory) -> GameResult<Self> {
let size = 16;
let buf = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/resources/DejaVuSerif.ttf"
));
// BUGGO: fix DPI. Get from Context? If we do that we can basically
// just make Context always keep the default Font itself... hmm.
Font::from_bytes("default", &buf[..], size, (75.0, 75.0))
Font::from_bytes(gfx_factory, "default", &buf[..], size, (75.0, 75.0))
}

/// Get the height of the Font in pixels.
Expand Down Expand Up @@ -316,9 +329,10 @@ impl fmt::Debug for Font {
/// Drawable text created from a `Font`.
#[derive(Clone)]
pub struct Text {
texture: Image,
texture: Option<Image>,
contents: String,
blend_mode: Option<BlendMode>,
glyph_brush: Option<&'static GlyphBrush<'static, Resources, Factory>>,
}

/// Compute a scale for a font of a given size.
Expand Down Expand Up @@ -350,73 +364,6 @@ fn text_width(glyphs: &[rusttype::PositionedGlyph]) -> f32 {
.unwrap_or(0.0)
}

fn render_ttf(
context: &mut Context,
text: &str,
font: &rusttype::Font<'static>,
scale: rusttype::Scale,
) -> GameResult<Text> {
// Ripped almost wholesale from
// https://github.com/dylanede/rusttype/blob/master/examples/simple.rs

let text_height_pixels = scale.y.ceil() as usize;
let v_metrics = font.v_metrics(scale);
let offset = rusttype::point(0.0, v_metrics.ascent);
// Then turn them into an array of positioned glyphs...
// `layout()` turns an abstract glyph, which contains no concrete
// size or position information, into a PositionedGlyph, which does.
let glyphs: Vec<rusttype::PositionedGlyph> = font.layout(text, scale, offset).collect();
// If the string is empty or only whitespace, we end up trying to create a 0-width
// texture which is invalid. Instead we create a texture 1 texel wide, with everything
// set to zero, which probably isn't ideal but is 100% consistent and doesn't require
// special-casing things like get_filter().
// See issue #109
let text_width_pixels = cmp::max(text_width(&glyphs).ceil() as usize, 1);
let bytes_per_pixel = 4;
let mut pixel_data = vec![0; text_width_pixels * text_height_pixels * bytes_per_pixel];
let pitch = text_width_pixels * bytes_per_pixel;

// Now we actually render the glyphs to a bitmap...
for g in glyphs {
if let Some(bb) = g.pixel_bounding_box() {
// v is the amount of the pixel covered
// by the glyph, in the range 0.0 to 1.0
g.draw(|x, y, v| {
let c = (v * 255.0) as u8;
let x = x as i32 + bb.min.x;
let y = y as i32 + bb.min.y;
// There's still a possibility that the glyph clips the boundaries of the bitmap
if x >= 0 && x < text_width_pixels as i32 && y >= 0 && y < text_height_pixels as i32
{
let x = x as usize * bytes_per_pixel;
let y = y as usize;
pixel_data[(x + y * pitch)] = 255;
pixel_data[(x + y * pitch + 1)] = 255;
pixel_data[(x + y * pitch + 2)] = 255;
pixel_data[(x + y * pitch + 3)] = c;
}
})
}
}

// Copy the bitmap into an image, and we're basically done!
assert!(text_width_pixels < u16::MAX as usize);
assert!(text_height_pixels < u16::MAX as usize);
let image = Image::from_rgba8(
context,
text_width_pixels as u16,
text_height_pixels as u16,
&pixel_data,
)?;

let text_string = text.to_string();
Ok(Text {
texture: image,
contents: text_string,
blend_mode: None,
})
}

/// Treats src and dst as row-major 2D arrays, and blits the given rect from src to dst.
/// Does no bounds checking or anything; if you feed it invalid bounds it will just panic.
/// Generally, you shouldn't need to use this directly.
Expand Down Expand Up @@ -515,9 +462,10 @@ fn render_dynamic_bitmap(context: &mut Context, text: &str, font: &BitmapFont) -
let text_string = text.to_string();

Ok(Text {
texture: image,
texture: Some(image),
contents: text_string,
blend_mode: None,
glyph_brush: None,
})
}

Expand All @@ -527,23 +475,37 @@ impl Text {
/// Note that this is relatively computationally expensive;
/// if you want to draw text every frame you probably want to save
/// it and only update it when the text changes.
pub fn new(context: &mut Context, text: &str, font: &Font) -> GameResult<Text> {
pub fn new(context: &mut Context, text: &str, font: &'static Font) -> GameResult<Text> {
match *font {
Font::TTFFont {
font: ref f, scale, ..
} => render_ttf(context, text, f, scale),
ref glyph_brush,
..
} => {
Ok(Text {
texture: None,
contents: text.to_string(),
blend_mode: None,
glyph_brush: Some(&glyph_brush),
})
},
Font::BitmapFontVariant(ref font) => render_dynamic_bitmap(context, text, font),
}
}

/// Returns the width of the rendered text, in pixels.
pub fn width(&self) -> u32 {
self.texture.width()
pub fn width(&self) -> Option<u32> {
match self.texture {
Some(ref texture) => Some(texture.width()),
None => None,
}
}

/// Returns the height of the rendered text, in pixels.
pub fn height(&self) -> u32 {
self.texture.height()
pub fn height(&self) -> Option<u32> {
match self.texture {
Some(ref texture) => Some(texture.height()),
None => None,
}
}

/// Returns the string that the text represents.
Expand All @@ -552,42 +514,56 @@ impl Text {
}

/// Returns the dimensions of the rendered text.
pub fn get_dimensions(&self) -> Rect {
self.texture.get_dimensions()
pub fn get_dimensions(&self) -> Option<Rect> {
match self.texture {
Some(ref texture) => Some(texture.get_dimensions()),
None => None,
}
}

/// Get the filter mode for the the rendered text.
pub fn get_filter(&self) -> FilterMode {
self.texture.get_filter()
pub fn get_filter(&self) -> Option<FilterMode> {
match self.texture {
Some(ref texture) => Some(texture.get_filter()),
None => None,
}
}

/// Set the filter mode for the the rendered text.
pub fn set_filter(&mut self, mode: FilterMode) {
self.texture.set_filter(mode);
if let Some(ref mut texture) = self.texture {
texture.set_filter(mode);
}
}

/// Returns a reference to the `Image` contained
/// by the `Text` object.
pub fn get_image(&self) -> &Image {
&self.texture
pub fn get_image(&self) -> Option<&Image> {
self.texture.as_ref()
}

/// Returns a mutable reference to the `Image` contained
/// by the `Text` object.
pub fn get_image_mut(&mut self) -> &mut Image {
&mut self.texture
pub fn get_image_mut(&mut self) -> Option<&mut Image> {
self.texture.as_mut()
}

/// Unwraps the `Image` contained
/// by the `Text` object.
pub fn into_inner(self) -> Image {
pub fn into_inner(self) -> Option<Image> {
self.texture
}
}

impl Drawable for Text {
fn draw_ex(&self, ctx: &mut Context, param: DrawParam) -> GameResult<()> {
draw_ex(ctx, &self.texture, param)
if let Some(ref brush) = self.glyph_brush {
Ok(())
} else if let Some(ref texture) = self.texture {
draw_ex(ctx, texture, param)
} else {
Err(GameError::FontError("Text has no glyph brush nor a rendered bitmap.".into()))
}
}
fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
self.blend_mode = mode;
Expand All @@ -599,11 +575,19 @@ impl Drawable for Text {

impl fmt::Debug for Text {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"<Text: {}x{}, {:p}>",
self.texture.width, self.texture.height, &self
)
if let (Some(w), Some(h)) = (self.width(), self.height()) {
write!(
f,
"<Text: {}x{}, {:p}>",
w, h, &self
)
} else {
write!(
f,
"<Text: , {:p}>",
&self
)
}
}
}

Expand Down

0 comments on commit 89d2ba7

Please sign in to comment.