#![warn(missing_docs)]
extern crate libc;
extern crate freetype_sys as freetype;
#[macro_use]
extern crate glium;
use glium::DrawParameters;
use glium::backend::Context;
use glium::backend::Facade;
use std::borrow::Cow;
use std::default::Default;
use std::io::Read;
use std::ops::Deref;
use std::rc::Rc;
pub struct FontTexture {
texture: glium::texture::Texture2d,
character_infos: Vec<(char, CharacterInfos)>,
}
pub struct TextSystem {
context: Rc<Context>,
program: glium::Program,
}
pub struct TextDisplay<F> where F: Deref<Target=FontTexture> {
context: Rc<Context>,
texture: F,
vertex_buffer: Option<glium::VertexBuffer<VertexFormat>>,
index_buffer: Option<glium::IndexBuffer<u16>>,
total_text_width: f32,
is_empty: bool,
}
#[derive(Copy, Clone, Debug)]
struct CharacterInfos {
tex_coords: (f32, f32),
tex_size: (f32, f32),
size: (f32, f32),
height_over_line: f32,
left_padding: f32,
right_padding: f32,
}
struct TextureData {
data: Vec<f32>,
width: u32,
height: u32,
}
impl<'a> glium::texture::Texture2dDataSource<'a> for &'a TextureData {
type Data = f32;
fn into_raw(self) -> glium::texture::RawImage2d<'a, f32> {
glium::texture::RawImage2d {
data: Cow::Borrowed(&self.data),
width: self.width,
height: self.height,
format: glium::texture::ClientFormat::F32,
}
}
}
#[derive(Copy, Clone)]
struct VertexFormat {
position: [f32; 2],
tex_coords: [f32; 2],
}
implement_vertex!(VertexFormat, position, tex_coords);
impl FontTexture {
pub fn new<R, F>(facade: &F, font: R, font_size: u32)
-> Result<FontTexture, ()> where R: Read, F: Facade
{
let library = unsafe {
extern "C" fn alloc_library(_memory: freetype::FT_Memory, size: libc::c_long) -> *mut libc::c_void {
unsafe {
libc::malloc(size as libc::size_t)
}
}
extern "C" fn free_library(_memory: freetype::FT_Memory, block: *mut libc::c_void) {
unsafe {
libc::free(block)
}
}
extern "C" fn realloc_library(_memory: freetype::FT_Memory,
_cur_size: libc::c_long,
new_size: libc::c_long,
block: *mut libc::c_void) -> *mut libc::c_void {
unsafe {
libc::realloc(block, new_size as libc::size_t)
}
}
static mut MEMORY: freetype::FT_MemoryRec = freetype::FT_MemoryRec {
user: 0 as *mut libc::c_void,
alloc: alloc_library,
free: free_library,
realloc: realloc_library,
};
let mut raw = ::std::ptr::null_mut();
if freetype::FT_New_Library(&mut MEMORY, &mut raw) != freetype::FT_Err_Ok {
return Err(());
}
freetype::FT_Add_Default_Modules(raw);
raw
};
let font: Vec<u8> = font.bytes().map(|c| c.unwrap()).collect();
let face: freetype::FT_Face = unsafe {
let mut face = ::std::ptr::null_mut();
let err = freetype::FT_New_Memory_Face(library, font.as_ptr(),
font.len() as freetype::FT_Long, 0, &mut face);
if err == freetype::FT_Err_Ok {
face
} else {
return Err(());
}
};
let characters_list = unsafe {
let mut result = Vec::new();
let mut g: freetype::FT_UInt = std::mem::uninitialized();
let mut c = freetype::FT_Get_First_Char(face, &mut g);
while g != 0 {
result.push(std::mem::transmute(c as u32));
c = freetype::FT_Get_Next_Char(face, c, &mut g);
}
result
};
let (texture_data, chr_infos) = unsafe {
build_font_image(face, characters_list, font_size)
};
let texture = glium::texture::Texture2d::new(facade, &texture_data).unwrap();
Ok(FontTexture {
texture: texture,
character_infos: chr_infos,
})
}
}
impl TextSystem {
pub fn new<F>(facade: &F) -> TextSystem where F: Facade {
TextSystem {
context: facade.get_context().clone(),
program: program!(facade,
140 => {
vertex: "
#version 140
uniform mat4 matrix;
in vec2 position;
in vec2 tex_coords;
out vec2 v_tex_coords;
void main() {
gl_Position = matrix * vec4(position, 0.0, 1.0);
v_tex_coords = tex_coords;
}
",
fragment: "
#version 140
in vec2 v_tex_coords;
out vec4 f_color;
uniform vec4 color;
uniform sampler2D tex;
void main() {
vec4 c = vec4(color.rgb, color.a * texture(tex, v_tex_coords));
if (c.a <= 0.01) {
discard;
} else {
f_color = c;
}
}
"
},
110 => {
vertex: "
#version 110
attribute vec2 position;
attribute vec2 tex_coords;
varying vec2 v_tex_coords;
uniform mat4 matrix;
void main() {
gl_Position = matrix * vec4(position.x, position.y, 0.0, 1.0);
v_tex_coords = tex_coords;
}
",
fragment: "
#version 110
varying vec2 v_tex_coords;
uniform vec4 color;
uniform sampler2D tex;
void main() {
gl_FragColor = vec4(color.rgb, color.a * texture2D(tex, v_tex_coords));
if (gl_FragColor.a <= 0.01) {
discard;
}
}
"
},
).unwrap()
}
}
}
impl<F> TextDisplay<F> where F: Deref<Target=FontTexture> {
pub fn new(system: &TextSystem, texture: F, text: &str) -> TextDisplay<F> {
let mut text_display = TextDisplay {
context: system.context.clone(),
texture: texture,
vertex_buffer: None,
index_buffer: None,
total_text_width: 0.0,
is_empty: true,
};
text_display.set_text(text);
text_display
}
pub fn get_width(&self) -> f32 {
self.total_text_width
}
pub fn set_text(&mut self, text: &str) {
self.is_empty = true;
self.total_text_width = 0.0;
self.vertex_buffer = None;
self.index_buffer = None;
if text.len() == 0 {
return;
}
let mut vertex_buffer_data = Vec::with_capacity(text.len() * 4 * 4);
let mut index_buffer_data = Vec::with_capacity(text.len() * 6);
for character in text.chars() {
let infos = match self.texture.character_infos
.iter().find(|&&(chr, _)| chr == character)
{
Some(infos) => infos,
None => continue
};
let infos = infos.1;
self.is_empty = false;
{
let first_vertex_offset = vertex_buffer_data.len() as u16;
index_buffer_data.push(first_vertex_offset);
index_buffer_data.push(first_vertex_offset + 1);
index_buffer_data.push(first_vertex_offset + 2);
index_buffer_data.push(first_vertex_offset + 2);
index_buffer_data.push(first_vertex_offset + 1);
index_buffer_data.push(first_vertex_offset + 3);
}
self.total_text_width += infos.left_padding;
let left_coord = self.total_text_width;
let right_coord = left_coord + infos.size.0;
let top_coord = infos.height_over_line;
let bottom_coord = infos.height_over_line - infos.size.1;
vertex_buffer_data.push(VertexFormat {
position: [left_coord, top_coord],
tex_coords: [infos.tex_coords.0, infos.tex_coords.1],
});
vertex_buffer_data.push(VertexFormat {
position: [right_coord, top_coord],
tex_coords: [infos.tex_coords.0 + infos.tex_size.0, infos.tex_coords.1],
});
vertex_buffer_data.push(VertexFormat {
position: [left_coord, bottom_coord],
tex_coords: [infos.tex_coords.0, infos.tex_coords.1 + infos.tex_size.1],
});
vertex_buffer_data.push(VertexFormat {
position: [right_coord, bottom_coord],
tex_coords: [
infos.tex_coords.0 + infos.tex_size.0,
infos.tex_coords.1 + infos.tex_size.1
],
});
self.total_text_width = right_coord + infos.right_padding;
}
if !vertex_buffer_data.len() != 0 {
self.vertex_buffer = Some(glium::VertexBuffer::new(&self.context,
&vertex_buffer_data).unwrap());
self.index_buffer = Some(glium::IndexBuffer::new(&self.context,
glium::index::PrimitiveType::TrianglesList,
&index_buffer_data).unwrap());
}
}
}
pub fn draw<F, S: ?Sized, M>(text: &TextDisplay<F>, system: &TextSystem, target: &mut S,
matrix: M, color: (f32, f32, f32, f32))
where S: glium::Surface, M: Into<[[f32; 4]; 4]>,
F: Deref<Target=FontTexture>
{
let matrix = matrix.into();
let &TextDisplay { ref vertex_buffer, ref index_buffer, ref texture, is_empty, .. } = text;
let color = [color.0, color.1, color.2, color.3];
if is_empty || vertex_buffer.is_none() || index_buffer.is_none() {
return;
}
let vertex_buffer = vertex_buffer.as_ref().unwrap();
let index_buffer = index_buffer.as_ref().unwrap();
let uniforms = uniform! {
matrix: matrix,
color: color,
tex: glium::uniforms::Sampler(&texture.texture, glium::uniforms::SamplerBehavior {
magnify_filter: glium::uniforms::MagnifySamplerFilter::Linear,
minify_filter: glium::uniforms::MinifySamplerFilter::Linear,
.. Default::default()
})
};
let params = {
use glium::BlendingFunction::Addition;
use glium::LinearBlendingFactor::*;
let blending_function = Addition {
source: SourceAlpha,
destination: OneMinusSourceAlpha
};
let blend = glium::Blend {
color: blending_function,
alpha: blending_function,
constant_value: (1.0, 1.0, 1.0, 1.0),
};
DrawParameters {
blend: blend,
.. Default::default()
}
};
target.draw(vertex_buffer, index_buffer, &system.program, &uniforms,
¶ms).unwrap();
}
unsafe fn build_font_image(face: freetype::FT_Face, characters_list: Vec<char>, font_size: u32)
-> (TextureData, Vec<(char, CharacterInfos)>)
{
use std::iter;
const MARGIN: u32 = 2;
if freetype::FT_Set_Pixel_Sizes(face, font_size, font_size) != 0 {
panic!();
}
let mut texture_data: Vec<f32> = Vec::with_capacity(characters_list.len() *
font_size as usize * font_size as usize);
let texture_width = get_nearest_po2(std::cmp::max(font_size * 2 as u32,
((((characters_list.len() as u32) * font_size * font_size) as f32).sqrt()) as u32));
let mut cursor_offset = (0u32, 0u32);
let mut rows_to_skip = 0u32;
let mut em_pixels = font_size as f32;
let mut characters_infos: Vec<(char, CharacterInfos)> = characters_list.into_iter().filter_map(|character| {
if freetype::FT_Load_Glyph(face, freetype::FT_Get_Char_Index(face, character as freetype::FT_ULong), freetype::FT_LOAD_RENDER) != 0 {
return None;
}
let bitmap = &(*(*face).glyph).bitmap;
cursor_offset.0 += MARGIN;
if character == 'M' {
em_pixels = bitmap.rows as f32;
}
if cursor_offset.0 + (bitmap.width as u32) + MARGIN >= texture_width {
assert!(bitmap.width as u32 <= texture_width);
cursor_offset.0 = 0;
cursor_offset.1 += rows_to_skip;
rows_to_skip = 0;
}
if rows_to_skip < MARGIN + bitmap.rows as u32 {
let diff = MARGIN + (bitmap.rows as u32) - rows_to_skip;
rows_to_skip = MARGIN + bitmap.rows as u32;
texture_data.extend(iter::repeat(0.0).take((diff * texture_width) as usize));
}
let offset_x_before_copy = cursor_offset.0;
if bitmap.rows >= 1 {
let destination = &mut texture_data[(cursor_offset.0 + cursor_offset.1 * texture_width) as usize ..];
let source = std::mem::transmute(bitmap.buffer);
let source = std::slice::from_raw_parts(source, destination.len());
for y in 0 .. bitmap.rows as u32 {
let source = &source[(y * bitmap.width as u32) as usize ..];
let destination = &mut destination[(y * texture_width) as usize ..];
for x in 0 .. bitmap.width {
let val: u8 = *source.get(x as usize).unwrap();
let val = (val as f32) / (std::u8::MAX as f32);
let dest = destination.get_mut(x as usize).unwrap();
*dest = val;
}
}
cursor_offset.0 += bitmap.width as u32;
debug_assert!(cursor_offset.0 <= texture_width);
}
let left_padding = (*(*face).glyph).bitmap_left;
Some((character, CharacterInfos {
tex_size: (bitmap.width as f32, bitmap.rows as f32),
tex_coords: (offset_x_before_copy as f32, cursor_offset.1 as f32),
size: (bitmap.width as f32, bitmap.rows as f32),
left_padding: left_padding as f32,
right_padding: ((*(*face).glyph).advance.x as i32 - bitmap.width * 64 - left_padding * 64) as f32 / 64.0,
height_over_line: (*(*face).glyph).bitmap_top as f32,
}))
}).collect();
{
let current_height = texture_data.len() as u32 / texture_width;
let requested_height = get_nearest_po2(current_height);
texture_data.extend(iter::repeat(0.0).take((texture_width * (requested_height - current_height)) as usize));
}
assert!((texture_data.len() as u32 % texture_width) == 0);
let texture_height = (texture_data.len() as u32 / texture_width) as f32;
let float_texture_width = texture_width as f32;
for chr in characters_infos.iter_mut() {
chr.1.tex_size.0 /= float_texture_width;
chr.1.tex_size.1 /= texture_height;
chr.1.tex_coords.0 /= float_texture_width;
chr.1.tex_coords.1 /= texture_height;
chr.1.size.0 /= em_pixels;
chr.1.size.1 /= em_pixels;
chr.1.left_padding /= em_pixels;
chr.1.right_padding /= em_pixels;
chr.1.height_over_line /= em_pixels;
}
(TextureData {
data: texture_data,
width: texture_width,
height: texture_height as u32,
}, characters_infos)
}
fn get_nearest_po2(mut x: u32) -> u32 {
assert!(x > 0);
x -= 1;
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
x + 1
}