Rebrand to Galaxy, major improvements to Bedrock support, still needs some TLC though

This commit is contained in:
Ryan Ward
2026-05-07 11:29:34 -05:00
parent f4e2475c60
commit a41cbd8cc7
2433 changed files with 14208 additions and 9409 deletions
@@ -0,0 +1,266 @@
use super::{
font_handle::FontHandle, FontFamily, LoadedSystemFonts, TextLayoutSystem,
ValidateFontSupportsEn,
};
use crate::fonts::FontId;
use anyhow::Result;
use font_kit::loader::Loader as _;
use font_kit::{
family_name::FamilyName as FKFamilyName, properties::Properties as FKProperties,
properties::Style as FKStyle, properties::Weight as FKWeight, source::SystemSource as FKSource,
};
use itertools::Itertools;
use owned_ttf_parser::OwnedFace;
use std::collections::HashMap;
use std::sync::Arc;
const EN_US_LOCALE: &str = "en-US";
/// Windows symbol fonts that are used to render window control icons. We specifically do not do any
/// validation of these fonts (i.e. to check if the font contains english characters).
const SYMBOL_ICON_FONTS: &[&str] = &["Segoe Fluent Icons", "Segoe MDL2 Assets"];
pub(crate) mod loader {
use crate::fonts::FontInfo;
use super::*;
pub fn load_all_system_fonts() -> LoadedSystemFonts {
let source = font_kit::source::SystemSource::new();
let fonts = match source.all_fonts() {
Ok(fonts) => fonts,
Err(err) => {
log::warn!("unable to retrieve all fonts from DirectWrite source: {err:?}");
return LoadedSystemFonts(vec![]);
}
};
let mut family_map = HashMap::new();
for font_handle in fonts.into_iter() {
if let Ok(font) = font_handle.load() {
let family_name = font.family_name();
let is_monospace = font.is_monospace();
if font.glyph_for_char('m').is_none() {
// Only allow the user to select fonts that have an English character set.
log::debug!("skipping family {family_name:?} because no 'm' glyph was found");
continue;
}
// Convert font_kit::Handle into UI framework-specific FontHandle.
let font_handle = match font_handle {
font_kit::handle::Handle::Path { path, font_index } => {
FontHandle::new(path, font_index, is_monospace)
}
font_kit::handle::Handle::Memory { bytes, font_index } => {
let owned_face_result = match Arc::try_unwrap(bytes) {
// If we can ensure ownership of the bytes, create an OwnedFace without copying.
Ok(owned_bytes) => OwnedFace::from_vec(owned_bytes, font_index),
// If we can't get sole ownership, create on OwnedFace from a copy the bytes
// (created by .to_vec()).
Err(shared_bytes) => {
OwnedFace::from_vec(shared_bytes.to_vec(), font_index)
}
};
match owned_face_result {
Ok(typeface) => FontHandle::from(typeface),
Err(err) => {
// If we can't parse the typeface, skip it.
log::warn!(
"unable to parse typeface from family {family_name}: {err:?}"
);
continue;
}
}
}
};
let (entry_info, entry_family) = family_map
.entry(family_name.clone())
.or_insert_with(move || {
(
FontInfo {
family_name: family_name.clone(),
is_monospace,
},
FontFamily {
name: family_name,
fonts: vec![],
},
)
});
entry_info.is_monospace |= is_monospace;
entry_family.fonts.push(font_handle);
}
}
LoadedSystemFonts(family_map.into_values().collect_vec())
}
pub fn load_system_font(font_family: &str) -> Result<FontFamily> {
let source = font_kit::source::SystemSource::new();
let family = source.select_family_by_name(font_family)?;
let validate_supports_en = if SYMBOL_ICON_FONTS.contains(&font_family) {
ValidateFontSupportsEn::No
} else {
ValidateFontSupportsEn::Yes
};
Ok(FontFamily {
name: font_family.to_string(),
fonts: family
.fonts()
.iter()
.flat_map(|font_kit_handle| {
load_font_from_handle(font_kit_handle, validate_supports_en)
})
.collect_vec(),
})
}
}
impl TextLayoutSystem {
/// Given a specific character and FontID, find alternate system fonts that can
/// render that character.
pub fn get_fallback_fonts_for_character(
&self,
character: char,
font_id: FontId,
) -> Result<Vec<FontId>> {
// Retrieve the font's family name and properties from the font store.
// First, find the font's fontdb ID.
let &original_font_id =
self.font_id_map
.read()
.get_by_left(&font_id)
.ok_or(anyhow::format_err!(
"No left entry found for {font_id:?} in font_id_map"
))?;
let (style, weight, family_name) = self.get_font_info_from_store(original_font_id)?;
let source = FKSource::new();
let style = match style {
fontdb::Style::Normal => FKStyle::Normal,
fontdb::Style::Italic => FKStyle::Italic,
fontdb::Style::Oblique => FKStyle::Oblique,
};
let weight = FKWeight(weight.0 as f32);
let properties = FKProperties {
style,
weight,
stretch: Default::default(),
};
let font_handle = source
.select_best_match(
&[
FKFamilyName::Title(family_name.to_owned()),
FKFamilyName::Monospace,
],
&properties,
)
.map_err(|err| anyhow::anyhow!("Didn't find {family_name} in fontdb: {err}"))?;
// Load fallback fonts for the requested character.
let loaded_font = font_handle.load().map_err(|err| {
anyhow::anyhow!("Unable to load typeface from font_kit Handle: {err:?}")
})?;
let fallback_result =
loaded_font.get_fallbacks(character.to_string().as_str(), EN_US_LOCALE);
// Convert each font-kit fallback `Font` into a UI framework `FontHandle` and load it into
// fontdb. We deliberately avoid `font_kit::Font::handle()` here: its default impl reads
// the full font file into an `Arc<Vec<u8>>` and returns a `Handle::Memory` with
// `font_index` hard-coded to `0` (see the FIXME at font-kit/src/loader.rs:172), which
// bypasses `TextLayoutSystem::insert_font`'s path-based dedup and loses TTC face indices.
// Instead we reach through `NativeFont` to the underlying `IDWriteFontFace` and recover
// the on-disk file path + real face index, the same way
// `DirectWriteSource::create_handle_from_dwrite_font` does for enumerated system fonts.
// This lets fontdb mmap the file lazily and lets `insert_font` dedup by `(path, index)`,
// so the same fallback family is loaded at most once per process.
let fallback_font_vec = fallback_result
.fonts
.into_iter()
.flat_map(|fallback_font| {
let loaded_handle =
fallback_font_path_handle(&fallback_font.font).or_else(|| {
// Last-resort fallback for fonts that aren't backed by a local file (e.g.
// custom collection loaders). These don't appear in practice for DirectWrite
// system fallbacks, but preserve the original byte-copy behavior so we
// degrade gracefully instead of dropping the glyph.
let handle = fallback_font.font.handle()?;
load_font_from_handle(&handle, ValidateFontSupportsEn::No).ok()
})?;
self.insert_font(loaded_handle).ok()
})
.collect_vec();
Ok(fallback_font_vec)
}
/// Critical section for fetching the font style, weight and family name from fontdb.
/// This function performs the minimum work required to fetch this information from
/// fontdb to minimize the amount of time spent holding a read lock on the font store.
fn get_font_info_from_store(
&self,
font_id: fontdb::ID,
) -> Result<(fontdb::Style, fontdb::Weight, String)> {
let store_read_lock = self.font_store.read();
let db_read = store_read_lock.db();
let face = db_read.face(font_id).ok_or(anyhow::anyhow!(
"Unable to retrieve font face from fontdb font_store"
))?;
let style = face.style;
let weight = face.weight;
let Some(en_us_family_info) = face.families.first() else {
return Err(anyhow::anyhow!("Font face doesn't have any family names"));
};
let (family_name, _) = en_us_family_info;
// Clone the family name because it's protected by the font store's RWLock.
Ok((style, weight, family_name.to_owned()))
}
}
fn load_font_from_handle(
font_handle: &font_kit::handle::Handle,
validate_supports_en_charset: ValidateFontSupportsEn,
) -> Result<FontHandle> {
let font = font_handle.load()?;
let is_monospace = font.is_monospace();
if matches!(validate_supports_en_charset, ValidateFontSupportsEn::Yes) {
font.glyph_for_char('m').ok_or(anyhow::format_err!(
"No 'm' glyph found for font {}",
font.full_name()
))?;
}
match font_handle {
font_kit::handle::Handle::Path { path, font_index } => {
Ok(FontHandle::new(path, *font_index, is_monospace))
}
font_kit::handle::Handle::Memory { bytes, font_index } => {
let typeface = OwnedFace::from_vec(bytes.to_vec(), *font_index)?;
Ok(FontHandle::from(typeface))
}
}
}
/// Builds a path-backed [`FontHandle`] for a font-kit DirectWrite `Font` by reaching through
/// [`font_kit::loaders::directwrite::NativeFont`] to the underlying `IDWriteFontFace`.
///
/// This mirrors what font-kit itself does for enumerated system fonts in
/// `DirectWriteSource::create_handle_from_dwrite_font` (font-kit/src/sources/directwrite.rs:103),
/// and is the reason we carry `dwrote` as a direct dependency: font-kit's generic
/// `Loader::handle()` default returns a `Handle::Memory` with a byte copy of the full file, which
/// we specifically need to avoid on the per-character fallback path.
///
/// Returns `None` when DirectWrite cannot produce a local file path for the font, i.e. the font
/// was loaded via a custom collection loader or backed only by an in-memory stream. For system
/// fallback fonts returned by `IDWriteFontFallback::MapCharacters` against the system font
/// collection, a path is always available.
fn fallback_font_path_handle(font: &font_kit::loaders::directwrite::Font) -> Option<FontHandle> {
let native = font.native_font();
let file = native.dwrite_font_face.files().ok()?.into_iter().next()?;
let path = file.font_file_path().ok()?;
let font_index = native.dwrite_font_face.get_index();
Some(FontHandle::new(path, font_index, font.is_monospace()))
}