From ec260da475ede65a63f7069c260ea5f409419650 Mon Sep 17 00:00:00 2001 From: keith Date: Tue, 28 Jan 2020 23:36:35 -0800 Subject: [PATCH] revive bold and italic support --- src/renderer/caching_shaper.rs | 96 ++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/src/renderer/caching_shaper.rs b/src/renderer/caching_shaper.rs index f8c8144..0796697 100644 --- a/src/renderer/caching_shaper.rs +++ b/src/renderer/caching_shaper.rs @@ -2,15 +2,17 @@ use std::collections::HashMap; use lru::LruCache; use skulpin::skia_safe::{TextBlob, Font as SkiaFont, Typeface, TextBlobBuilder, Data}; -use font_kit::{source::SystemSource, metrics::Metrics, properties::Properties, family_name::FamilyName, font::Font}; +use font_kit::{source::SystemSource, metrics::Metrics, properties::{Properties, Weight, Style, Stretch}, family_name::FamilyName, font::Font, }; use skribo::{LayoutSession, FontRef as SkriboFont, FontFamily, FontCollection, TextStyle}; const STANDARD_CHARACTER_STRING: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; const MONOSPACE_FONT: &'static str = "Fira Code Regular Nerd Font Complete.otf"; +const MONOSPACE_BOLD_FONT: &'static str = "Fira Code Bold Nerd Font Complete.otf"; const SYMBOL_FONT: &'static str = "DejaVuSansMono.ttf"; const EMOJI_FONT: &'static str = "NotoColorEmoji.ttf"; const WIDE_FONT: &'static str = "NotoSansMonoCJKjp-Regular.otf"; +const WIDE_BOLD_FONT: &'static str = "NotoSansMonoCJKjp-Bold.otf"; #[derive(RustEmbed)] #[folder = "assets/fonts/"] @@ -25,27 +27,46 @@ struct ShapeKey { pub italic: bool } -pub struct CachingShaper { - pub font_name: Option, - pub base_size: f32, - collection: FontCollection, - font_cache: LruCache, - blob_cache: LruCache> +struct FontSet { + normal: FontCollection, + bold: FontCollection, + italic: FontCollection, + bold_italic: FontCollection, } -fn build_collection_by_font_name(font_name: Option<&str>) -> FontCollection { +fn build_collection_by_font_name(font_name: Option<&str>, bold: bool, italic: bool) -> FontCollection { let source = SystemSource::new(); let mut collection = FontCollection::new(); if let Some(font_name) = font_name { - if let Ok(custom) = source.select_best_match(&[FamilyName::Title(font_name.to_string())], &Properties::new()) { + let weight = if bold { + Weight::BOLD + } else { + Weight::NORMAL + }; + + let style = if italic { + Style::Italic + } else { + Style::Normal + }; + + let properties = Properties { + weight, style, stretch: Stretch::NORMAL + }; + if let Ok(custom) = source.select_best_match(&[FamilyName::Title(font_name.to_string())], &properties) { let font = custom.load().unwrap(); collection.add_family(FontFamily::new_from_font(font)); } } - let monospace_data = Asset::get(MONOSPACE_FONT).expect("Failed to read monospace font data"); + let monospace_style = if bold { + MONOSPACE_BOLD_FONT + } else { + MONOSPACE_FONT + }; + let monospace_data = Asset::get(monospace_style).expect("Failed to read monospace font data"); let monospace_font = Font::from_bytes(monospace_data.to_vec().into(), 0).expect("Failed to parse monospace font data"); collection.add_family(FontFamily::new_from_font(monospace_font)); @@ -53,7 +74,12 @@ fn build_collection_by_font_name(font_name: Option<&str>) -> FontCollection { let emoji_font = Font::from_bytes(emoji_data.to_vec().into(), 0).expect("Failed to parse emoji font data"); collection.add_family(FontFamily::new_from_font(emoji_font)); - let wide_data = Asset::get(WIDE_FONT).expect("Failed to read wide font data"); + let wide_style = if bold { + WIDE_BOLD_FONT + } else { + WIDE_FONT + }; + let wide_data = Asset::get(wide_style).expect("Failed to read wide font data"); let wide_font = Font::from_bytes(wide_data.to_vec().into(), 0).expect("Failed to parse wide font data"); collection.add_family(FontFamily::new_from_font(wide_font)); @@ -64,7 +90,36 @@ fn build_collection_by_font_name(font_name: Option<&str>) -> FontCollection { collection } -fn build_skia_font_from_skribo_font(skribo_font: &SkriboFont, base_size: f32) -> SkiaFont { +impl FontSet { + fn new(font_name: Option<&str>) -> FontSet { + FontSet { + normal: build_collection_by_font_name(font_name, false, false), + bold: build_collection_by_font_name(font_name, true, false), + italic: build_collection_by_font_name(font_name, false, true), + bold_italic: build_collection_by_font_name(font_name, true, true), + } + } + + fn get(&self, bold: bool, italic: bool) -> &FontCollection { + match (bold, italic) { + (false, false) => &self.normal, + (true, false) => &self.bold, + (false, true) => &self.italic, + (true, true) => &self.bold_italic + } + } +} + +pub struct CachingShaper { + pub font_name: Option, + pub base_size: f32, + font_set: FontSet, + font_cache: LruCache, + blob_cache: LruCache> +} + + +fn build_skia_font_from_skribo_font(skribo_font: &SkriboFont, base_size: f32, bold: bool, italic: bool) -> 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(); @@ -77,17 +132,16 @@ impl CachingShaper { CachingShaper { font_name: None, base_size: DEFAULT_FONT_SIZE, - collection: build_collection_by_font_name(None), + font_set: FontSet::new(None), font_cache: LruCache::new(100), blob_cache: LruCache::new(10000), } } - - fn get_skia_font(&mut self, skribo_font: &SkriboFont) -> &SkiaFont { + fn get_skia_font(&mut self, skribo_font: &SkriboFont, bold: bool, italic: bool) -> &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); + let font = build_skia_font_from_skribo_font(skribo_font, self.base_size, bold, italic); self.font_cache.put(font_name.clone(), font); } @@ -95,13 +149,13 @@ impl CachingShaper { } fn metrics(&self) -> Metrics { - self.collection.itemize("a").next().unwrap().1.font.metrics() + self.font_set.normal.itemize("a").next().unwrap().1.font.metrics() } pub fn shape(&mut self, text: &str, bold: bool, italic: bool) -> Vec { let style = TextStyle { size: self.base_size }; - let session = LayoutSession::create(text, &style, &self.collection); + let session = LayoutSession::create(text, &style, &self.font_set.get(bold, italic)); let metrics = self.metrics(); let ascent = metrics.ascent * self.base_size / metrics.units_per_em as f32; @@ -110,7 +164,7 @@ impl CachingShaper { for layout_run in session.iter_all() { let skribo_font = layout_run.font(); - let skia_font = self.get_skia_font(&skribo_font); + let skia_font = self.get_skia_font(&skribo_font, bold, italic); let mut blob_builder = TextBlobBuilder::new(); @@ -140,7 +194,7 @@ impl CachingShaper { pub fn change_font(&mut self, font_name: Option<&str>, base_size: Option) { 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_set = FontSet::new(font_name); self.font_cache.clear(); self.blob_cache.clear(); } @@ -150,7 +204,7 @@ impl CachingShaper { let font_height = (metrics.ascent - metrics.descent) * self.base_size / metrics.units_per_em as f32; let style = TextStyle { size: self.base_size }; - let session = LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.collection); + let session = LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.font_set.normal); let layout_run = session.iter_all().next().unwrap(); let glyph_offsets: Vec = layout_run.glyphs().map(|glyph| glyph.offset.x).collect();