@ -2,138 +2,112 @@ use std::sync::Arc;
use std ::collections ::HashMap ;
use lru ::LruCache ;
use skulpin ::skia_safe ::{ TextBlob , Font as SkiaFont , FontStyle , Typeface , TextBlobBuilder };
use font_kit ::source ::SystemSource ;
use skulpin ::skia_safe ::{ TextBlob , Font as SkiaFont , FontStyle , Typeface , TextBlobBuilder , Data };
use font_kit ::{ source ::SystemSource , metrics ::Metrics , properties ::Properties , family_name ::FamilyName } ;
use skribo ::{ layout_run , LayoutSession , FontRef as SkriboFont , FontFamily , FontCollection , TextStyle } ;
use crate ::error_handling ::OptionPanicExplanation ;
const STANDARD_CHARACTER_STRING : & ' static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" ;
#[ cfg(target_os = " windows " ) ]
const DEFAULT_FONT : & str = "Consolas" ;
#[ cfg(target_os = " windows " ) ]
const EMOJI_FONT : & str = "Segoe UI Emoji" ;
#[ cfg(target_os = " macos " ) ]
const DEFAULT_FONT : & str = "Menlo" ;
#[ cfg(target_os = " macos " ) ]
const EMOJI_FONT : & str = "Apple COlor Emoji" ;
#[ cfg(target_os = " linux " ) ]
const DEFAULT_FONT : & str = "Monospace" ;
#[ cfg(target_os = " linux " ) ]
const EMOJI_FONT : & str = "Noto Color Emoji" ;
const DEFAULT_FONT_SIZE : f32 = 14.0 ;
#[ derive(new, Clone, Hash, PartialEq, Eq, Debug) ]
struct FontKey {
pub scale : u16 ,
pub bold : bool ,
pub italic : bool
}
#[ derive(new, Clone, Hash, PartialEq, Eq, Debug) ]
struct ShapeKey {
pub text : String ,
pub font_key : FontKey
}
struct FontPair {
normal : ( SkiaFont , SkriboFont ) ,
emoji : Option < ( SkiaFont , SkriboFont ) >
pub bold : bool ,
pub italic : bool
}
#[ derive(Debug) ]
pub struct CachingShaper {
pub font_name : String,
pub font_name : Option < String > ,
pub base_size : f32 ,
font_cache : LruCache < FontKey , FontPair > ,
collection : FontCollection ,
font_cache : LruCache < String , SkiaFont > ,
blob_cache : LruCache < ShapeKey , Vec < TextBlob > >
}
fn build_ fonts( font_key : & FontKey , font_name : & str , base_size : f32 ) -> Option < ( SkiaFont , SkriboFont ) > {
fn build_ collection_by_font_name( font_name : Option < & str > ) -> FontCollection {
let source = SystemSource ::new ( ) ;
let skribo_font = SkriboFont ::new (
source . select_family_by_name ( font_name )
. ok ( ) ?
. fonts ( ) [ 0 ]
. load ( )
. ok ( ) ? ) ;
let font_style = match ( font_key . bold , font_key . italic ) {
( false , false ) = > FontStyle ::normal ( ) ,
( true , false ) = > FontStyle ::bold ( ) ,
( false , true ) = > FontStyle ::italic ( ) ,
( true , true ) = > FontStyle ::bold_italic ( )
} ;
let skia_font = SkiaFont ::from_typeface (
Typeface ::new ( font_name . clone ( ) , font_style ) ? ,
base_size * font_key . scale as f32 ) ;
Some ( ( skia_font , skribo_font ) )
let mut collection = FontCollection ::new ( ) ;
if let Some ( font_name ) = font_name {
if let Ok ( custom ) = source . select_family_by_name ( font_name ) {
let font = custom . fonts ( ) [ 0 ] . load ( ) . unwrap ( ) ;
collection . add_family ( FontFamily ::new_from_font ( font ) ) ;
}
}
if let Ok ( monospace ) = source . select_best_match ( & [ FamilyName ::Monospace ] , & Properties ::new ( ) ) {
let font = monospace . load ( ) . unwrap ( ) ;
collection . add_family ( FontFamily ::new_from_font ( font ) ) ;
}
if let Ok ( emoji ) = source . select_family_by_name ( EMOJI_FONT ) {
let font = emoji . fonts ( ) [ 0 ] . load ( ) . unwrap ( ) ;
collection . add_family ( FontFamily ::new_from_font ( font ) ) ;
}
collection
}
fn build_skia_font_from_skribo_font ( skribo_font : & SkriboFont , base_size : f32 ) -> SkiaFont {
let font_data = skribo_font . font . copy_font_data ( ) . unwrap ( ) ;
let skia_data = Data ::new_copy ( & font_data [ .. ] ) ;
let typeface = Typeface ::from_data ( skia_data , None ) . unwrap ( ) ;
SkiaFont ::from_typeface ( typeface , base_size )
}
impl CachingShaper {
pub fn new ( ) -> CachingShaper {
CachingShaper {
font_name : DEFAULT_FONT . to_string ( ) ,
font_name : None ,
base_size : DEFAULT_FONT_SIZE ,
collection : build_collection_by_font_name ( None ) ,
font_cache : LruCache ::new ( 100 ) ,
blob_cache : LruCache ::new ( 10000 ) ,
}
}
fn get_font_pair ( & mut self , font_key : & FontKey ) -> & FontPair {
if ! self . font_cache . contains ( font_key ) {
let font_pair = FontPair {
normal : build_fonts ( font_key , & self . font_name , self . base_size )
. unwrap_or_explained_panic (
"Could not load configured font" ,
& format! ( "Could not load {}. This font was either the font configured in init scripts via guifont, or the default font is not available on your machine. Defaults are defined here: https://github.com/Kethku/neovide/blob/master/src/renderer/caching_shaper.rs" , & self . font_name ) ) ,
emoji : build_fonts ( font_key , EMOJI_FONT , self . base_size )
} ;
self . font_cache . put ( font_key . clone ( ) , font_pair ) ;
}
self . font_cache . get ( font_key ) . unwrap ( )
fn get_skia_font ( & mut self , skribo_font : & SkriboFont ) -> & SkiaFont {
let font_name = skribo_font . font . postscript_name ( ) . unwrap ( ) ;
if ! self . font_cache . contains ( & font_name ) {
let font = build_skia_font_from_skribo_font ( skribo_font , self . base_size ) ;
self . font_cache . put ( font_name . clone ( ) , font ) ;
}
pub fn shape ( & mut self , text : & str , scale : u16 , bold : bool , italic : bool ) -> Vec < TextBlob > {
let base_size = self . base_size ;
let font_key = FontKey ::new ( scale , bold , italic ) ;
let font_pair = self . get_font_pair ( & font_key ) ;
let style = TextStyle { size : base_size * scale as f32 } ;
let mut collection = FontCollection ::new ( ) ;
let mut normal_family = FontFamily ::new ( ) ;
normal_family . add_font ( font_pair . normal . 1. clone ( ) ) ;
collection . add_family ( normal_family ) ;
self . font_cache . get ( & font_name ) . unwrap ( )
}
if let Some ( emoji_font_pair ) = & font_pair . emoji {
let mut emoji_family = FontFamily ::new ( ) ;
emoji_family . add_font ( emoji_font_pair . 1. clone ( ) ) ;
collection . add_family ( emoji_family ) ;
fn metrics ( & self ) -> Metrics {
self . collection . itemize ( "a" ) . next ( ) . unwrap ( ) . 1. font . metrics ( )
}
let session = LayoutSession ::create ( text , & style , & collection ) ;
pub fn shape ( & mut self , text : & str , bold : bool , italic : bool ) -> Vec < TextBlob > {
let style = TextStyle { size : self . base_size } ;
let metrics = font_pair . normal . 1. font . metrics ( ) ;
let ascent = metrics . ascent * base_size / metrics . units_per_em as f32 ;
let session = LayoutSession ::create ( text , & style , & self . collection ) ;
let metrics = self . metrics ( ) ;
let ascent = metrics . ascent * self . base_size / metrics . units_per_em as f32 ;
let mut blobs = Vec ::new ( ) ;
for layout_run in session . iter_all ( ) {
let skribo_font = layout_run . font ( ) ;
let skia_font = if Arc ::ptr_eq ( & skribo_font . font , & font_pair . normal . 1. font ) {
& font_pair . normal . 0
} else {
& font_pair . emoji . as_ref ( ) . unwrap ( ) . 0
} ;
let skia_font = self . get_skia_font ( & skribo_font ) ;
let mut blob_builder = TextBlobBuilder ::new ( ) ;
@ -150,11 +124,10 @@ impl CachingShaper {
blobs
}
pub fn shape_cached ( & mut self , text : & str , scale : u16 , bold : bool , italic : bool ) -> & Vec < TextBlob > {
let font_key = FontKey ::new ( scale , bold , italic ) ;
let key = ShapeKey ::new ( text . to_string ( ) , font_key ) ;
pub fn shape_cached ( & mut self , text : & str , bold : bool , italic : bool ) -> & Vec < TextBlob > {
let key = ShapeKey ::new ( text . to_string ( ) , bold , italic ) ;
if ! self . blob_cache . contains ( & key ) {
let blobs = self . shape ( text , scale, bold, italic ) ;
let blobs = self . shape ( text , bold, italic ) ;
self . blob_cache . put ( key . clone ( ) , blobs ) ;
}
@ -162,23 +135,22 @@ impl CachingShaper {
}
pub fn change_font ( & mut self , font_name : Option < & str > , base_size : Option < f32 > ) {
self . font_name = font_name . map ( | name | name . to_string ( ) ) ;
self . base_size = base_size . unwrap_or ( DEFAULT_FONT_SIZE ) ;
self . collection = build_collection_by_font_name ( font_name ) ;
self . font_cache . clear ( ) ;
self . blob_cache . clear ( ) ;
self . font_name = font_name . unwrap_or ( DEFAULT_FONT ) . to_string ( ) ;
self . base_size = base_size . unwrap_or ( DEFAULT_FONT_SIZE ) ;
}
pub fn font_base_dimensions ( & mut self ) -> ( f32 , f32 ) {
let base_size = self . base_size ;
let font_key = FontKey ::new ( 1 , false , false ) ;
let ( skia_font , skribo_font ) = & self . get_font_pair ( & font_key ) . normal ;
let metrics = self . metrics ( ) ;
let font_height = ( metrics . ascent - metrics . descent ) * self . base_size / metrics . units_per_em as f32 ;
let ( _ , metrics ) = skia_font . metrics ( ) ;
let font_height = metrics . descent - metrics . ascent ;
let style = TextStyle { size : self . base_size } ;
let session = LayoutSession ::create ( STANDARD_CHARACTER_STRING , & style , & self . collection ) ;
let style = TextStyle { size : base_size } ;
let layout = layout_run ( & style , & skribo_font , STANDARD_CHARACTER_STRING ) ;
let glyph_offsets : Vec < f32 > = layout . glyphs . iter ( ) . map ( | glyph | glyph . offset . x ) . collect ( ) ;
let layout_run = session . iter_all ( ) . next ( ) . unwrap ( ) ;
let glyph_offsets : Vec < f32 > = layout_run . glyphs ( ) . map ( | glyph | glyph . offset . x ) . collect ( ) ;
let glyph_advances : Vec < f32 > = glyph_offsets . windows ( 2 ) . map ( | pair | pair [ 1 ] - pair [ 0 ] ) . collect ( ) ;
let mut amounts = HashMap ::new ( ) ;
@ -193,11 +165,8 @@ impl CachingShaper {
( font_width , font_height )
}
pub fn underline_position ( & mut self , scale : u16 ) -> f32 {
let font_key = FontKey ::new ( scale , false , false ) ;
let ( skia_font , _ ) = & self . get_font_pair ( & font_key ) . normal ;
let ( _ , metrics ) = skia_font . metrics ( ) ;
metrics . underline_position ( ) . unwrap ( )
pub fn underline_position ( & mut self ) -> f32 {
let metrics = self . metrics ( ) ;
metrics . underline_position
}
}