extern crate freetypegl;
use self::freetypegl::TextureGlyph;
use gl;
use glin;
use glin::CreationContext;
use gl::types::GLenum;
use graphics;
use graphics::Mesh;
use graphics::Vertex2DTex;
use graphics::projection::CoordinateOrigin;
use na::*;
use math::Rect;
use color::{ToRgba,Rgba};
use std::f32;
use std::rc::Rc;
use std::ops::Deref;
use util::{Result, Error};
use glin::RenderSurface;
use super::{CreationProxy, Renderer};
pub struct Ttf{
ttf: graphics::Ttf,
gamma: f32,
texture: Rc<glin::Texture>,
pixel: Vec3,
}
pub struct Builder<'a>{
ttf_builder: graphics::ttf::Builder<'a>,
context: CreationProxy,
gamma: f32,
}
impl<'a> Builder<'a>{
pub(crate) fn new(context: CreationProxy, path: &'a str, pt_height: f32) -> Builder<'a>{
Builder{
ttf_builder: graphics::ttf::Builder::new(path, pt_height),
context,
gamma: 1.0,
}
}
pub(crate) fn from_bytes(context: CreationProxy, bytes: &'a [u8], pt_height: f32) -> Builder<'a>{
Builder{
ttf_builder: graphics::ttf::Builder::from_bytes(bytes, pt_height),
context,
gamma: 1.0,
}
}
pub fn antialiasing(mut self, antialiasing: graphics::ttf::Antialiasing) -> Builder<'a>{
self.ttf_builder = self.ttf_builder.antialiasing(antialiasing);
self
}
pub fn gamma(mut self, gamma: f32) -> Builder<'a>{
self.gamma = gamma;
self
}
pub fn create(self) -> Result<Ttf>{
let ttf = self.ttf_builder.create()?;
let tex_internal = Ttf::texture_internal_for_aa(ttf.antialiasing_type());
let tex_format = Ttf::texture_format_for_aa(ttf.antialiasing_type());
let mut texture = self.context.new_texture().from_format(glin::texture::Format{
target: gl::TEXTURE_2D,
internal_format: tex_internal,
width: ttf.freetypegl().atlas().width() as u32,
height: ttf.freetypegl().atlas().height() as u32,
levels: 1,
#[cfg(feature = "gl")]
samples: 0,
}).map_err(|e| Error::with_cause("Error creating texture", e))?;
texture.load_data(ttf.freetypegl().atlas().data(), glin::texture::DataFormat{
width: ttf.freetypegl().atlas().width() as u32,
height: ttf.freetypegl().atlas().height() as u32,
format: tex_format,
ty: gl::UNSIGNED_BYTE,
.. Default::default()
}).map_err(|e| Error::with_cause("Error uploading texture", e))?;
let vgamma = if ttf.antialiasing_type() == graphics::ttf::Antialiasing::LCD && self.gamma>-0.01 && self.gamma<0.01 {
0.01
}else{
self.gamma
};
let pixel = if vgamma>-0.01 && vgamma<0.01{
vec3!(0.0)
}else{
vec3(1.0/texture.width() as f32, 1.0/texture.height() as f32, ttf.antialiasing_type() as i32 as f32)
};
Ok( Ttf{
ttf,
gamma: vgamma,
texture: Rc::new(texture),
pixel,
})
}
}
impl Ttf{
pub fn line_height(&self) -> f32{
self.ttf.line_height()
}
pub fn ascender(&self) -> f32{
self.ttf.ascender()
}
pub fn descender(&self) -> f32{
self.ttf.descender()
}
pub fn line_gap(&self) -> f32{
self.ttf.line_gap()
}
pub fn gamma(&self) -> f32{
self.gamma
}
pub fn antialiasing_type(&self) -> graphics::ttf::Antialiasing{
self.ttf.antialiasing_type()
}
pub fn mesh(&self, string: &str, x: f32, y: f32, coordinate_origin: CoordinateOrigin) -> Mesh<Vertex2DTex>{
let mut mesh = Mesh::default();
self.shape(string,x,y,coordinate_origin, |bl, tr, glyph|{
append_glyph(&mut mesh,bl,tr,glyph);
});
mesh
}
pub fn bounding_box(&self, string: &str, x: f32, y: f32, coordinate_origin: CoordinateOrigin) -> Rect<f32>{
self.ttf.bounding_box(string, x, y, coordinate_origin)
}
pub fn box_mesh(&self, string: &str, x: f32, y: f32, w: f32, h: f32, coordinate_origin: CoordinateOrigin) -> Mesh<Vertex2DTex>{
let mut mesh = Mesh::default();
self.box_shape(string,x,y,w,h,coordinate_origin,|word,x_word,y_word|{
self.shape(word,x_word,y_word,coordinate_origin, |bl, tr, glyph|{
append_glyph(&mut mesh,bl,tr,glyph);
});
});
mesh
}
pub fn box_height(&self, string: &str, w: f32) -> f32{
self.box_shape(string,0.0,0.0,w,-1.0,CoordinateOrigin::TopLeft,|_,_,_|())
}
pub fn next_pos(&self, string: &str, x: f32, y: f32, coordinate_origin: CoordinateOrigin) -> Vec2{
self.shape(string,x,y,coordinate_origin,|_,_,_|())
}
pub fn shape<F>(&self, string: &str, x: f32, y: f32, coordinate_origin: CoordinateOrigin, f: F) -> Vec2
where F: FnMut(Vec2, Vec2, &TextureGlyph)
{
self.ttf.shape(string, x, y, coordinate_origin, f)
}
pub fn shape_iter<'a>(&'a self, string: &'a str, x: f32, y: f32, coordinate_origin: CoordinateOrigin) -> ShapeIter<'a> {
let mut xpos = x;
let mut ypos = match coordinate_origin{
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => y,
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => y - self.line_height(),
};
let line_height = match coordinate_origin{
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => self.line_height(),
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => -self.line_height(),
};
let iter = Box::new(string.lines().flat_map(move |l|{
xpos = x;
ypos -= line_height;
let mut prevc = '\0';
l.chars().enumerate().map(move |(i,c)|{
let glyph = self.ttf.freetypegl().glyph(c);
if i > 0 {
xpos+= glyph.kerning(prevc);
}
let (bl,tr) = match coordinate_origin{
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => {
let x0 = ( xpos + glyph.offset_x() as f32 ) as i32 as f32;
let y1 = ( ypos + glyph.offset_y() as f32 ) as i32 as f32;
let x1 = ( x0 + glyph.width() as f32 ) as i32 as f32;
let y0 = ( y1 - glyph.height() as f32 ) as i32 as f32;
(pnt2(x0,y0), pnt2(x1,y1))
}
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => {
let x0 = ( xpos + glyph.offset_x() as f32 ) as i32 as f32;
let y0 = ( ypos - glyph.offset_y() as f32 ) as i32 as f32;
let x1 = ( x0 + glyph.width() as f32 ) as i32 as f32;
let y1 = ( y0 + glyph.height() as f32 ) as i32 as f32;
(pnt2(x0,y1), pnt2(x1,y0))
}
};
xpos += glyph.advance_x();
prevc = c;
Shape{
rect: Rect{ pos: bl, width: tr.x - bl.x, height: tr.y - bl.y},
glyph,
character: c
}
})
}));
ShapeIter{ iter }
}
pub fn box_shape<F>(&self, string: &str, x: f32, y: f32, w: f32, h: f32, coordinate_origin: CoordinateOrigin, f: F) -> f32
where F: FnMut(&str, f32, f32)
{
self.ttf.box_shape(string, x, y, w, h, coordinate_origin, f)
}
pub fn texture(&self) -> &glin::Texture{
self.texture.deref()
}
pub fn material<C:ToRgba>(&self, color: &C) -> Material{
let color = color.to_rgba();
let mut uniforms = uniforms!{
font_color: color,
font_texture: self.texture,
};
if self.gamma.abs()>0.01{
uniforms.push(glin::program::uniform("gamma", &self.gamma));
uniforms.push(glin::program::uniform("pixel", &self.pixel));
}
Material{
texture: self.texture.clone(),
color,
gamma: self.gamma,
uniforms: vec![],
}
}
#[cfg(feature="gl")]
fn texture_internal_for_aa(aa: graphics::ttf::Antialiasing) -> GLenum{
match aa{
graphics::ttf::Antialiasing::None => gl::R8,
graphics::ttf::Antialiasing::Gray => gl::R8,
graphics::ttf::Antialiasing::LCD => gl::RGB8
}
}
#[cfg(any(feature="gles", feature="webgl"))]
fn texture_internal_for_aa(aa: graphics::ttf::Antialiasing) -> GLenum{
match aa{
graphics::ttf::Antialiasing::None => gl::LUMINANCE,
graphics::ttf::Antialiasing::Gray => gl::LUMINANCE,
graphics::ttf::Antialiasing::LCD => gl::RGB
}
}
#[cfg(feature="gl")]
fn texture_format_for_aa(aa: graphics::ttf::Antialiasing) -> GLenum{
match aa{
graphics::ttf::Antialiasing::None => gl::RED,
graphics::ttf::Antialiasing::Gray => gl::RED,
graphics::ttf::Antialiasing::LCD => gl::RGB
}
}
#[cfg(any(feature="gles", feature="webgl"))]
fn texture_format_for_aa(aa: graphics::ttf::Antialiasing) -> GLenum{
match aa{
graphics::ttf::Antialiasing::None => gl::LUMINANCE,
graphics::ttf::Antialiasing::Gray => gl::LUMINANCE,
graphics::ttf::Antialiasing::LCD => gl::RGB
}
}
pub fn texture_for<R: RenderSurface>(context: &Renderer<R>, ttf: &graphics::Ttf) -> Result<glin::Texture>{
let tex_internal = Ttf::texture_internal_for_aa(ttf.antialiasing_type());
let tex_format = Ttf::texture_format_for_aa(ttf.antialiasing_type());
let mut texture = context.new_texture().from_format(glin::texture::Format{
target: gl::TEXTURE_2D,
internal_format: tex_internal,
width: ttf.freetypegl().atlas().width() as u32,
height: ttf.freetypegl().atlas().height() as u32,
levels: 1,
#[cfg(feature = "gl")]
samples: 0,
}).map_err(|e| Error::with_cause("Error creating texture", e))?;
texture.load_data(ttf.freetypegl().atlas().data(), glin::texture::DataFormat{
width: ttf.freetypegl().atlas().width() as u32,
height: ttf.freetypegl().atlas().height() as u32,
format: tex_format,
ty: gl::UNSIGNED_BYTE,
.. Default::default()
}).map_err(|e| Error::with_cause("Error uploading texture", e))?;
Ok(texture)
}
pub fn default_material_for<C: ToRgba, R: RenderSurface>(context: &Renderer<R>, ttf: &graphics::Ttf, color: &C) -> Result<Material>{
Ttf::texture_for(context, ttf).map(|texture|{
let color = color.to_rgba();
let gamma = 1.0;
let pixel = vec3(
1.0/texture.width() as f32,
1.0/texture.height() as f32,
ttf.antialiasing_type() as i32 as f32);
let uniforms = uniforms!{
font_color: color,
font_texture: texture,
gamma: gamma,
pixel: pixel,
};
Material{
color,
gamma,
texture: Rc::new(texture),
uniforms,
}
})
}
}
pub struct Shape{
pub rect: Rect<f32>,
pub glyph: TextureGlyph,
pub character: char,
}
pub struct ShapeIter<'a>{
iter: Box<Iterator<Item=Shape> + 'a>
}
impl<'a> Iterator for ShapeIter<'a>{
type Item = Shape;
fn next(&mut self) -> Option<Shape>{
self.iter.next()
}
}
pub fn append_glyph(mesh: &mut Mesh<Vertex2DTex>, bl: Vec2, tr: Vec2, glyph: &TextureGlyph){
mesh.push(Vertex2DTex{ position: bl, texcoord: vec2(glyph.s0(), glyph.t1()) });
mesh.push(Vertex2DTex{ position: vec2(tr.x, bl.y), texcoord: vec2(glyph.s1(), glyph.t1()) });
mesh.push(Vertex2DTex{ position: tr, texcoord: vec2(glyph.s1(), glyph.t0()) });
mesh.push(Vertex2DTex{ position: bl, texcoord: vec2(glyph.s0(), glyph.t1()) });
mesh.push(Vertex2DTex{ position: tr, texcoord: vec2(glyph.s1(), glyph.t0()) });
mesh.push(Vertex2DTex{ position: vec2(bl.x,tr.y), texcoord: vec2(glyph.s0(), glyph.t0()) });
}
pub struct Material{
color: Rgba<f32>,
texture: Rc<glin::Texture>,
gamma: f32,
uniforms: Vec<glin::program::Uniform>,
}
impl Material{
pub fn texture(&self) -> &glin::Texture{
&self.texture
}
pub fn set_color<C: ToRgba>(&mut self, color: &C){
self.color = color.to_rgba();
self.uniforms[0] = glin::program::uniform("font_color", &self.color);
}
}
impl gl::Material for Material{
fn program<R: RenderSurface>(&self, renderer: &Renderer<R>) -> &glin::Program{
if self.gamma>-0.01 && self.gamma<0.01{
shaders::get_ttf_program_no_gamma(renderer)
}else{
shaders::get_ttf_program(renderer)
}
}
fn uniforms<R: RenderSurface>(&self, _gl: &Renderer<R>) -> &[glin::program::Uniform]{
&self.uniforms
}
fn properties(&self) -> &[glin::Property]{
&[
glin::Property::Blend(true),
glin::Property::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA),
glin::Property::BlendColor([1.0,1.0,1.0,1.0]),
]
}
}
#[cfg(feature = "gl")]
mod shaders{
program_cache!("shaders/vertex_ttf.glsl", "shaders/fragment_ttf.glsl", get_ttf_program, SHADER_TTF);
program_cache!("shaders/vertex_ttf.glsl", "shaders/fragment_ttf.glsl", get_ttf_program_no_gamma, SHADER_TTF_NO_GAMMA);
}
#[cfg(any(feature="gles", feature="webgl"))]
mod shaders{
program_cache!("shaders/vertex_ttf_gles.glsl", "shaders/fragment_ttf_gles.glsl", get_ttf_program, SHADER_TTF);
program_cache!("shaders/vertex_ttf_gles.glsl", "shaders/fragment_ttf_gles.glsl", get_ttf_program_no_gamma, SHADER_TTF_NO_GAMMA);
}