Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace freetype with rusttype #288

Merged
3 commits merged into from Mar 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ name = "gfx_graphics"


[dependencies]
freetype-rs = "0.6.0"
rusttype = "0.2.0"
gfx = "0.10.1"
piston-shaders_graphics2d = "0.2.0"
piston-gfx_texture = "0.12.0"
Expand Down
86 changes: 54 additions & 32 deletions src/glyph.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
//! Glyph caching

extern crate freetype as ft;
extern crate rusttype as rt;

use std::path::Path;
use std::io;
use std::path::{Path};
use std::collections::hash_map::{ HashMap, Entry };
use graphics::character::{ CharacterCache, Character };
use graphics::types::{FontSize, Scalar};
use self::ft::render_mode::RenderMode;
use { gfx, Texture, TextureSettings };
use gfx::core::factory::CombinedError;

/// An enum to represent various possible run-time errors that may occur.
#[derive(Clone, Debug, PartialEq)]
#[derive(Debug)]
pub enum Error {
/// An error happened when creating a gfx texture.
Texture(CombinedError),
/// An error happened with the FreeType library.
Freetype(ft::error::Error)
/// An io error happened when reading font files.
IoError(io::Error),
}

impl From<CombinedError> for Error {
Expand All @@ -25,30 +25,38 @@ impl From<CombinedError> for Error {
}
}

impl From<ft::error::Error> for Error {
fn from(ft_err: ft::error::Error) -> Self {
Error::Freetype(ft_err)
impl From<io::Error> for Error {
fn from(io_error: io::Error) -> Self {
Error::IoError(io_error)
}
}

/// A struct used for caching rendered font.
/// A struct used for caching a rendered font.
pub struct GlyphCache<R, F> where R: gfx::Resources {
/// The font face.
pub face: ft::Face<'static>,
/// The font.
pub font: rt::Font<'static>,
factory: F,
// Maps from fontsize and character to offset, size and texture.
data: HashMap<(FontSize, char), ([Scalar; 2], [Scalar; 2], Texture<R>)>
}

impl<R, F> GlyphCache<R, F> where R: gfx::Resources {
/// Constructor for a GlyphCache.
pub fn new<P>(font: P, factory: F) -> Result<Self, Error>
where P: AsRef<Path>
{
let freetype = try!(ft::Library::init());
let face = try!(freetype.new_face(font.as_ref(), 0));
pub fn new<P>(font_path: P, factory: F) -> Result<Self, Error>
where P: AsRef<Path> {

use std::io::Read;
use std::fs::File;

let mut file = try!(File::open(font_path));
let mut file_buffer = Vec::new();
try!(file.read_to_end(&mut file_buffer));

let collection = rt::FontCollection::from_bytes(file_buffer);
let font = collection.into_font().unwrap(); // only succeeds if collection consists of one font

Ok(GlyphCache {
face: face,
font: font,
factory: factory,
data: HashMap::new(),
})
Expand All @@ -66,6 +74,8 @@ impl<R, F> CharacterCache for GlyphCache<R, F> where
size: FontSize,
ch: char
) -> Character<'a, Self::Texture> {
let size = ((size as f32) * 1.333).round() as u32 ; // convert points to pixels

match self.data.entry((size, ch)) {
//returning `into_mut()' to get reference with 'a lifetime
Entry::Occupied(v) => {
Expand All @@ -77,31 +87,43 @@ impl<R, F> CharacterCache for GlyphCache<R, F> where
}
}
Entry::Vacant(v) => {
self.face.set_pixel_sizes(0, size).unwrap();
self.face.load_char(ch as usize, ft::face::DEFAULT).unwrap();
let glyph = self.face.glyph().get_glyph().unwrap();
let bitmap_glyph = glyph.to_bitmap(RenderMode::Normal, None).unwrap();
let glyph_size = [glyph.advance_x(), glyph.advance_y()];
// fallback to glyph zero or U+FFFD if glyph is not present
let glyph = self.font.glyph(ch).unwrap_or(self.font.glyph(rt::Codepoint(0)).unwrap_or(self.font.glyph('\u{FFFD}').unwrap()));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite right. As mentioned in the documentation for glyph, it will handle returning glyph zero (which would be rt::GlyphID(0), not rt::Codepoint(0)) for you, as defined by the TrueType standard. The thing you need to check is whether, in the case that glyph zero is returned, that glyph has no shape, in which case you should try rt::Codepoint('\u{FFFD}'). You can check the returned glyph ID with the id method. To check whether there is a shape for the glyph, unfortunately I don't have a direct method for that, but you can query the shape itself or the bounding box on a ScaledGlyph, which will both return None if there is no shape.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small tidbit - I think this line could be refactored to use or_else, i.e.

let glyph = self.font.glyph(ch)
    .or_else(|| self.font.glyph(rt::Codepoint(0)))
    .or_else(|| self.font.glyph('\u{FFFD}'))
    .unwrap();

Using or_else will make the evaluation of self.font.glyph(rt::Codepoint(0)) and self.font.glyph('\u{FFFD}') lazy and perhaps result in a little more efficiency.

Similarly, lines 95 and 97 could probably use unwrap_or_else(|| instead of unwrap_or(

Also, we should probably return some sort of Err instead of unwraping and panicking here.


let glyph = glyph.scaled(rt::Scale::uniform(size as f32));
let h_metrics = glyph.h_metrics();
let bounding_box = glyph.exact_bounding_box().unwrap_or(rt::Rect{min: rt::Point{x: 0.0, y: 0.0}, max: rt::Point{x: 0.0, y: 0.0} });
let glyph = glyph.positioned(rt::point(0.0, 0.0));
let pixel_bounding_box = glyph.pixel_bounding_box().unwrap_or(rt::Rect{min: rt::Point{x: 0, y: 0}, max: rt::Point{x: 0, y: 0} });
let pixel_bb_width = pixel_bounding_box.width();
let pixel_bb_height = pixel_bounding_box.height();

let mut image_buffer = Vec::<u8>::new();
image_buffer.resize((pixel_bb_width * pixel_bb_height) as usize, 0);
glyph.draw(|x, y, v| {
let pos = (x + y * (pixel_bb_width as u32)) as usize;
image_buffer[pos] = (255.0 * v) as u8;
});

let &mut (offset, size, ref texture) = v.insert((
[
bitmap_glyph.left() as f64,
bitmap_glyph.top() as f64
bounding_box.min.x as Scalar,
-pixel_bounding_box.min.y as Scalar,
],
[
(glyph_size[0] >> 16) as f64,
(glyph_size[1] >> 16) as f64
h_metrics.advance_width as Scalar,
0 as Scalar,
],
{
let bitmap = bitmap_glyph.bitmap();
if bitmap.width() == 0 || bitmap.rows() == 0 {
if pixel_bb_width == 0 || pixel_bb_height == 0 {
Texture::empty(&mut self.factory)
.unwrap()
} else {
Texture::from_memory_alpha(
&mut self.factory,
bitmap.buffer(),
bitmap.width() as u32,
bitmap.rows() as u32,
&image_buffer,
pixel_bb_width as u32,
pixel_bb_height as u32,
&TextureSettings::new()
).unwrap()
}
Expand Down