use std::sync::Arc;
use fontdue::Font;
use glow::HasContext;
use crate::types::Color;
use super::{ ATLAS_SIZE, GlesCanvas, GlyphEntry };
const GLYPH_CACHE_SOFT_CAP: usize = 8192;
fn font_id( font: &Arc<Font> ) -> 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<Font> )
{
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<Font>> )
{
self.activate_target();
let scaled = size * self.dpi_scale;
let size_key = ( scaled * 10.0 ) as u32;
unsafe
{
self.gl.active_texture( glow::TEXTURE0 );
self.gl.bind_texture( glow::TEXTURE_2D, Some( self.atlas_texture ) );
}
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
{
font: f,
bytes: Arc::new( Vec::new() ),
face: 0,
}
}
} ).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( text, scaled, resolve );
if shaped.is_empty() { return; }
let mut fonts: Vec<( usize, Arc<Font> )> = Vec::new();
if !canvas_handle.bytes.is_empty()
{
fonts.push( ( font_id( &canvas_handle.font ), Arc::clone( &canvas_handle.font ) ) );
}
for g in &shaped
{
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 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 self.glyph_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
{
self.glyph_cache.insert( key, GlyphEntry
{
metrics,
tex_w: 0,
tex_h: 0,
atlas_x: 0,
atlas_y: 0,
} );
i += 1;
continue;
}
let ( w, h ) = ( metrics.width as u32, metrics.height as u32 );
if self.glyph_cache.len() >= GLYPH_CACHE_SOFT_CAP && !reset_used
{
self.atlas_reset();
reset_used = true;
i = 0;
continue;
}
let pos = match self.atlas_alloc( w, h )
{
Some( p ) => p,
None if !reset_used =>
{
self.atlas_reset();
reset_used = true;
i = 0;
continue;
}
None =>
{
self.glyph_cache.insert( key, GlyphEntry
{
metrics,
tex_w: 0,
tex_h: 0,
atlas_x: 0,
atlas_y: 0,
} );
i += 1;
continue;
}
};
unsafe
{
self.gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 1 );
self.gl.tex_sub_image_2d(
glow::TEXTURE_2D, 0,
pos.0 as i32, pos.1 as i32,
w as i32, h as i32,
self.atlas_format, glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice( Some( &bitmap ) ),
);
self.gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 4 );
}
self.glyph_cache.insert( key, GlyphEntry
{
metrics,
tex_w: w as i32,
tex_h: h as i32,
atlas_x: pos.0,
atlas_y: pos.1,
} );
i += 1;
}
let atlas_size = ATLAS_SIZE as f32;
let surface_w = self.width as f32;
let surface_h = self.height as f32;
let mut verts: Vec<f32> = Vec::with_capacity( shaped.len() * 24 );
let mut cx = x;
for g in &shaped
{
let glyph_id = g.glyph_id as u16;
let key = ( glyph_id, size_key, g.font_id );
let Some( entry ) = self.glyph_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 u0 = entry.atlas_x as f32 / atlas_size;
let v0 = entry.atlas_y as f32 / atlas_size;
let u1 = ( entry.atlas_x as f32 + gw ) / atlas_size;
let v1 = ( entry.atlas_y as f32 + gh ) / atlas_size;
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 !verts.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 ) );
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 );
}
}
unsafe { self.gl.bind_texture( glow::TEXTURE_2D, None ); }
}
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<Font> ) -> f32
{
self.measure_inner( text, size, Some( font ) )
}
fn measure_inner( &self, text: &str, size: f32, font: Option<&Arc<Font>> ) -> 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
{
font: f,
bytes: Arc::new( Vec::new() ),
face: 0,
}
}
} ).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( text, scaled, 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,
}
}
fn atlas_alloc( &mut self, w: u32, h: u32 ) -> Option<( u32, u32 )>
{
const PAD: u32 = 1;
if w + PAD > ATLAS_SIZE || h + PAD > ATLAS_SIZE { return None; }
if self.atlas_cursor_x + w + PAD > ATLAS_SIZE
{
self.atlas_cursor_x = 0;
self.atlas_cursor_y += self.atlas_row_height;
self.atlas_row_height = 0;
}
if self.atlas_cursor_y + h + PAD > ATLAS_SIZE { return None; }
let x = self.atlas_cursor_x;
let y = self.atlas_cursor_y;
self.atlas_cursor_x += w + PAD;
if h + PAD > self.atlas_row_height { self.atlas_row_height = h + PAD; }
Some( ( x, y ) )
}
fn atlas_reset( &mut self )
{
self.glyph_cache.clear();
self.atlas_cursor_x = 0;
self.atlas_cursor_y = 0;
self.atlas_row_height = 0;
}
}