use std::sync::Arc;
use crate::font_face::FontFace;
use glow::HasContext;
use crate::types::{ Color, Rect };
use super::GlesCanvas;
use super::glyph_atlas::GlyphEntry;
const GLYPH_CACHE_SOFT_CAP: usize = 8192;
fn font_id( font: &Arc<FontFace> ) -> usize
{
Arc::as_ptr( font ) as usize
}
impl GlesCanvas
{
pub fn draw_text( &mut self, text: &str, x: f32, y: f32, size: f32, color: Color )
{
self.draw_text_inner( text, x, y, size, color, None );
}
pub fn draw_text_with_font( &mut self, text: &str, x: f32, y: f32, size: f32, color: Color, font: &Arc<FontFace> )
{
self.draw_text_inner( text, x, y, size, color, Some( font ) );
}
fn draw_text_inner( &mut self, text: &str, x: f32, y: f32, size: f32, color: Color, font: Option<&Arc<FontFace>> )
{
self.activate_target();
let scaled = size * self.dpi_scale;
let size_key = ( scaled * 10.0 ) as u32;
unsafe { self.gl.active_texture( glow::TEXTURE0 ); }
let canvas_handle = self.font_handle();
let prefer_handle = font.cloned().map( |f|
{
if Arc::ptr_eq( &f, &canvas_handle.font )
{
canvas_handle.clone()
}
else
{
crate::system_fonts::FontHandle::of( f )
}
} ).unwrap_or_else( || canvas_handle.clone() );
let resolve = |ch: char| -> Option<crate::system_fonts::FontHandle>
{
if prefer_handle.font.lookup_glyph_index( ch ) != 0 && !prefer_handle.bytes.is_empty()
{
return Some( prefer_handle.clone() );
}
crate::system_fonts::lookup_handle( ch ).or_else( ||
if !canvas_handle.bytes.is_empty() { Some( canvas_handle.clone() ) } else { None }
)
};
let shaped = crate::text_shaping::shape_line_cached(
text, scaled, Arc::as_ptr( &prefer_handle.font ) as usize, resolve,
);
if shaped.is_empty() { return; }
let mut fonts: Vec<( usize, Arc<FontFace> )> = Vec::new();
if !canvas_handle.bytes.is_empty()
{
fonts.push( ( font_id( &canvas_handle.font ), Arc::clone( &canvas_handle.font ) ) );
}
if !Arc::ptr_eq( &prefer_handle.font, &canvas_handle.font )
{
fonts.push( ( font_id( &prefer_handle.font ), Arc::clone( &prefer_handle.font ) ) );
}
for g in shaped.iter()
{
if fonts.iter().any( |( id, _ )| *id == g.font_id ) { continue; }
let mut found = None;
for ch in text.chars()
{
if let Some( h ) = crate::system_fonts::lookup_handle( ch )
{
if font_id( &h.font ) == g.font_id { found = Some( h.font ); break; }
}
}
if let Some( f ) = found
{
fonts.push( ( g.font_id, f ) );
}
}
let atlas_rc = std::rc::Rc::clone( &self.glyph_atlas );
let mut atlas = atlas_rc.borrow_mut();
let mut reset_used = false;
let mut i = 0;
while i < shaped.len()
{
let g = &shaped[ i ];
let glyph_id = g.glyph_id as u16;
let key = ( glyph_id, size_key, g.font_id );
if atlas.cache.contains_key( &key )
{
i += 1;
continue;
}
let Some( ( _, font_arc ) ) = fonts.iter().find( |( id, _ )| *id == g.font_id ) else
{
i += 1;
continue;
};
let ( metrics, bitmap ) = font_arc.rasterize_indexed( glyph_id, scaled );
if metrics.width == 0 || metrics.height == 0
{
atlas.cache.insert( key, GlyphEntry
{
metrics,
tex_w: 0,
tex_h: 0,
page: 0,
atlas_x: 0,
atlas_y: 0,
} );
i += 1;
continue;
}
let ( w, h ) = ( metrics.width as u32, metrics.height as u32 );
if atlas.cache.len() >= GLYPH_CACHE_SOFT_CAP && !reset_used
{
atlas.reset( &self.gl );
reset_used = true;
i = 0;
continue;
}
let ( page, atlas_x, atlas_y ) = match atlas.alloc( &self.gl, w, h )
{
Some( p ) => p,
None if !reset_used =>
{
atlas.reset( &self.gl );
reset_used = true;
i = 0;
continue;
}
None =>
{
atlas.cache.insert( key, GlyphEntry
{
metrics,
tex_w: 0,
tex_h: 0,
page: 0,
atlas_x: 0,
atlas_y: 0,
} );
i += 1;
continue;
}
};
atlas.upload( &self.gl, page, atlas_x, atlas_y, w, h, &bitmap );
atlas.cache.insert( key, GlyphEntry
{
metrics,
tex_w: w as i32,
tex_h: h as i32,
page,
atlas_x,
atlas_y,
} );
i += 1;
}
let surface_w = self.width as f32;
let surface_h = self.height as f32;
let mut batches: Vec<( usize, Vec<f32> )> = Vec::new();
let mut color_glyphs: Vec<( Arc<crate::font_face::ColorGlyph>, Rect )> = Vec::new();
let mut cx = x;
for g in shaped.iter()
{
let glyph_id = g.glyph_id as u16;
let key = ( glyph_id, size_key, g.font_id );
let color_glyph = fonts.iter().find( |( id, _ )| *id == g.font_id ).and_then( |( _, f )| f.color_glyph( glyph_id ) );
if let Some( image ) = color_glyph
{
let rect = Rect
{
x: cx + g.x_offset + image.left * scaled,
y: y - g.y_offset - ( image.bottom + image.em_height ) * scaled,
width: image.em_width * scaled,
height: image.em_height * scaled,
};
color_glyphs.push( ( image, rect ) );
cx += g.x_advance;
continue;
}
let Some( entry ) = atlas.cache.get( &key ) else
{
cx += g.x_advance;
continue;
};
if entry.tex_w == 0 || entry.tex_h == 0
{
cx += g.x_advance;
continue;
}
let pen_x = cx + g.x_offset;
let pen_y = y - g.y_offset;
let gx = ( pen_x + entry.metrics.xmin as f32 ).round();
let gy = ( pen_y - entry.metrics.height as f32 - entry.metrics.ymin as f32 + 1.0 ).round();
let gw = entry.tex_w as f32;
let gh = entry.tex_h as f32;
let x0_ndc = gx * 2.0 / surface_w - 1.0;
let x1_ndc = ( gx + gw ) * 2.0 / surface_w - 1.0;
let y0_ndc = 1.0 - gy * 2.0 / surface_h;
let y1_ndc = 1.0 - ( gy + gh ) * 2.0 / surface_h;
let ( page_w, page_h ) = atlas.page_size( entry.page );
let u0 = entry.atlas_x as f32 / page_w;
let v0 = entry.atlas_y as f32 / page_h;
let u1 = ( entry.atlas_x as f32 + gw ) / page_w;
let v1 = ( entry.atlas_y as f32 + gh ) / page_h;
let verts = match batches.iter().position( |( p, _ )| *p == entry.page )
{
Some( b ) => &mut batches[ b ].1,
None =>
{
batches.push( ( entry.page, Vec::with_capacity( shaped.len() * 24 ) ) );
&mut batches.last_mut().unwrap().1
}
};
verts.extend_from_slice( &[
x0_ndc, y0_ndc, u0, v0,
x1_ndc, y0_ndc, u1, v0,
x0_ndc, y1_ndc, u0, v1,
x1_ndc, y0_ndc, u1, v0,
x1_ndc, y1_ndc, u1, v1,
x0_ndc, y1_ndc, u0, v1,
] );
cx += g.x_advance;
}
if !batches.is_empty()
{
unsafe
{
self.gl.use_program( Some( self.glyph_batch_program ) );
self.gl.uniform_4_f32( Some( &self.u_glyph_batch_color ), color.r, color.g, color.b, color.a );
self.gl.uniform_1_f32( Some( &self.u_glyph_batch_opacity ), self.global_alpha );
self.gl.uniform_1_i32( Some( &self.u_glyph_batch_sampler ), 0 );
self.gl.bind_vertex_array( Some( self.glyph_batch_vao ) );
self.gl.bind_buffer( glow::ARRAY_BUFFER, Some( self.glyph_batch_vbo ) );
for ( page, verts ) in &batches
{
self.gl.bind_texture( glow::TEXTURE_2D, Some( atlas.page_texture( *page ) ) );
self.gl.buffer_data_u8_slice( glow::ARRAY_BUFFER, super::helpers::bytemuck_cast_slice( verts ), glow::STREAM_DRAW );
let vertex_count = ( verts.len() / 4 ) as i32;
self.gl.draw_arrays( glow::TRIANGLES, 0, vertex_count );
}
self.gl.bind_vertex_array( None );
}
}
drop( atlas );
unsafe { self.gl.bind_texture( glow::TEXTURE_2D, None ); }
for ( image, rect ) in color_glyphs
{
self.draw_image_data( &image.rgba, image.width, image.height, rect, color.a );
}
}
pub fn measure_text( &self, text: &str, size: f32 ) -> f32
{
self.measure_inner( text, size, None )
}
pub fn measure_text_with_font( &self, text: &str, size: f32, font: &Arc<FontFace> ) -> f32
{
self.measure_inner( text, size, Some( font ) )
}
fn measure_inner( &self, text: &str, size: f32, font: Option<&Arc<FontFace>> ) -> f32
{
let scaled = size * self.dpi_scale;
let canvas_handle = self.font_handle();
let prefer_handle = font.cloned().map( |f|
{
if Arc::ptr_eq( &f, &canvas_handle.font )
{
canvas_handle.clone()
}
else
{
crate::system_fonts::FontHandle::of( f )
}
} ).unwrap_or_else( || canvas_handle.clone() );
let resolve = |ch: char| -> Option<crate::system_fonts::FontHandle>
{
if prefer_handle.font.lookup_glyph_index( ch ) != 0 && !prefer_handle.bytes.is_empty()
{
return Some( prefer_handle.clone() );
}
crate::system_fonts::lookup_handle( ch ).or_else( ||
if !canvas_handle.bytes.is_empty() { Some( canvas_handle.clone() ) } else { None }
)
};
let shaped = crate::text_shaping::shape_line_cached(
text, scaled, Arc::as_ptr( &prefer_handle.font ) as usize, resolve,
);
if shaped.is_empty()
{
return text.chars().map( |ch|
{
let f = font.cloned().unwrap_or_else( || self.font_for_char( ch ) );
f.metrics( ch, scaled ).advance_width
} ).sum();
}
shaped.iter().map( |g| g.x_advance ).sum()
}
fn font_handle( &self ) -> crate::system_fonts::FontHandle
{
crate::system_fonts::FontHandle
{
font: Arc::clone( &self.font ),
bytes: Arc::clone( &self.font_bytes ),
face: self.font_face,
}
}
}