use std::sync::Arc;
use fontdue::Font;
use crate::types::Color;
use super::{ GlyphEntry, GlyphKey, SoftwareCanvas };
const GLYPH_CACHE_SOFT_CAP: usize = 8192;
impl SoftwareCanvas
{
fn evict_if_full( &mut self, key: &GlyphKey )
{
if !self.glyph_cache.contains_key( key )
&& self.glyph_cache.len() >= GLYPH_CACHE_SOFT_CAP
{
let drop_n = self.glyph_cache.len() / 2;
let victims: Vec<_> = self.glyph_cache.keys().copied().take( drop_n ).collect();
for k in victims
{
self.glyph_cache.remove( &k );
}
}
}
fn rasterize_indexed_cached( &mut self, font: &Arc<Font>, glyph_id: u16, scaled: f32, font_id: usize ) -> &GlyphEntry
{
let key = GlyphKey { glyph_id, size_bits: scaled.to_bits(), font_id };
self.evict_if_full( &key );
if !self.glyph_cache.contains_key( &key )
{
let ( metrics, bitmap ) = font.rasterize_indexed( glyph_id, scaled );
self.glyph_cache.insert( key, GlyphEntry { metrics, bitmap } );
}
self.glyph_cache.get( &key ).expect( "inserted above on miss" )
}
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>> )
{
let scaled = size * self.dpi_scale;
let line_h = scaled * 1.5;
let ph = self.pixmap.height() as f32;
let pw = self.pixmap.width() as f32;
if y + line_h < 0.0 || y - line_h > ph { return; }
if x > pw { return; }
if self.has_clip() && !self.strip_intersects_clip( y - line_h, y + 0.5 * line_h )
{
return;
}
let canvas_handle =
{
let h = self.font_handle();
match font
{
Some( f ) if Arc::ptr_eq( f, &h.font ) => h,
Some( f ) => crate::system_fonts::FontHandle
{
font: Arc::clone( f ),
bytes: Arc::new( Vec::new() ),
face: 0,
},
None => h,
}
};
let resolve = |ch: char| -> Option<crate::system_fonts::FontHandle>
{
if canvas_handle.font.lookup_glyph_index( ch ) != 0 && !canvas_handle.bytes.is_empty()
{
return Some( canvas_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();
let primary_id = Arc::as_ptr( &canvas_handle.font ) as usize;
if !canvas_handle.bytes.is_empty()
{
fonts.push( ( primary_id, 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 )
{
let id = Arc::as_ptr( &h.font ) as usize;
if id == g.font_id { found = Some( h.font ); break; }
}
}
if let Some( f ) = found
{
fonts.push( ( g.font_id, f ) );
}
}
let mut layout: Vec<( GlyphKey, f32, f32 )> = Vec::with_capacity( shaped.len() );
{
let mut cursor_x = x;
for g in &shaped
{
let Some( ( _, font_arc ) ) = fonts.iter().find( |( id, _ )| *id == g.font_id ) else
{
cursor_x += g.x_advance;
continue;
};
let glyph_id = g.glyph_id as u16;
let _ = self.rasterize_indexed_cached( font_arc, glyph_id, scaled, g.font_id );
let key = GlyphKey { glyph_id, size_bits: scaled.to_bits(), font_id: g.font_id };
layout.push( ( key, cursor_x + g.x_offset, g.y_offset ) );
cursor_x += g.x_advance;
}
}
let w = self.pixmap.width() as i32;
let h = self.pixmap.height() as i32;
let cr = (color.r * 255.0) as u8;
let cg = (color.g * 255.0) as u8;
let cb = (color.b * 255.0) as u8;
let color_a = color.a * self.global_alpha;
let pixels = self.pixmap.data_mut();
let cache = &self.glyph_cache;
let mask_data = self.clip_mask.as_ref().map( |m| ( m.data(), m.width() as i32 ) );
for ( key, cursor_x, glyph_y_offset ) in layout
{
let entry = cache.get( &key ).expect( "warmed above" );
let metrics = &entry.metrics;
let bitmap = &entry.bitmap;
if metrics.width == 0 || metrics.height == 0 { continue; }
for ( i, &alpha ) in bitmap.iter().enumerate()
{
if alpha == 0 { continue; }
let px = cursor_x as i32 + metrics.xmin + (i % metrics.width) as i32;
let py = ( y - glyph_y_offset ) as i32
- metrics.ymin as i32
- metrics.height as i32
+ 1
+ (i / metrics.width) as i32;
if px < 0 || py < 0 || px >= w || py >= h { continue; }
if let Some( ( md, mw ) ) = mask_data
{
if md[ ( py * mw + px ) as usize ] == 0 { continue; }
}
let idx = (py as usize * w as usize + px as usize) * 4;
let a = (alpha as f32 / 255.0) * color_a;
let inv = 1.0 - a;
pixels[idx] = (cr as f32 * a + pixels[idx] as f32 * inv) as u8;
pixels[idx + 1] = (cg as f32 * a + pixels[idx + 1] as f32 * inv) as u8;
pixels[idx + 2] = (cb as f32 * a + pixels[idx + 2] as f32 * inv) as u8;
let a_dst = pixels[idx + 3] as f32 / 255.0;
pixels[idx + 3] = ( ( a + a_dst * inv ) * 255.0 ) as u8;
}
}
}
pub fn measure_text( &self, text: &str, size: f32 ) -> f32
{
self.measure_with_font( text, size, None )
}
pub fn measure_text_with_font( &self, text: &str, size: f32, font: &Arc<Font> ) -> f32
{
self.measure_with_font( text, size, Some( font ) )
}
fn measure_with_font( &self, text: &str, size: f32, font: Option<&Arc<Font>> ) -> f32
{
let scaled = size * self.dpi_scale;
let canvas_handle =
{
let h = self.font_handle();
match font
{
Some( f ) if Arc::ptr_eq( f, &h.font ) => h,
Some( f ) => crate::system_fonts::FontHandle
{
font: Arc::clone( f ),
bytes: Arc::new( Vec::new() ),
face: 0,
},
None => h,
}
};
let resolve = |ch: char| -> Option<crate::system_fonts::FontHandle>
{
if canvas_handle.font.lookup_glyph_index( ch ) != 0 && !canvas_handle.bytes.is_empty()
{
return Some( canvas_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.map( Arc::clone ).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,
}
}
}