Rebrand to Galaxy, major improvements to Bedrock support, still needs some TLC though
This commit is contained in:
@@ -0,0 +1,656 @@
|
||||
use super::text_layout::{layout_line, layout_text};
|
||||
use crate::fonts::font_kit::{properties_to_font_kit, Rasterizer};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use core_foundation::array::{CFArray, CFArrayRef};
|
||||
use core_foundation::base::{CFType, ItemRef, TCFType};
|
||||
use core_foundation::dictionary::CFDictionary;
|
||||
use core_foundation::string::{CFString, CFStringRef, UniChar};
|
||||
use core_graphics::display::CGSize;
|
||||
use core_graphics::font::CGGlyph;
|
||||
use core_text::font::{cascade_list_for_languages as ct_cascade_list_for_languages, CTFont};
|
||||
use core_text::font_descriptor::{
|
||||
kCTFontFamilyNameAttribute, kCTFontLanguagesAttribute, kCTFontNameAttribute,
|
||||
kCTFontOrientationHorizontal, CTFontDescriptor, CTFontDescriptorCopyAttribute,
|
||||
SymbolicTraitAccessors, TraitAccessors,
|
||||
};
|
||||
use core_text::{font, font_collection, font_descriptor};
|
||||
use dashmap::{mapref::entry::Entry, DashMap};
|
||||
use font_kit::font::Font;
|
||||
use font_kit::loaders::core_text::NativeFont;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt as _;
|
||||
use itertools::Itertools as _;
|
||||
use ordered_float::OrderedFloat;
|
||||
use pathfinder_geometry::rect::RectI;
|
||||
use pathfinder_geometry::vector::{Vector2F, Vector2I};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use galaxyui_core::fonts::{
|
||||
canvas::RasterFormat, FamilyId, FontId, FontInfo, GlyphId, Metrics, Properties,
|
||||
RasterizedGlyph, SubpixelAlignment,
|
||||
};
|
||||
use galaxyui_core::platform::{self, FontDB as _, LineStyle, TextLayoutSystem};
|
||||
use galaxyui_core::rendering;
|
||||
use galaxyui_core::text_layout::{ClipConfig, StyleAndFont, TextAlignment, TextFrame};
|
||||
|
||||
struct FontFamily {
|
||||
name: String,
|
||||
fonts: Vec<Font>,
|
||||
}
|
||||
|
||||
mod loader {
|
||||
use super::*;
|
||||
|
||||
// Font-kit loads fonts by copying their font file into the running memory, which
|
||||
// is extremely inefficient. Thus, for system fonts, we load these fonts by reference
|
||||
// through CTFontDescriptorCreateWithAttributes function and create a dummy font-kit
|
||||
// Font interface to access its functions.
|
||||
pub fn load_system_font(font_family: &str) -> Result<FontFamily> {
|
||||
let Some(descriptors) = FontDB::descriptors_for_family(font_family) else {
|
||||
bail!(
|
||||
"could not find a non-empty font family matching one of the given names {:?}",
|
||||
font_family
|
||||
);
|
||||
};
|
||||
let mut fonts = Vec::with_capacity(descriptors.len() as usize);
|
||||
for fontdesc in descriptors.into_iter() {
|
||||
// The font size here does not affect our rendering. In CTFont, pt_size
|
||||
// is used to calculate font metrics like ascent, descent, etc. However,
|
||||
// font-kit creates its own layer of calculating font metrics at render time
|
||||
// so we just need a place-holder here for getting the CTFont object. Use
|
||||
// 16.0 here as it is consistent with https://docs.rs/core-text/19.2.0/src/core_text/font.rs.html#130
|
||||
let font = Font::from_ct_font(font::new_from_descriptor(&fontdesc, DEFAULT_FONT_SIZE));
|
||||
|
||||
let glyph_id = font.glyph_for_char('m');
|
||||
if glyph_id.is_none() {
|
||||
return Err(anyhow!("font must contain a glyph for the 'm' character"));
|
||||
}
|
||||
|
||||
fonts.push(font);
|
||||
}
|
||||
Ok(FontFamily {
|
||||
fonts,
|
||||
name: font_family.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_all_system_fonts() -> LoadedSystemFonts {
|
||||
let collection = font_collection::create_for_all_families();
|
||||
let Some(descriptors) = collection.get_descriptors() else {
|
||||
return LoadedSystemFonts(vec![]);
|
||||
};
|
||||
|
||||
let mut fonts: Vec<(FontInfo, FontFamily)> = Vec::with_capacity(descriptors.len() as usize);
|
||||
for descriptor in descriptors.iter() {
|
||||
let name = match unsafe { FontDB::get_family_name(&descriptor) } {
|
||||
Some(family) => family,
|
||||
None => {
|
||||
log::warn!("Failed to load the font as it does not have a valid family name.");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let internal_name = match unsafe { FontDB::get_font_name(&descriptor) } {
|
||||
Some(family) => family,
|
||||
None => {
|
||||
log::warn!("Failed to load the font as it does not have a valid font name.");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// We should only load languages that support english
|
||||
if !FontDB::supports_english(&descriptor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(idx) = fonts.iter().position(|font| font.0.family_name == name) {
|
||||
// Some font families (e.g. Osaka) could contains both monospace
|
||||
// and variable-width fonts. To make sure the returned font family
|
||||
// info is consistent everytime, we set is_monospace to true if the
|
||||
// family contains one font that is monospace.
|
||||
if descriptor.traits().symbolic_traits().is_monospace()
|
||||
&& !fonts[idx].0.is_monospace
|
||||
{
|
||||
// Updating here since font family names are guaranteed to be unique.
|
||||
fonts[idx].0.is_monospace = true;
|
||||
}
|
||||
// Since we keep track of font families, add this font as a possible style.
|
||||
fonts[idx].0.font_names.push(internal_name)
|
||||
} else {
|
||||
let font_family = match load_system_font(&name) {
|
||||
Ok(font) => font,
|
||||
Err(err) => {
|
||||
log::debug!("Failed to load {}: {:?}", name.as_str(), err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
fonts.push((
|
||||
FontInfo {
|
||||
family_name: name,
|
||||
font_names: vec![internal_name],
|
||||
is_monospace: descriptor.traits().symbolic_traits().is_monospace(),
|
||||
},
|
||||
font_family,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
LoadedSystemFonts(fonts)
|
||||
}
|
||||
|
||||
// We use font-kit's family handle to load fonts that come with Warp as
|
||||
// these binaries are already in memory and won't increase our memory load.
|
||||
pub fn load_font_family_from_bytes(name: &str, font_bytes: Vec<Vec<u8>>) -> Result<FontFamily> {
|
||||
let mut fonts = Vec::with_capacity(font_bytes.len());
|
||||
|
||||
for font in font_bytes {
|
||||
let Ok(font) = Font::from_bytes(Arc::new(font), 0) else {
|
||||
log::info!("Unable to parse font bytes for font {name:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
let glyph_id = font.glyph_for_char('m');
|
||||
if glyph_id.is_none() {
|
||||
return Err(anyhow!("font must contain a glyph for the 'm' character"));
|
||||
}
|
||||
fonts.push(font);
|
||||
}
|
||||
|
||||
Ok(FontFamily {
|
||||
fonts,
|
||||
name: name.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct LoadedSystemFonts(Vec<(FontInfo, FontFamily)>);
|
||||
|
||||
impl platform::LoadedSystemFonts for LoadedSystemFonts {
|
||||
fn as_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
self as Box<dyn Any>
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FontDB {
|
||||
next_family_id: AtomicUsize,
|
||||
families: HashMap<FamilyId, Family>,
|
||||
next_font_id: AtomicUsize,
|
||||
rasterizer: Rasterizer,
|
||||
font_names: DashMap<FontId, Arc<String>>,
|
||||
native_fonts: DashMap<(FontId, OrderedFloat<f32>), NativeFont>,
|
||||
fonts_by_name: DashMap<Arc<String>, FontId>,
|
||||
fallback_fonts: DashMap<FontId, Arc<Vec<FontId>>>,
|
||||
metrics: DashMap<FontId, Metrics>,
|
||||
font_selections: DashMap<(FamilyId, Properties), FontId>,
|
||||
space_advances: DashMap<FontId, Option<f64>>,
|
||||
}
|
||||
|
||||
struct Family {
|
||||
name: String,
|
||||
font_ids: Vec<FontId>,
|
||||
}
|
||||
|
||||
impl Default for FontDB {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_FONT_SIZE: f64 = 16.0;
|
||||
|
||||
// Returns the horizontal advance (in points) of a single space in the given font.
|
||||
fn space_advance_width(font: &CTFont) -> Option<f64> {
|
||||
let space_char: UniChar = ' ' as u16;
|
||||
let mut glyph: CGGlyph = 0;
|
||||
|
||||
let ok =
|
||||
unsafe { font.get_glyphs_for_characters(&space_char as *const UniChar, &mut glyph, 1) };
|
||||
if !ok || glyph == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut advance = CGSize {
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
};
|
||||
unsafe {
|
||||
font.get_advances_for_glyphs(
|
||||
kCTFontOrientationHorizontal,
|
||||
&glyph as *const CGGlyph,
|
||||
&mut advance as *mut CGSize,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
let width = advance.width;
|
||||
(width.is_finite() && width > 0.0).then_some(width)
|
||||
}
|
||||
|
||||
impl FontDB {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
next_family_id: Default::default(),
|
||||
families: Default::default(),
|
||||
next_font_id: Default::default(),
|
||||
rasterizer: Rasterizer::new(),
|
||||
font_names: Default::default(),
|
||||
native_fonts: Default::default(),
|
||||
fonts_by_name: Default::default(),
|
||||
fallback_fonts: Default::default(),
|
||||
metrics: Default::default(),
|
||||
font_selections: Default::default(),
|
||||
space_advances: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// This functions the same as the family_name method in core text font descriptor, but it returns
|
||||
// None instead of panicking when the descriptor does not include the family_name attribute.
|
||||
unsafe fn get_family_name(descriptor: &ItemRef<CTFontDescriptor>) -> Option<String> {
|
||||
let value = CTFontDescriptorCopyAttribute(
|
||||
descriptor.as_concrete_TypeRef(),
|
||||
kCTFontFamilyNameAttribute,
|
||||
);
|
||||
if value.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = CFType::wrap_under_create_rule(value);
|
||||
let s = CFString::wrap_under_get_rule(value.as_CFTypeRef() as CFStringRef);
|
||||
Some(s.to_string())
|
||||
}
|
||||
|
||||
unsafe fn get_font_name(descriptor: &ItemRef<CTFontDescriptor>) -> Option<String> {
|
||||
let value =
|
||||
CTFontDescriptorCopyAttribute(descriptor.as_concrete_TypeRef(), kCTFontNameAttribute);
|
||||
if value.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = CFType::wrap_under_create_rule(value);
|
||||
let s = CFString::wrap_under_get_rule(value.as_CFTypeRef() as CFStringRef);
|
||||
Some(s.to_string())
|
||||
}
|
||||
|
||||
pub fn fallback_fonts(&self, font_id: FontId) -> Vec<FontId> {
|
||||
self.fallback_fonts
|
||||
.get(&font_id)
|
||||
.expect("Font fallback should not be empty")
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
// Check if the font family supports english.
|
||||
fn supports_english(descriptor: &ItemRef<CTFontDescriptor>) -> bool {
|
||||
unsafe {
|
||||
let value = CTFontDescriptorCopyAttribute(
|
||||
descriptor.as_concrete_TypeRef(),
|
||||
kCTFontLanguagesAttribute,
|
||||
) as CFArrayRef;
|
||||
|
||||
if value.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let languages: CFArray<CFString> = CFArray::wrap_under_create_rule(value);
|
||||
languages.iter().any(|s| *s == "en")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_family_name_from_id(&self, id: FamilyId) -> Option<String> {
|
||||
self.families.get(&id).map(|s| s.name.clone())
|
||||
}
|
||||
|
||||
fn create_new_family_id(&self) -> FamilyId {
|
||||
FamilyId(self.next_family_id.fetch_add(1, Ordering::SeqCst))
|
||||
}
|
||||
|
||||
/// Return fallback descriptors for font/language list.
|
||||
/// Heavily inspired by crossfont's implementation:
|
||||
/// https://github.com/alacritty/crossfont/blob/d3515de22494c6fa70d84d2a9264c10097e303bd/src/darwin/mod.rs#L288
|
||||
fn cascade_list_for_languages(&self, ct_font: &CTFont, languages: &[&str]) -> Vec<FontId> {
|
||||
// Convert language type &Vec<String> -> CFArray.
|
||||
let langarr: CFArray<CFString> = {
|
||||
let tmp: Vec<CFString> = languages
|
||||
.iter()
|
||||
.map(|language| CFString::new(language))
|
||||
.collect();
|
||||
CFArray::from_CFTypes(&tmp)
|
||||
};
|
||||
|
||||
// CFArray of CTFontDescriptorRef (again).
|
||||
let list = ct_cascade_list_for_languages(ct_font, &langarr);
|
||||
|
||||
let mut fallback_fonts: Vec<FontId> = list
|
||||
.into_iter()
|
||||
.filter_map(|fontdesc| self.descriptor_to_font_id(fontdesc))
|
||||
.collect();
|
||||
|
||||
// While .Apple Symbols Fallback is not a valid font. Apple Symbols is and it provides
|
||||
// many fallback characters. This implementation is consistent with Alacritty:
|
||||
// See: https://github.com/alacritty/crossfont/blob/d3515de22494c6fa70d84d2a9264c10097e303bd/src/darwin/mod.rs#L91
|
||||
if let Some(font) = FontDB::descriptors_for_family("Apple Symbols")
|
||||
.as_ref()
|
||||
.and_then(|descriptor| descriptor.into_iter().next())
|
||||
.and_then(|font_descriptor| self.descriptor_to_font_id(font_descriptor))
|
||||
{
|
||||
fallback_fonts.push(font);
|
||||
}
|
||||
|
||||
fallback_fonts
|
||||
}
|
||||
|
||||
// Get a list of CTFontDescriptors for a font family.
|
||||
fn descriptors_for_family(name: &str) -> Option<CFArray<CTFontDescriptor>> {
|
||||
let attributes: CFDictionary<CFString, CFType> = CFDictionary::from_CFType_pairs(&[(
|
||||
CFString::new("NSFontFamilyAttribute"),
|
||||
CFString::new(name).as_CFType(),
|
||||
)]);
|
||||
|
||||
let descriptor = font_descriptor::new_from_attributes(&attributes);
|
||||
let collection_descriptors = &CFArray::from_CFTypes(&[descriptor]);
|
||||
let collection = font_collection::new_from_descriptors(collection_descriptors);
|
||||
|
||||
collection.get_descriptors()
|
||||
}
|
||||
|
||||
// Convert a CTFontDescriptor to font_id. This function does not load fallback fonts
|
||||
// and assumes the descriptor refers to a valid system font.
|
||||
fn descriptor_to_font_id(&self, fontdesc: ItemRef<CTFontDescriptor>) -> Option<FontId> {
|
||||
let name = match unsafe { FontDB::get_family_name(&fontdesc) } {
|
||||
Some(family) => family,
|
||||
None => {
|
||||
log::warn!("Failed to load the font as it does not have a valid family name.");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let font_name = match unsafe { FontDB::get_font_name(&fontdesc) } {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
log::warn!("Failed to load the font as it does not have a valid name.");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// We should not load fonts with name that starts with dot
|
||||
// https://developer.apple.com/videos/play/wwdc2019/227/?time=200
|
||||
(!name.starts_with('.')).then(|| {
|
||||
// Check if the fallback font is in cache.
|
||||
match self.fonts_by_name.entry(Arc::new(font_name)) {
|
||||
Entry::Occupied(entry) => return *entry.get(),
|
||||
Entry::Vacant(_) => (),
|
||||
}
|
||||
|
||||
// We need to push font after releasing the entry of the dashmap to prevent deadlocks.
|
||||
self.push_font(Font::from_ct_font(font::new_from_descriptor(
|
||||
&fontdesc,
|
||||
DEFAULT_FONT_SIZE,
|
||||
)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select_font(&self, family_id: FamilyId, properties: Properties) -> FontId {
|
||||
match self.font_selections.entry((family_id, properties)) {
|
||||
Entry::Occupied(entry) => *entry.get(),
|
||||
Entry::Vacant(entry) => {
|
||||
let family = &self
|
||||
.families
|
||||
.get(&family_id)
|
||||
.expect("FamilyId must correspond to a valid family");
|
||||
let candidates = family
|
||||
.font_ids
|
||||
.iter()
|
||||
.map(|font_id| self.font(*font_id).properties())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let font_id = {
|
||||
if let Ok(idx) = font_kit::matching::find_best_match(
|
||||
&candidates,
|
||||
&properties_to_font_kit(properties),
|
||||
) {
|
||||
self.font(family.font_ids[idx]).properties();
|
||||
family.font_ids[idx]
|
||||
} else {
|
||||
font_kit::matching::find_best_match(&candidates, &Default::default())
|
||||
.map(|idx| family.font_ids[idx])
|
||||
.unwrap_or(family.font_ids[0])
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure we've loaded fallback fonts for the selected font.
|
||||
if !self.fallback_fonts.contains_key(&font_id) {
|
||||
self.fallback_fonts.insert(
|
||||
font_id,
|
||||
Arc::new(self.cascade_list_for_languages(
|
||||
&self.font(font_id).native_font(),
|
||||
&["en"],
|
||||
)),
|
||||
);
|
||||
}
|
||||
*entry.insert(font_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn font(&self, font_id: FontId) -> Arc<Font> {
|
||||
self.rasterizer.font_for_id(font_id)
|
||||
}
|
||||
|
||||
pub fn native_font(&self, font_id: FontId, size: f32) -> NativeFont {
|
||||
match self.native_fonts.entry((font_id, OrderedFloat(size))) {
|
||||
Entry::Occupied(entry) => entry.get().clone(),
|
||||
Entry::Vacant(entry) => entry
|
||||
.insert(
|
||||
self.rasterizer
|
||||
.font_for_id(font_id)
|
||||
.native_font()
|
||||
.clone_with_font_size(size as f64),
|
||||
)
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the horizontal advance of a space character at the given size, or `None` if the
|
||||
/// advance could not be measured. Uses a cached reference advance (measured at
|
||||
/// `DEFAULT_FONT_SIZE`) scaled linearly to `size`.
|
||||
pub fn space_advance_width(&self, font_id: FontId, size: f32) -> Option<f64> {
|
||||
let stored = *self.space_advances.get(&font_id)?;
|
||||
stored.map(|a| a * size as f64 / DEFAULT_FONT_SIZE)
|
||||
}
|
||||
|
||||
pub fn font_id_for_native_font(&self, native_font: NativeFont) -> FontId {
|
||||
let postscript_name = native_font.postscript_name();
|
||||
if let Some(font_id) = self.fonts_by_name.get(&postscript_name).as_ref() {
|
||||
return *font_id.value();
|
||||
}
|
||||
|
||||
self.push_font(Font::from_ct_font(native_font))
|
||||
}
|
||||
|
||||
fn push_font(&self, font: Font) -> FontId {
|
||||
let name = Arc::new(font.postscript_name().unwrap());
|
||||
let font_id = FontId(self.next_font_id.fetch_add(1, Ordering::SeqCst));
|
||||
|
||||
let ct_font = font.native_font().clone_with_font_size(DEFAULT_FONT_SIZE);
|
||||
let advance = space_advance_width(&ct_font);
|
||||
|
||||
self.rasterizer.insert(font_id, Arc::new(font));
|
||||
self.font_names.insert(font_id, name.clone());
|
||||
self.fonts_by_name.insert(name, font_id);
|
||||
self.space_advances.insert(font_id, advance);
|
||||
font_id
|
||||
}
|
||||
|
||||
fn insert_font_family(&mut self, font_family: FontFamily) -> Result<FamilyId> {
|
||||
if let Some(family_id) = self.family_id_for_name(&font_family.name) {
|
||||
return Ok(family_id);
|
||||
}
|
||||
|
||||
let font_ids = font_family
|
||||
.fonts
|
||||
.into_iter()
|
||||
.map(|font| self.push_font(font));
|
||||
|
||||
let family_id = self.create_new_family_id();
|
||||
self.families.insert(
|
||||
family_id,
|
||||
Family {
|
||||
name: font_family.name,
|
||||
font_ids: font_ids.collect(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(family_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::platform::FontDB for FontDB {
|
||||
fn load_from_bytes(&mut self, name: &str, bytes: Vec<Vec<u8>>) -> Result<FamilyId> {
|
||||
let family = loader::load_font_family_from_bytes(name, bytes)?;
|
||||
self.insert_font_family(family)
|
||||
}
|
||||
|
||||
fn load_from_system(&mut self, font_family: &str) -> Result<FamilyId> {
|
||||
let family = loader::load_system_font(font_family)?;
|
||||
self.insert_font_family(family)
|
||||
}
|
||||
|
||||
fn load_all_system_fonts(&self) -> BoxFuture<'static, Box<dyn platform::LoadedSystemFonts>> {
|
||||
async { Box::new(loader::load_all_system_fonts()) as Box<dyn platform::LoadedSystemFonts> }
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn process_loaded_system_fonts(
|
||||
&mut self,
|
||||
loaded_system_fonts: Box<dyn platform::LoadedSystemFonts>,
|
||||
) -> Vec<(Option<FamilyId>, crate::fonts::FontInfo)> {
|
||||
let loaded_system_fonts: Box<LoadedSystemFonts> = loaded_system_fonts
|
||||
.as_any()
|
||||
.downcast()
|
||||
.expect("should not fail to downcast to concrete type");
|
||||
|
||||
loaded_system_fonts
|
||||
.0
|
||||
.into_iter()
|
||||
.flat_map(|(font_info, family)| {
|
||||
let family_id = self.insert_font_family(family).ok()?;
|
||||
Some((Some(family_id), font_info))
|
||||
})
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
fn fallback_fonts(&self, _ch: char, font_id: FontId) -> Vec<FontId> {
|
||||
self.fallback_fonts(font_id)
|
||||
}
|
||||
|
||||
fn load_family_name_from_id(&self, id: FamilyId) -> Option<String> {
|
||||
self.load_family_name_from_id(id)
|
||||
}
|
||||
|
||||
fn select_font(&self, family_id: FamilyId, properties: Properties) -> FontId {
|
||||
self.select_font(family_id, properties)
|
||||
}
|
||||
|
||||
fn font_metrics(&self, font_id: FontId) -> Metrics {
|
||||
match self.metrics.entry(font_id) {
|
||||
Entry::Occupied(entry) => *entry.get(),
|
||||
Entry::Vacant(entry) => *entry.insert(self.font(font_id).metrics().into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn glyph_advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Vector2I> {
|
||||
Ok(self.font(font_id).advance(glyph_id)?.to_i32())
|
||||
}
|
||||
|
||||
fn glyph_raster_bounds(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
point_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
scale: Vector2F,
|
||||
glyph_config: &rendering::GlyphConfig,
|
||||
) -> Result<RectI> {
|
||||
self.rasterizer
|
||||
.glyph_raster_bounds(font_id, point_size, glyph_id, scale, glyph_config)
|
||||
}
|
||||
|
||||
fn glyph_typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<RectI> {
|
||||
Ok(self.font(font_id).typographic_bounds(glyph_id)?.to_i32())
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
point_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
scale: Vector2F,
|
||||
subpixel_alignment: SubpixelAlignment,
|
||||
glyph_config: &rendering::GlyphConfig,
|
||||
format: RasterFormat,
|
||||
) -> Result<RasterizedGlyph> {
|
||||
self.rasterizer.rasterize_glyph(
|
||||
font_id,
|
||||
point_size,
|
||||
glyph_id,
|
||||
scale,
|
||||
subpixel_alignment,
|
||||
glyph_config,
|
||||
format,
|
||||
)
|
||||
}
|
||||
|
||||
fn glyph_for_char(&self, font: FontId, char: char) -> Option<GlyphId> {
|
||||
self.font(font).glyph_for_char(char)
|
||||
}
|
||||
|
||||
fn family_id_for_name(&self, name: &str) -> Option<FamilyId> {
|
||||
self.families
|
||||
.iter()
|
||||
.find(|(_, f)| f.name == name)
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
fn text_layout_system(&self) -> &dyn TextLayoutSystem {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::platform::TextLayoutSystem for FontDB {
|
||||
fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
line_style: LineStyle,
|
||||
style_runs: &[(Range<usize>, StyleAndFont)],
|
||||
_max_width: f32,
|
||||
clip_config: ClipConfig,
|
||||
) -> crate::text_layout::Line {
|
||||
layout_line(text, line_style, style_runs, self, clip_config)
|
||||
}
|
||||
|
||||
fn layout_text(
|
||||
&self,
|
||||
text: &str,
|
||||
line_style: LineStyle,
|
||||
style_runs: &[(Range<usize>, StyleAndFont)],
|
||||
max_width: f32,
|
||||
max_height: f32,
|
||||
alignment: TextAlignment,
|
||||
first_line_head_indent: Option<f32>,
|
||||
) -> TextFrame {
|
||||
layout_text(
|
||||
text,
|
||||
line_style,
|
||||
style_runs,
|
||||
self,
|
||||
max_width,
|
||||
max_height,
|
||||
alignment,
|
||||
first_line_head_indent,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user