mirror of https://github.com/sgoudham/neovide.git
separate font types into files in their own folder
parent
86c241ee55
commit
fed63a9507
@ -1,573 +0,0 @@
|
|||||||
use cfg_if::cfg_if as define;
|
|
||||||
use font_kit::{
|
|
||||||
family_handle::FamilyHandle,
|
|
||||||
font::Font,
|
|
||||||
metrics::Metrics,
|
|
||||||
properties::{Properties, Stretch, Style, Weight},
|
|
||||||
source::SystemSource,
|
|
||||||
};
|
|
||||||
use log::{trace, warn};
|
|
||||||
use lru::LruCache;
|
|
||||||
use skribo::{FontCollection, FontFamily, FontRef as SkriboFont, LayoutSession, TextStyle};
|
|
||||||
use skulpin::skia_safe::{Data, Font as SkiaFont, TextBlob, TextBlobBuilder, Typeface};
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
use super::font_options::FontOptions;
|
|
||||||
|
|
||||||
const STANDARD_CHARACTER_STRING: &str =
|
|
||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
|
||||||
|
|
||||||
define! {
|
|
||||||
if #[cfg(target_os = "windows")] {
|
|
||||||
const SYSTEM_DEFAULT_FONT: &str = "Consolas";
|
|
||||||
const SYSTEM_SYMBOL_FONT: &str = "Segoe UI Symbol";
|
|
||||||
const SYSTEM_EMOJI_FONT: &str = "Segoe UI Emoji";
|
|
||||||
} else if #[cfg(target_os = "linux")] {
|
|
||||||
const SYSTEM_DEFAULT_FONT: &str = "Noto Sans Mono";
|
|
||||||
const SYSTEM_SYMBOL_FONT: &str = "Noto Sans Mono";
|
|
||||||
const SYSTEM_EMOJI_FONT: &str = "Noto Color Emoji";
|
|
||||||
} else if #[cfg(target_os = "macos")] {
|
|
||||||
const SYSTEM_DEFAULT_FONT: &str = "Menlo";
|
|
||||||
const SYSTEM_SYMBOL_FONT: &str = "Apple Symbols";
|
|
||||||
const SYSTEM_EMOJI_FONT: &str = "Apple Color Emoji";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXTRA_SYMBOL_FONT: &str = "Extra Symbols.otf";
|
|
||||||
const MISSING_GLYPH_FONT: &str = "Missing Glyphs.otf";
|
|
||||||
|
|
||||||
#[cfg(any(feature = "embed-fonts", test))]
|
|
||||||
#[derive(RustEmbed)]
|
|
||||||
#[folder = "assets/fonts/"]
|
|
||||||
struct Asset;
|
|
||||||
|
|
||||||
const DEFAULT_FONT_SIZE: f32 = 14.0;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ExtendedFontFamily {
|
|
||||||
pub fonts: Vec<SkriboFont>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ExtendedFontFamily {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtendedFontFamily {
|
|
||||||
pub fn new() -> ExtendedFontFamily {
|
|
||||||
ExtendedFontFamily { fonts: Vec::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_font(&mut self, font: SkriboFont) {
|
|
||||||
self.fonts.push(font);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, props: Properties) -> Option<&Font> {
|
|
||||||
if let Some(first_handle) = &self.fonts.first() {
|
|
||||||
for handle in &self.fonts {
|
|
||||||
let font = &handle.font;
|
|
||||||
let properties = font.properties();
|
|
||||||
|
|
||||||
if properties.weight == props.weight
|
|
||||||
&& properties.style == props.style
|
|
||||||
&& properties.stretch == props.stretch
|
|
||||||
{
|
|
||||||
return Some(&font);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(&first_handle.font);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FamilyHandle> for ExtendedFontFamily {
|
|
||||||
fn from(handle: FamilyHandle) -> Self {
|
|
||||||
handle
|
|
||||||
.fonts()
|
|
||||||
.iter()
|
|
||||||
.fold(ExtendedFontFamily::new(), |mut family, font| {
|
|
||||||
if let Ok(font) = font.load() {
|
|
||||||
family.add_font(SkriboFont::new(font));
|
|
||||||
}
|
|
||||||
family
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExtendedFontFamily> for FontFamily {
|
|
||||||
fn from(extended_font_family: ExtendedFontFamily) -> Self {
|
|
||||||
extended_font_family
|
|
||||||
.fonts
|
|
||||||
.iter()
|
|
||||||
.fold(FontFamily::new(), |mut new_family, font| {
|
|
||||||
new_family.add_font(font.clone());
|
|
||||||
new_family
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FontLoader {
|
|
||||||
cache: LruCache<String, ExtendedFontFamily>,
|
|
||||||
source: SystemSource,
|
|
||||||
random_font_name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FontLoader {
|
|
||||||
pub fn new() -> FontLoader {
|
|
||||||
FontLoader {
|
|
||||||
cache: LruCache::new(10),
|
|
||||||
source: SystemSource::new(),
|
|
||||||
random_font_name: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
|
||||||
self.cache.get(&String::from(font_name)).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "embed-fonts", test))]
|
|
||||||
fn load_from_asset(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
|
||||||
let mut family = ExtendedFontFamily::new();
|
|
||||||
|
|
||||||
if let Some(font) = Asset::get(font_name)
|
|
||||||
.and_then(|font_data| Font::from_bytes(font_data.to_vec().into(), 0).ok())
|
|
||||||
{
|
|
||||||
family.add_font(SkriboFont::new(font));
|
|
||||||
self.cache.put(String::from(font_name), family);
|
|
||||||
self.get(font_name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "embed-fonts", test)))]
|
|
||||||
fn load_from_asset(&self, font_name: &str) -> Option<ExtendedFontFamily> {
|
|
||||||
warn!(
|
|
||||||
"Tried to load {} from assets but build didn't include embed-fonts feature",
|
|
||||||
font_name
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
|
||||||
let handle = match self.source.select_family_by_name(font_name) {
|
|
||||||
Ok(it) => it,
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !handle.is_empty() {
|
|
||||||
let family = ExtendedFontFamily::from(handle);
|
|
||||||
self.cache.put(String::from(font_name), family);
|
|
||||||
self.get(font_name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_random_system_font_family(&mut self) -> Option<ExtendedFontFamily> {
|
|
||||||
if let Some(font) = self.random_font_name.clone() {
|
|
||||||
self.get(&font)
|
|
||||||
} else {
|
|
||||||
let font_names = self.source.all_families().expect("fonts exist");
|
|
||||||
let n = rand::thread_rng().gen::<usize>() % font_names.len();
|
|
||||||
let font_name = &font_names[n];
|
|
||||||
self.random_font_name = Some(font_name.clone());
|
|
||||||
self.load(&font_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_or_load(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
|
||||||
if let Some(cached) = self.get(font_name) {
|
|
||||||
Some(cached)
|
|
||||||
} else if let Some(loaded) = self.load(font_name) {
|
|
||||||
Some(loaded)
|
|
||||||
} else {
|
|
||||||
self.load_from_asset(font_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_collection_by_font_name(
|
|
||||||
&mut self,
|
|
||||||
fallback_list: &[String],
|
|
||||||
properties: Properties,
|
|
||||||
) -> FontCollection {
|
|
||||||
let mut collection = FontCollection::new();
|
|
||||||
|
|
||||||
let gui_fonts = fallback_list
|
|
||||||
.iter()
|
|
||||||
.map(|fallback_item| fallback_item.as_ref())
|
|
||||||
.chain(iter::once(SYSTEM_DEFAULT_FONT));
|
|
||||||
|
|
||||||
for font_name in gui_fonts {
|
|
||||||
if let Some(family) = self.get_or_load(font_name) {
|
|
||||||
if let Some(font) = family.get(properties) {
|
|
||||||
collection.add_family(FontFamily::new_from_font(font.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for font in &[
|
|
||||||
SYSTEM_SYMBOL_FONT,
|
|
||||||
SYSTEM_EMOJI_FONT,
|
|
||||||
EXTRA_SYMBOL_FONT,
|
|
||||||
MISSING_GLYPH_FONT,
|
|
||||||
] {
|
|
||||||
if let Some(family) = self.get_or_load(font) {
|
|
||||||
collection.add_family(FontFamily::from(family));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.cache.is_empty() {
|
|
||||||
let font_family = self.get_random_system_font_family();
|
|
||||||
collection.add_family(FontFamily::from(font_family.expect("font family loaded")));
|
|
||||||
}
|
|
||||||
collection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(new, Clone, Hash, PartialEq, Eq, Debug)]
|
|
||||||
struct ShapeKey {
|
|
||||||
pub text: String,
|
|
||||||
pub bold: bool,
|
|
||||||
pub italic: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_properties(bold: bool, italic: bool) -> Properties {
|
|
||||||
let weight = if bold { Weight::BOLD } else { Weight::NORMAL };
|
|
||||||
let style = if italic { Style::Italic } else { Style::Normal };
|
|
||||||
Properties {
|
|
||||||
weight,
|
|
||||||
style,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FontSet {
|
|
||||||
normal: FontCollection,
|
|
||||||
bold: FontCollection,
|
|
||||||
italic: FontCollection,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FontSet {
|
|
||||||
fn new(fallback_list: &[String], loader: &mut FontLoader) -> FontSet {
|
|
||||||
FontSet {
|
|
||||||
normal: loader
|
|
||||||
.build_collection_by_font_name(fallback_list, build_properties(false, false)),
|
|
||||||
bold: loader
|
|
||||||
.build_collection_by_font_name(fallback_list, build_properties(true, false)),
|
|
||||||
italic: loader
|
|
||||||
.build_collection_by_font_name(fallback_list, build_properties(false, true)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&self, bold: bool, italic: bool) -> &FontCollection {
|
|
||||||
match (bold, italic) {
|
|
||||||
(true, _) => &self.bold,
|
|
||||||
(false, false) => &self.normal,
|
|
||||||
(false, true) => &self.italic,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CachingShaper {
|
|
||||||
pub options: FontOptions,
|
|
||||||
font_set: FontSet,
|
|
||||||
font_loader: FontLoader,
|
|
||||||
font_cache: LruCache<String, SkiaFont>,
|
|
||||||
blob_cache: LruCache<ShapeKey, Vec<TextBlob>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_skia_font_from_skribo_font(skribo_font: &SkriboFont, base_size: f32) -> Option<SkiaFont> {
|
|
||||||
let font_data = skribo_font.font.copy_font_data()?;
|
|
||||||
let skia_data = Data::new_copy(&font_data[..]);
|
|
||||||
let typeface = Typeface::from_data(skia_data, None)?;
|
|
||||||
|
|
||||||
Some(SkiaFont::from_typeface(typeface, base_size))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CachingShaper {
|
|
||||||
pub fn new() -> CachingShaper {
|
|
||||||
let options = FontOptions::new(String::from(SYSTEM_DEFAULT_FONT), DEFAULT_FONT_SIZE);
|
|
||||||
let mut loader = FontLoader::new();
|
|
||||||
let font_set = FontSet::new(&options.fallback_list, &mut loader);
|
|
||||||
|
|
||||||
CachingShaper {
|
|
||||||
options,
|
|
||||||
font_set,
|
|
||||||
font_loader: loader,
|
|
||||||
font_cache: LruCache::new(10),
|
|
||||||
blob_cache: LruCache::new(10000),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_skia_font(&mut self, skribo_font: &SkriboFont) -> Option<&SkiaFont> {
|
|
||||||
let font_name = skribo_font.font.postscript_name()?;
|
|
||||||
if !self.font_cache.contains(&font_name) {
|
|
||||||
let font = build_skia_font_from_skribo_font(skribo_font, self.options.size)?;
|
|
||||||
self.font_cache.put(font_name.clone(), font);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.font_cache.get(&font_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn metrics(&self) -> Metrics {
|
|
||||||
self.font_set
|
|
||||||
.normal
|
|
||||||
.itemize("a")
|
|
||||||
.next()
|
|
||||||
.expect("Cannot get font metrics")
|
|
||||||
.1
|
|
||||||
.font
|
|
||||||
.metrics()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shape(&mut self, text: &str, bold: bool, italic: bool) -> Vec<TextBlob> {
|
|
||||||
let style = TextStyle {
|
|
||||||
size: self.options.size,
|
|
||||||
};
|
|
||||||
let session = LayoutSession::create(text, &style, &self.font_set.get(bold, italic));
|
|
||||||
let metrics = self.metrics();
|
|
||||||
let ascent = metrics.ascent * self.options.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();
|
|
||||||
|
|
||||||
if let Some(skia_font) = self.get_skia_font(&skribo_font) {
|
|
||||||
let mut blob_builder = TextBlobBuilder::new();
|
|
||||||
let count = layout_run.glyphs().count();
|
|
||||||
let (glyphs, positions) =
|
|
||||||
blob_builder.alloc_run_pos_h(&skia_font, count, ascent, None);
|
|
||||||
|
|
||||||
for (i, glyph) in layout_run.glyphs().enumerate() {
|
|
||||||
glyphs[i] = glyph.glyph_id as u16;
|
|
||||||
positions[i] = glyph.offset.x();
|
|
||||||
}
|
|
||||||
|
|
||||||
blobs.push(blob_builder.make().unwrap());
|
|
||||||
} else {
|
|
||||||
warn!("Could not load skribo font");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blobs
|
|
||||||
}
|
|
||||||
|
|
||||||
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, bold, italic);
|
|
||||||
self.blob_cache.put(key.clone(), blobs);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.blob_cache.get(&key).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_font(&mut self, guifont_setting: &str) -> bool {
|
|
||||||
let updated = self.options.update(guifont_setting);
|
|
||||||
if updated {
|
|
||||||
trace!("Font changed: {:?}", self.options);
|
|
||||||
self.font_set = FontSet::new(&self.options.fallback_list, &mut self.font_loader);
|
|
||||||
self.font_cache.clear();
|
|
||||||
self.blob_cache.clear();
|
|
||||||
}
|
|
||||||
updated
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn font_base_dimensions(&mut self) -> (f32, f32) {
|
|
||||||
let metrics = self.metrics();
|
|
||||||
let font_height =
|
|
||||||
(metrics.ascent - metrics.descent) * self.options.size / metrics.units_per_em as f32;
|
|
||||||
let style = TextStyle {
|
|
||||||
size: self.options.size,
|
|
||||||
};
|
|
||||||
let session =
|
|
||||||
LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.font_set.normal);
|
|
||||||
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();
|
|
||||||
|
|
||||||
for advance in glyph_advances.iter() {
|
|
||||||
amounts
|
|
||||||
.entry(advance.to_string())
|
|
||||||
.and_modify(|e| *e += 1)
|
|
||||||
.or_insert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (font_width, _) = amounts.into_iter().max_by_key(|(_, count)| *count).unwrap();
|
|
||||||
let font_width = font_width.parse::<f32>().unwrap();
|
|
||||||
|
|
||||||
(font_width, font_height)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn underline_position(&mut self) -> f32 {
|
|
||||||
let metrics = self.metrics();
|
|
||||||
-metrics.underline_position * self.options.size / metrics.units_per_em as f32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const PROPERTIES1: Properties = Properties {
|
|
||||||
weight: Weight::NORMAL,
|
|
||||||
style: Style::Normal,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROPERTIES2: Properties = Properties {
|
|
||||||
weight: Weight::BOLD,
|
|
||||||
style: Style::Normal,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROPERTIES3: Properties = Properties {
|
|
||||||
weight: Weight::NORMAL,
|
|
||||||
style: Style::Italic,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROPERTIES4: Properties = Properties {
|
|
||||||
weight: Weight::BOLD,
|
|
||||||
style: Style::Italic,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn dummy_font() -> SkriboFont {
|
|
||||||
SkriboFont::new(
|
|
||||||
Asset::get(EXTRA_SYMBOL_FONT)
|
|
||||||
.and_then(|font_data| Font::from_bytes(font_data.to_vec().into(), 0).ok())
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_build_properties() {
|
|
||||||
assert_eq!(build_properties(false, false), PROPERTIES1);
|
|
||||||
assert_eq!(build_properties(true, false), PROPERTIES2);
|
|
||||||
assert_eq!(build_properties(false, true), PROPERTIES3);
|
|
||||||
assert_eq!(build_properties(true, true), PROPERTIES4);
|
|
||||||
}
|
|
||||||
|
|
||||||
mod extended_font_family {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_add_font() {
|
|
||||||
let mut eft = ExtendedFontFamily::new();
|
|
||||||
let font = dummy_font();
|
|
||||||
eft.add_font(font.clone());
|
|
||||||
assert_eq!(
|
|
||||||
eft.fonts.first().unwrap().font.full_name(),
|
|
||||||
font.font.full_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get() {
|
|
||||||
let mut eft = ExtendedFontFamily::new();
|
|
||||||
assert!(eft.get(PROPERTIES1).is_none());
|
|
||||||
|
|
||||||
let font = dummy_font();
|
|
||||||
eft.fonts.push(font.clone());
|
|
||||||
assert_eq!(
|
|
||||||
eft.get(font.font.properties()).unwrap().full_name(),
|
|
||||||
font.font.full_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod font_loader {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_load_from_asset() {
|
|
||||||
let mut loader = FontLoader::new();
|
|
||||||
|
|
||||||
let font_family = loader.load_from_asset("");
|
|
||||||
assert!(font_family.is_none());
|
|
||||||
|
|
||||||
let font = dummy_font();
|
|
||||||
let mut eft = ExtendedFontFamily::new();
|
|
||||||
eft.add_font(font.clone());
|
|
||||||
let font_family = loader.load_from_asset(EXTRA_SYMBOL_FONT);
|
|
||||||
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
|
||||||
assert_eq!(&result, &eft.fonts.first().unwrap().font.full_name());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&result,
|
|
||||||
&loader
|
|
||||||
.cache
|
|
||||||
.get(&EXTRA_SYMBOL_FONT.to_string())
|
|
||||||
.unwrap()
|
|
||||||
.fonts
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.font
|
|
||||||
.full_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_load() {
|
|
||||||
let mut loader = FontLoader::new();
|
|
||||||
let junk_text = "uhasiudhaiudshiaushd";
|
|
||||||
let font_family = loader.load(junk_text);
|
|
||||||
assert!(font_family.is_none());
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
const SYSTEM_DEFAULT_FONT: &str = "DejaVu Serif";
|
|
||||||
|
|
||||||
let font_family = loader.load(SYSTEM_DEFAULT_FONT);
|
|
||||||
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
|
||||||
assert_eq!(
|
|
||||||
&result,
|
|
||||||
&loader
|
|
||||||
.cache
|
|
||||||
.get(&SYSTEM_DEFAULT_FONT.to_string())
|
|
||||||
.unwrap()
|
|
||||||
.fonts
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.font
|
|
||||||
.full_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_random_system_font() {
|
|
||||||
let mut loader = FontLoader::new();
|
|
||||||
|
|
||||||
let font_family = loader.get_random_system_font_family();
|
|
||||||
let font_name = loader.random_font_name.unwrap();
|
|
||||||
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
|
||||||
assert_eq!(
|
|
||||||
&result,
|
|
||||||
&loader
|
|
||||||
.cache
|
|
||||||
.get(&font_name)
|
|
||||||
.unwrap()
|
|
||||||
.fonts
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.font
|
|
||||||
.full_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,190 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use font_kit::metrics::Metrics;
|
||||||
|
use log::{trace, warn};
|
||||||
|
use lru::LruCache;
|
||||||
|
use skribo::{FontCollection, FontRef as SkriboFont, LayoutSession, TextStyle};
|
||||||
|
use skulpin::skia_safe::{Font as SkiaFont, TextBlob, TextBlobBuilder};
|
||||||
|
|
||||||
|
use super::font_loader::*;
|
||||||
|
use super::font_options::*;
|
||||||
|
use super::utils::*;
|
||||||
|
|
||||||
|
const STANDARD_CHARACTER_STRING: &str =
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||||
|
|
||||||
|
#[cfg(any(feature = "embed-fonts", test))]
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "assets/fonts/"]
|
||||||
|
pub struct Asset;
|
||||||
|
|
||||||
|
const DEFAULT_FONT_SIZE: f32 = 14.0;
|
||||||
|
|
||||||
|
#[derive(new, Clone, Hash, PartialEq, Eq, Debug)]
|
||||||
|
struct ShapeKey {
|
||||||
|
pub text: String,
|
||||||
|
pub bold: bool,
|
||||||
|
pub italic: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FontSet {
|
||||||
|
normal: FontCollection,
|
||||||
|
bold: FontCollection,
|
||||||
|
italic: FontCollection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontSet {
|
||||||
|
fn new(fallback_list: &[String], loader: &mut FontLoader) -> FontSet {
|
||||||
|
FontSet {
|
||||||
|
normal: loader
|
||||||
|
.build_collection_by_font_name(fallback_list, build_properties(false, false)),
|
||||||
|
bold: loader
|
||||||
|
.build_collection_by_font_name(fallback_list, build_properties(true, false)),
|
||||||
|
italic: loader
|
||||||
|
.build_collection_by_font_name(fallback_list, build_properties(false, true)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, bold: bool, italic: bool) -> &FontCollection {
|
||||||
|
match (bold, italic) {
|
||||||
|
(true, _) => &self.bold,
|
||||||
|
(false, false) => &self.normal,
|
||||||
|
(false, true) => &self.italic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CachingShaper {
|
||||||
|
pub options: FontOptions,
|
||||||
|
font_set: FontSet,
|
||||||
|
font_loader: FontLoader,
|
||||||
|
font_cache: LruCache<String, SkiaFont>,
|
||||||
|
blob_cache: LruCache<ShapeKey, Vec<TextBlob>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CachingShaper {
|
||||||
|
pub fn new() -> CachingShaper {
|
||||||
|
let options = FontOptions::new(String::from(SYSTEM_DEFAULT_FONT), DEFAULT_FONT_SIZE);
|
||||||
|
let mut loader = FontLoader::new();
|
||||||
|
let font_set = FontSet::new(&options.fallback_list, &mut loader);
|
||||||
|
|
||||||
|
CachingShaper {
|
||||||
|
options,
|
||||||
|
font_set,
|
||||||
|
font_loader: loader,
|
||||||
|
font_cache: LruCache::new(10),
|
||||||
|
blob_cache: LruCache::new(10000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_skia_font(&mut self, skribo_font: &SkriboFont) -> Option<&SkiaFont> {
|
||||||
|
let font_name = skribo_font.font.postscript_name()?;
|
||||||
|
if !self.font_cache.contains(&font_name) {
|
||||||
|
let font = build_skia_font_from_skribo_font(skribo_font, self.options.size)?;
|
||||||
|
self.font_cache.put(font_name.clone(), font);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.font_cache.get(&font_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metrics(&self) -> Metrics {
|
||||||
|
self.font_set
|
||||||
|
.normal
|
||||||
|
.itemize("a")
|
||||||
|
.next()
|
||||||
|
.expect("Cannot get font metrics")
|
||||||
|
.1
|
||||||
|
.font
|
||||||
|
.metrics()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shape(&mut self, text: &str, bold: bool, italic: bool) -> Vec<TextBlob> {
|
||||||
|
let style = TextStyle {
|
||||||
|
size: self.options.size,
|
||||||
|
};
|
||||||
|
let session = LayoutSession::create(text, &style, &self.font_set.get(bold, italic));
|
||||||
|
let metrics = self.metrics();
|
||||||
|
let ascent = metrics.ascent * self.options.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();
|
||||||
|
|
||||||
|
if let Some(skia_font) = self.get_skia_font(&skribo_font) {
|
||||||
|
let mut blob_builder = TextBlobBuilder::new();
|
||||||
|
let count = layout_run.glyphs().count();
|
||||||
|
let (glyphs, positions) =
|
||||||
|
blob_builder.alloc_run_pos_h(&skia_font, count, ascent, None);
|
||||||
|
|
||||||
|
for (i, glyph) in layout_run.glyphs().enumerate() {
|
||||||
|
glyphs[i] = glyph.glyph_id as u16;
|
||||||
|
positions[i] = glyph.offset.x();
|
||||||
|
}
|
||||||
|
|
||||||
|
blobs.push(blob_builder.make().unwrap());
|
||||||
|
} else {
|
||||||
|
warn!("Could not load skribo font");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blobs
|
||||||
|
}
|
||||||
|
|
||||||
|
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, bold, italic);
|
||||||
|
self.blob_cache.put(key.clone(), blobs);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.blob_cache.get(&key).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_font(&mut self, guifont_setting: &str) -> bool {
|
||||||
|
let updated = self.options.update(guifont_setting);
|
||||||
|
if updated {
|
||||||
|
trace!("Font changed: {:?}", self.options);
|
||||||
|
self.font_set = FontSet::new(&self.options.fallback_list, &mut self.font_loader);
|
||||||
|
self.font_cache.clear();
|
||||||
|
self.blob_cache.clear();
|
||||||
|
}
|
||||||
|
updated
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn font_base_dimensions(&mut self) -> (f32, f32) {
|
||||||
|
let metrics = self.metrics();
|
||||||
|
let font_height =
|
||||||
|
(metrics.ascent - metrics.descent) * self.options.size / metrics.units_per_em as f32;
|
||||||
|
let style = TextStyle {
|
||||||
|
size: self.options.size,
|
||||||
|
};
|
||||||
|
let session =
|
||||||
|
LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.font_set.normal);
|
||||||
|
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();
|
||||||
|
|
||||||
|
for advance in glyph_advances.iter() {
|
||||||
|
amounts
|
||||||
|
.entry(advance.to_string())
|
||||||
|
.and_modify(|e| *e += 1)
|
||||||
|
.or_insert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (font_width, _) = amounts.into_iter().max_by_key(|(_, count)| *count).unwrap();
|
||||||
|
let font_width = font_width.parse::<f32>().unwrap();
|
||||||
|
|
||||||
|
(font_width, font_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn underline_position(&mut self) -> f32 {
|
||||||
|
let metrics = self.metrics();
|
||||||
|
-metrics.underline_position * self.options.size / metrics.units_per_em as f32
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
use font_kit::{family_handle::FamilyHandle, font::Font, properties::Properties};
|
||||||
|
use skribo::{FontFamily, FontRef as SkriboFont};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ExtendedFontFamily {
|
||||||
|
pub fonts: Vec<SkriboFont>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ExtendedFontFamily {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtendedFontFamily {
|
||||||
|
pub fn new() -> ExtendedFontFamily {
|
||||||
|
ExtendedFontFamily { fonts: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_font(&mut self, font: SkriboFont) {
|
||||||
|
self.fonts.push(font);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, props: Properties) -> Option<&Font> {
|
||||||
|
if let Some(first_handle) = &self.fonts.first() {
|
||||||
|
for handle in &self.fonts {
|
||||||
|
let font = &handle.font;
|
||||||
|
let properties = font.properties();
|
||||||
|
|
||||||
|
if properties.weight == props.weight
|
||||||
|
&& properties.style == props.style
|
||||||
|
&& properties.stretch == props.stretch
|
||||||
|
{
|
||||||
|
return Some(&font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(&first_handle.font);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FamilyHandle> for ExtendedFontFamily {
|
||||||
|
fn from(handle: FamilyHandle) -> Self {
|
||||||
|
handle
|
||||||
|
.fonts()
|
||||||
|
.iter()
|
||||||
|
.fold(ExtendedFontFamily::new(), |mut family, font| {
|
||||||
|
if let Ok(font) = font.load() {
|
||||||
|
family.add_font(SkriboFont::new(font));
|
||||||
|
}
|
||||||
|
family
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ExtendedFontFamily> for FontFamily {
|
||||||
|
fn from(extended_font_family: ExtendedFontFamily) -> Self {
|
||||||
|
extended_font_family
|
||||||
|
.fonts
|
||||||
|
.iter()
|
||||||
|
.fold(FontFamily::new(), |mut new_family, font| {
|
||||||
|
new_family.add_font(font.clone());
|
||||||
|
new_family
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use font_kit::properties::{Properties, Stretch, Style, Weight};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::renderer::fonts::caching_shaper::Asset;
|
||||||
|
|
||||||
|
const PROPERTIES: Properties = Properties {
|
||||||
|
weight: Weight::NORMAL,
|
||||||
|
style: Style::Normal,
|
||||||
|
stretch: Stretch::NORMAL,
|
||||||
|
};
|
||||||
|
const EXTRA_SYMBOL_FONT: &str = "Extra Symbols.otf";
|
||||||
|
|
||||||
|
fn dummy_font() -> SkriboFont {
|
||||||
|
SkriboFont::new(
|
||||||
|
Asset::get(EXTRA_SYMBOL_FONT)
|
||||||
|
.and_then(|font_data| Font::from_bytes(font_data.to_vec().into(), 0).ok())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_font() {
|
||||||
|
let mut eft = ExtendedFontFamily::new();
|
||||||
|
let font = dummy_font();
|
||||||
|
eft.add_font(font.clone());
|
||||||
|
assert_eq!(
|
||||||
|
eft.fonts.first().unwrap().font.full_name(),
|
||||||
|
font.font.full_name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get() {
|
||||||
|
let mut eft = ExtendedFontFamily::new();
|
||||||
|
assert!(eft.get(PROPERTIES).is_none());
|
||||||
|
|
||||||
|
let font = dummy_font();
|
||||||
|
eft.fonts.push(font.clone());
|
||||||
|
assert_eq!(
|
||||||
|
eft.get(font.font.properties()).unwrap().full_name(),
|
||||||
|
font.font.full_name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,278 @@
|
|||||||
|
use std::iter;
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use font_kit::{properties::Properties, source::SystemSource};
|
||||||
|
use lru::LruCache;
|
||||||
|
use rand::Rng;
|
||||||
|
use skribo::{FontCollection, FontFamily};
|
||||||
|
|
||||||
|
#[cfg(any(feature = "embed-fonts", test))]
|
||||||
|
use super::caching_shaper::Asset;
|
||||||
|
use super::extended_font_family::*;
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(target_os = "windows")] {
|
||||||
|
pub const SYSTEM_DEFAULT_FONT: &str = "Consolas";
|
||||||
|
pub const SYSTEM_SYMBOL_FONT: &str = "Segoe UI Symbol";
|
||||||
|
pub const SYSTEM_EMOJI_FONT: &str = "Segoe UI Emoji";
|
||||||
|
} else if #[cfg(target_os = "linux")] {
|
||||||
|
pub const SYSTEM_DEFAULT_FONT: &str = "Noto Sans Mono";
|
||||||
|
pub const SYSTEM_SYMBOL_FONT: &str = "Noto Sans Mono";
|
||||||
|
pub const SYSTEM_EMOJI_FONT: &str = "Noto Color Emoji";
|
||||||
|
} else if #[cfg(target_os = "macos")] {
|
||||||
|
pub const SYSTEM_DEFAULT_FONT: &str = "Menlo";
|
||||||
|
pub const SYSTEM_SYMBOL_FONT: &str = "Apple Symbols";
|
||||||
|
pub const SYSTEM_EMOJI_FONT: &str = "Apple Color Emoji";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const EXTRA_SYMBOL_FONT: &str = "Extra Symbols.otf";
|
||||||
|
pub const MISSING_GLYPH_FONT: &str = "Missing Glyphs.otf";
|
||||||
|
|
||||||
|
pub struct FontLoader {
|
||||||
|
cache: LruCache<String, ExtendedFontFamily>,
|
||||||
|
source: SystemSource,
|
||||||
|
random_font_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontLoader {
|
||||||
|
pub fn new() -> FontLoader {
|
||||||
|
FontLoader {
|
||||||
|
cache: LruCache::new(10),
|
||||||
|
source: SystemSource::new(),
|
||||||
|
random_font_name: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
||||||
|
self.cache.get(&String::from(font_name)).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "embed-fonts", test))]
|
||||||
|
fn load_from_asset(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
||||||
|
use font_kit::font::Font;
|
||||||
|
use skribo::FontRef as SkriboFont;
|
||||||
|
let mut family = ExtendedFontFamily::new();
|
||||||
|
|
||||||
|
if let Some(font) = Asset::get(font_name)
|
||||||
|
.and_then(|font_data| Font::from_bytes(font_data.to_vec().into(), 0).ok())
|
||||||
|
{
|
||||||
|
family.add_font(SkriboFont::new(font));
|
||||||
|
self.cache.put(String::from(font_name), family);
|
||||||
|
self.get(font_name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "embed-fonts", test)))]
|
||||||
|
fn load_from_asset(&self, font_name: &str) -> Option<ExtendedFontFamily> {
|
||||||
|
log::warn!(
|
||||||
|
"Tried to load {} from assets but build didn't include embed-fonts feature",
|
||||||
|
font_name
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
||||||
|
let handle = match self.source.select_family_by_name(font_name) {
|
||||||
|
Ok(it) => it,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !handle.is_empty() {
|
||||||
|
let family = ExtendedFontFamily::from(handle);
|
||||||
|
self.cache.put(String::from(font_name), family);
|
||||||
|
self.get(font_name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_random_system_font_family(&mut self) -> Option<ExtendedFontFamily> {
|
||||||
|
if let Some(font) = self.random_font_name.clone() {
|
||||||
|
self.get(&font)
|
||||||
|
} else {
|
||||||
|
let font_names = self.source.all_families().expect("fonts exist");
|
||||||
|
let n = rand::thread_rng().gen::<usize>() % font_names.len();
|
||||||
|
let font_name = &font_names[n];
|
||||||
|
self.random_font_name = Some(font_name.clone());
|
||||||
|
self.load(&font_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_or_load(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
||||||
|
if let Some(cached) = self.get(font_name) {
|
||||||
|
Some(cached)
|
||||||
|
} else if let Some(loaded) = self.load(font_name) {
|
||||||
|
Some(loaded)
|
||||||
|
} else {
|
||||||
|
self.load_from_asset(font_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_collection_by_font_name(
|
||||||
|
&mut self,
|
||||||
|
fallback_list: &[String],
|
||||||
|
properties: Properties,
|
||||||
|
) -> FontCollection {
|
||||||
|
let mut collection = FontCollection::new();
|
||||||
|
|
||||||
|
let gui_fonts = fallback_list
|
||||||
|
.iter()
|
||||||
|
.map(|fallback_item| fallback_item.as_ref())
|
||||||
|
.chain(iter::once(SYSTEM_DEFAULT_FONT));
|
||||||
|
|
||||||
|
for font_name in gui_fonts {
|
||||||
|
if let Some(family) = self.get_or_load(font_name) {
|
||||||
|
if let Some(font) = family.get(properties) {
|
||||||
|
collection.add_family(FontFamily::new_from_font(font.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for font in &[
|
||||||
|
SYSTEM_SYMBOL_FONT,
|
||||||
|
SYSTEM_EMOJI_FONT,
|
||||||
|
EXTRA_SYMBOL_FONT,
|
||||||
|
MISSING_GLYPH_FONT,
|
||||||
|
] {
|
||||||
|
if let Some(family) = self.get_or_load(font) {
|
||||||
|
collection.add_family(FontFamily::from(family));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.cache.is_empty() {
|
||||||
|
let font_family = self.get_random_system_font_family();
|
||||||
|
collection.add_family(FontFamily::from(font_family.expect("font family loaded")));
|
||||||
|
}
|
||||||
|
collection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use font_kit::{
|
||||||
|
font::Font,
|
||||||
|
properties::{Properties, Stretch, Style, Weight},
|
||||||
|
};
|
||||||
|
use skribo::FontRef as SkriboFont;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::renderer::fonts::utils::*;
|
||||||
|
|
||||||
|
const PROPERTIES1: Properties = Properties {
|
||||||
|
weight: Weight::NORMAL,
|
||||||
|
style: Style::Normal,
|
||||||
|
stretch: Stretch::NORMAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROPERTIES2: Properties = Properties {
|
||||||
|
weight: Weight::BOLD,
|
||||||
|
style: Style::Normal,
|
||||||
|
stretch: Stretch::NORMAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROPERTIES3: Properties = Properties {
|
||||||
|
weight: Weight::NORMAL,
|
||||||
|
style: Style::Italic,
|
||||||
|
stretch: Stretch::NORMAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROPERTIES4: Properties = Properties {
|
||||||
|
weight: Weight::BOLD,
|
||||||
|
style: Style::Italic,
|
||||||
|
stretch: Stretch::NORMAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn dummy_font() -> SkriboFont {
|
||||||
|
SkriboFont::new(
|
||||||
|
Asset::get(EXTRA_SYMBOL_FONT)
|
||||||
|
.and_then(|font_data| Font::from_bytes(font_data.to_vec().into(), 0).ok())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_properties() {
|
||||||
|
assert_eq!(build_properties(false, false), PROPERTIES1);
|
||||||
|
assert_eq!(build_properties(true, false), PROPERTIES2);
|
||||||
|
assert_eq!(build_properties(false, true), PROPERTIES3);
|
||||||
|
assert_eq!(build_properties(true, true), PROPERTIES4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_from_asset() {
|
||||||
|
let mut loader = FontLoader::new();
|
||||||
|
|
||||||
|
let font_family = loader.load_from_asset("");
|
||||||
|
assert!(font_family.is_none());
|
||||||
|
|
||||||
|
let font = dummy_font();
|
||||||
|
let mut eft = ExtendedFontFamily::new();
|
||||||
|
eft.add_font(font.clone());
|
||||||
|
let font_family = loader.load_from_asset(EXTRA_SYMBOL_FONT);
|
||||||
|
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
||||||
|
assert_eq!(&result, &eft.fonts.first().unwrap().font.full_name());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&result,
|
||||||
|
&loader
|
||||||
|
.cache
|
||||||
|
.get(&EXTRA_SYMBOL_FONT.to_string())
|
||||||
|
.unwrap()
|
||||||
|
.fonts
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.font
|
||||||
|
.full_name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load() {
|
||||||
|
let mut loader = FontLoader::new();
|
||||||
|
let junk_text = "uhasiudhaiudshiaushd";
|
||||||
|
let font_family = loader.load(junk_text);
|
||||||
|
assert!(font_family.is_none());
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const SYSTEM_DEFAULT_FONT: &str = "DejaVu Serif";
|
||||||
|
|
||||||
|
let font_family = loader.load(SYSTEM_DEFAULT_FONT);
|
||||||
|
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
||||||
|
assert_eq!(
|
||||||
|
&result,
|
||||||
|
&loader
|
||||||
|
.cache
|
||||||
|
.get(&SYSTEM_DEFAULT_FONT.to_string())
|
||||||
|
.unwrap()
|
||||||
|
.fonts
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.font
|
||||||
|
.full_name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_random_system_font() {
|
||||||
|
let mut loader = FontLoader::new();
|
||||||
|
|
||||||
|
let font_family = loader.get_random_system_font_family();
|
||||||
|
let font_name = loader.random_font_name.unwrap();
|
||||||
|
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
||||||
|
assert_eq!(
|
||||||
|
&result,
|
||||||
|
&loader
|
||||||
|
.cache
|
||||||
|
.get(&font_name)
|
||||||
|
.unwrap()
|
||||||
|
.fonts
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.font
|
||||||
|
.full_name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
pub mod caching_shaper;
|
||||||
|
mod extended_font_family;
|
||||||
|
mod font_loader;
|
||||||
|
mod font_options;
|
||||||
|
mod utils;
|
@ -0,0 +1,24 @@
|
|||||||
|
use font_kit::properties::{Properties, Stretch, Style, Weight};
|
||||||
|
use skribo::FontRef as SkriboFont;
|
||||||
|
use skulpin::skia_safe::{Data, Font as SkiaFont, Typeface};
|
||||||
|
|
||||||
|
pub fn build_skia_font_from_skribo_font(
|
||||||
|
skribo_font: &SkriboFont,
|
||||||
|
base_size: f32,
|
||||||
|
) -> Option<SkiaFont> {
|
||||||
|
let font_data = skribo_font.font.copy_font_data()?;
|
||||||
|
let skia_data = Data::new_copy(&font_data[..]);
|
||||||
|
let typeface = Typeface::from_data(skia_data, None)?;
|
||||||
|
|
||||||
|
Some(SkiaFont::from_typeface(typeface, base_size))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_properties(bold: bool, italic: bool) -> Properties {
|
||||||
|
let weight = if bold { Weight::BOLD } else { Weight::NORMAL };
|
||||||
|
let style = if italic { Style::Italic } else { Style::Normal };
|
||||||
|
Properties {
|
||||||
|
weight,
|
||||||
|
style,
|
||||||
|
stretch: Stretch::NORMAL,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue