use glin::gl;
use glin::CreationContext;
use gl::types::GLenum;
use rin_graphics::Mesh;
use rin_graphics::Vertex2DTex;
use rin_graphics::projection::CoordinateOrigin;
use rin_graphics as graphics;
use rin_math::{Rect, Vec3, Vec2, Pnt2, vec3};
pub use rin_graphics::ttf::{Shape, BoxCoordinatesX, BoxCoordinatesY, BoxFlags, BoxMesh};
use color::{ToRgba,Rgba};
use std::f32;
use std::rc::Rc;
use rin_util::{Result, Error};
use glin::RenderSurface;
use glin::uniforms;
use std::cell::{RefCell, Ref, Cell};
use super::{CreationProxy, Renderer};
pub struct Ttf{
ttf: graphics::Ttf,
gamma: f32,
texture: Rc<RefCell<glin::Texture>>,
pixel: Vec3,
used: Cell<usize>,
context: CreationProxy,
}
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 build(self) -> Result<Ttf>{
let ttf = self.ttf_builder.build()?;
let texture = Ttf::texture_for(&self.context, &ttf)?;
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)
};
let used = ttf.freetypegl().atlas().used();
Ok( Ttf{
ttf,
gamma: vgamma,
texture: Rc::new(RefCell::new(texture)),
pixel,
used: Cell::new(used),
context: self.context,
})
}
}
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, pos: &Pnt2, coordinate_origin: CoordinateOrigin) -> Mesh<Vertex2DTex>{
self.ttf.mesh(string, pos, coordinate_origin)
}
pub fn bounding_box(&self, string: &str, pos: &Pnt2, coordinate_origin: CoordinateOrigin) -> Rect<f32>{
self.ttf.bounding_box(string, pos, coordinate_origin)
}
pub fn box_mesh<H>(
&self,
string: &str,
pos: &Pnt2,
w: BoxCoordinatesX,
h: H,
coordinate_origin: CoordinateOrigin,
flags: BoxFlags) -> BoxMesh<Vertex2DTex>
where H: Into<Option<BoxCoordinatesY>>
{
self.ttf.box_mesh(string, pos, w, h, coordinate_origin, flags)
}
pub fn box_size(&self, string: &str, w: BoxCoordinatesX) -> Vec2{
self.ttf.box_size(string, w)
}
pub fn next_pos(&self, string: &str, pos: &Pnt2, coordinate_origin: CoordinateOrigin) -> Pnt2 {
self.ttf.next_pos(string, pos, coordinate_origin)
}
pub fn shape_iter<'a>(
&'a self,
string: &'a str,
pos: &'a Pnt2,
coordinate_origin: CoordinateOrigin) -> impl Iterator<Item = Shape> + 'a
{
self.ttf.shape_iter(string, pos, coordinate_origin)
}
pub fn box_shape_iter<'a, H>(
&'a self,
string: &'a str,
pos: &Pnt2,
w: BoxCoordinatesX,
h: H,
coordinate_origin: CoordinateOrigin,
flags: BoxFlags) -> impl Iterator<Item = Shape> + 'a
where H: Into<Option<BoxCoordinatesY>> + 'a
{
self.ttf.box_shape_iter(string, pos, w, h, coordinate_origin, flags)
}
pub fn texture(&self) -> Ref<glin::Texture> {
self.texture.borrow()
}
pub fn material<C: ToRgba>(&self, color: &C) -> Material{
let color: Rgba<f32> = color.to_rgba().to_standard();
let mut uniforms = uniforms!{
font_color: color,
font_texture: &*self.texture.borrow(),
};
if self.gamma.abs()>0.01{
uniforms.push(glin::program::uniform("gamma", &self.gamma));
uniforms.push(glin::program::uniform("pixel", &self.pixel));
}
self.update_texture();
Material{
texture: self.texture.clone(),
color: Some(color),
gamma: self.gamma,
pixel: self.pixel,
uniforms,
used: self.used.get(),
}
}
pub fn material_no_color(&self) -> Material{
let mut uniforms = uniforms!{
font_texture: &*self.texture.borrow(),
};
if self.gamma.abs()>0.01{
uniforms.push(glin::program::uniform("gamma", &self.gamma));
uniforms.push(glin::program::uniform("pixel", &self.pixel));
}
self.update_texture();
Material{
texture: self.texture.clone(),
color: None,
gamma: self.gamma,
pixel: self.pixel,
uniforms,
used: self.used.get(),
}
}
#[cfg(not(any(feature="gles", feature="webgl")))]
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(not(any(feature="gles", feature="webgl")))]
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(context: &CreationProxy, ttf: &graphics::Ttf) -> Result<glin::Texture>{
let tex_internal = Ttf::texture_internal_for_aa(ttf.antialiasing_type());
let mut texture = context.new_texture()
.from_format(glin::texture::Format::new(
tex_internal,
ttf.freetypegl().atlas().width() as u32,
ttf.freetypegl().atlas().height() as u32
)).map_err(|e| Error::with_cause("Error creating texture", e))?;
texture
.load_data(ttf.freetypegl().atlas().data(), Default::default())
.map_err(|e| Error::with_cause("Error uploading texture", e))?;
Ok(texture)
}
fn update_texture(&self) {
if self.used.get() != self.ttf.freetypegl().atlas().used() {
if self.ttf.freetypegl().atlas().width() != self.texture.borrow().width() as usize
|| self.ttf.freetypegl().atlas().width() != self.texture.borrow().height() as usize
{
*self.texture.borrow_mut() = Ttf::texture_for(&self.context, &self.ttf).unwrap();
}else{
self.texture.borrow_mut()
.load_data(self.ttf.freetypegl().atlas().data(), Default::default())
.unwrap();
}
self.used.set(self.ttf.freetypegl().atlas().used());
}
}
}
pub struct MaterialBuilder<'a>{
context: CreationProxy,
color: Option<Rgba<f32>>,
gamma: f32,
ttf: &'a graphics::Ttf
}
impl<'a> MaterialBuilder<'a>{
pub(super) fn new(context: CreationProxy, ttf: &graphics::Ttf) -> MaterialBuilder{
MaterialBuilder{
context,
color: None,
gamma: 1.0,
ttf,
}
}
pub fn color<C: ToRgba>(&mut self, color: &C) -> &mut MaterialBuilder<'a>{
self.color = Some(color.to_rgba().to_standard());
self
}
pub fn gamma(&mut self, gamma: f32) -> &mut MaterialBuilder<'a>{
self.gamma = gamma;
self
}
pub fn build(&mut self) -> Result<Material>{
Ttf::texture_for(&self.context, self.ttf).map(|texture|{
let gamma = self.gamma;
let pixel = vec3(
1.0/texture.width() as f32,
1.0/texture.height() as f32,
self.ttf.antialiasing_type() as i32 as f32);
let uniforms = if let Some(color) = self.color {
uniforms!{
font_color: color,
font_texture: texture,
gamma: gamma,
pixel: pixel,
}
}else{
uniforms!{
font_texture: texture,
gamma: gamma,
pixel: pixel,
}
};
Material{
color: self.color,
gamma: self.gamma,
pixel,
texture: Rc::new(RefCell::new(texture)),
uniforms,
used: self.ttf.freetypegl().atlas().used(),
}
})
}
}
pub struct Material{
color: Option<Rgba<f32>>,
gamma: f32,
pixel: Vec3,
texture: Rc<RefCell<glin::Texture>>,
uniforms: Vec<glin::program::Uniform>,
used: usize,
}
impl Material{
pub fn texture(&self) -> Ref<glin::Texture>{
self.texture.borrow()
}
pub fn set_color<C: ToRgba>(&mut self, color: &C){
self.color = Some(color.to_rgba().to_standard());
self.regenerate_uniforms();
}
pub fn no_color(&mut self){
self.color = None;
self.regenerate_uniforms();
}
pub fn set_gamma(&mut self, gamma: f32){
self.gamma = gamma;
self.regenerate_uniforms();
}
pub fn update(&mut self, ttf: &graphics::Ttf) -> Result<()> {
if self.used != ttf.freetypegl().atlas().used(){
if ttf.freetypegl().atlas().width() != self.texture.borrow().width() as usize
|| ttf.freetypegl().atlas().width() != self.texture.borrow().height() as usize
{
return Err(Error::new("The font used to update a ttf Material, has different dimensions than the one used originally"))
}
let tex_format = Ttf::texture_format_for_aa(ttf.antialiasing_type());
self.texture.borrow_mut()
.load_data(ttf.freetypegl().atlas().data(), Default::default())
.map_err(|e| Error::with_cause("Error uploading texture", e))?;
self.used = ttf.freetypegl().atlas().used();
}
Ok(())
}
fn regenerate_uniforms(&mut self){
self.uniforms.clear();
if let Some(color) = self.color.as_ref() {
self.uniforms.push(glin::program::uniform("font_color", color));
}
self.uniforms.push(glin::program::uniform("font_texture", &*self.texture.borrow()));
if self.gamma.abs()>0.01{
self.uniforms.push(glin::program::uniform("gamma", &self.gamma));
self.uniforms.push(glin::program::uniform("pixel", &self.pixel));
}
}
}
impl crate::Material for Material{
fn program<R: RenderSurface>(&self, renderer: &Renderer<R>) -> &glin::Program{
if self.color.is_some(){
shaders::get_ttf_program(renderer)
}else{
shaders::get_ttf_program_no_global_color(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::BlendFuncSeparate(
gl::SRC_ALPHA,
gl::ONE_MINUS_SRC_ALPHA,
gl::ONE,
gl::ONE_MINUS_SRC_ALPHA,
)
]
}
}
#[cfg(not(any(feature="gles", feature="webgl")))]
mod shaders{
program_cache!("shaders/vertex_ttf.glsl", "shaders/fragment_ttf.glsl", get_ttf_program, SHADER_TTF);
program_cache!(glin::program::Settings{
version: crate::default_glsl_version(),
extensions: vec![],
precision: glin::program::ShaderPrecision::High,
defines: vec![
("USE_GLOBAL_COLOR", "0"),
],
shaders: vec![
(glin::gl::VERTEX_SHADER, include_str!("shaders/vertex_ttf.glsl")),
(glin::gl::FRAGMENT_SHADER, include_str!("shaders/fragment_ttf.glsl")),
],
bindings: crate::default_attribute_bindings().clone(),
base_path: "",
.. Default::default()
}, get_ttf_program_no_global_color, SHADER_TTF_NO_GLOBAL_COLOR);
}
#[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);
}