use alloc::string::String;
use alloc::sync::Arc;
use alloc::{vec, vec::Vec};
use hashbrown::HashMap;
use std::{
ffi::{OsString, c_void},
os::windows::ffi::OsStringExt,
path::PathBuf,
};
use windows::Win32::Foundation::STATUS_NOT_FOUND;
use windows::{
Win32::Graphics::DirectWrite::{
DWRITE_FACTORY_TYPE_SHARED, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_WEIGHT_REGULAR, DWRITE_READING_DIRECTION,
DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, DWriteCreateFactory, IDWriteFactory,
IDWriteFactory2, IDWriteFont, IDWriteFontCollection, IDWriteFontFace, IDWriteFontFallback,
IDWriteFontFamily, IDWriteFontFile, IDWriteLocalFontFileLoader, IDWriteNumberSubstitution,
IDWriteTextAnalysisSource, IDWriteTextAnalysisSource_Impl,
},
core::{Interface, OutRef, PCWSTR, implement},
};
use super::{
FallbackKey, FamilyId, FamilyInfo, FamilyNameMap, FontInfo, GenericFamily, GenericFamilyMap,
ScriptExt, SourcePathMap,
};
const DEFAULT_GENERIC_FAMILIES: &[(GenericFamily, &[&str])] = &[
(GenericFamily::Serif, &["Times New Roman"]),
(GenericFamily::SansSerif, &["Arial"]),
(GenericFamily::Monospace, &["Consolas"]),
(GenericFamily::Cursive, &["Comic Sans MS"]),
(GenericFamily::Fantasy, &["Impact"]),
(GenericFamily::SystemUi, &["Segoe UI"]),
(GenericFamily::Emoji, &["Segoe UI Emoji"]),
(GenericFamily::Math, &["Cambria Math"]),
(GenericFamily::FangSong, &["FangSong"]),
];
pub(crate) struct SystemFonts {
pub(crate) name_map: Arc<FamilyNameMap>,
pub(crate) generic_families: Arc<GenericFamilyMap>,
source_cache: SourcePathMap,
family_map: HashMap<FamilyId, Option<FamilyInfo>>,
dwrite_fonts: DWriteSystemFonts,
}
impl SystemFonts {
pub(crate) fn new() -> Self {
let dwrite_fonts = DWriteSystemFonts::new(false).unwrap();
let mut name_map = FamilyNameMap::default();
for family in dwrite_fonts.families() {
if let Some(names) = family.names() {
let [first_name, other_names @ ..] = names.as_slice() else {
continue;
};
let id = name_map.get_or_insert(first_name).id();
for other_name in other_names {
name_map.add_alias(id, other_name);
}
}
}
let mut generic_families = GenericFamilyMap::default();
for (family, names) in DEFAULT_GENERIC_FAMILIES {
generic_families.set(
*family,
names
.iter()
.filter_map(|name| name_map.get(name))
.map(|name| name.id()),
);
}
Self {
name_map: Arc::new(name_map),
generic_families: Arc::new(generic_families),
source_cache: SourcePathMap::default(),
family_map: HashMap::default(),
dwrite_fonts,
}
}
pub(crate) fn family(&mut self, id: FamilyId) -> Option<FamilyInfo> {
match self.family_map.get(&id) {
Some(Some(family)) => return Some(family.clone()),
Some(None) => return None,
_ => {}
}
let name = self.name_map.get_by_id(id)?;
let mut fonts = smallvec::SmallVec::<[FontInfo; 4]>::default();
if let Some(family) = self.dwrite_fonts.family_by_name(name.name()) {
for font in family.fonts() {
if let Some(font) = FontInfo::from_dwrite(&font, &mut self.source_cache) {
if !fonts
.iter()
.any(|f| f.source().id() == font.source().id() && f.index() == font.index())
{
fonts.push(font);
}
}
}
if !fonts.is_empty() {
let family = FamilyInfo::new(name.clone(), fonts);
self.family_map.insert(id, Some(family.clone()));
return Some(family);
}
}
self.family_map.insert(id, None);
None
}
pub(crate) fn fallback(&mut self, key: impl Into<FallbackKey>) -> Option<FamilyId> {
let key = key.into();
let text = key.script().sample()?;
let locale = key.locale_str();
let family_name = self.dwrite_fonts.family_name_for_text(text, locale)?;
self.name_map.get(&family_name).map(|name| name.id())
}
}
impl FontInfo {
fn from_dwrite(font: &DWriteFont, paths: &mut SourcePathMap) -> Option<Self> {
let path = font.file_path()?;
let index = font.index();
let data = paths.get_or_insert(&path);
Self::from_source(data, index)
}
}
struct DWriteSystemFonts {
collection: IDWriteFontCollection,
fallback: Option<IDWriteFontFallback>,
map_buf: Vec<u16>,
}
impl DWriteSystemFonts {
fn new(update: bool) -> windows_core::Result<Self> {
unsafe {
let factory = DWriteCreateFactory::<IDWriteFactory>(DWRITE_FACTORY_TYPE_SHARED)?;
let mut collection: Option<IDWriteFontCollection> = None;
factory.GetSystemFontCollection(&mut collection, update)?;
let collection = collection.ok_or_else(|| {
windows_core::Error::new(STATUS_NOT_FOUND.to_hresult(), "no collection")
})?;
let fallback = factory
.cast::<IDWriteFactory2>()
.and_then(|f| f.GetSystemFontFallback())
.ok();
Ok(Self {
collection,
fallback,
map_buf: vec![],
})
}
}
fn get(&self, index: u32) -> Option<DWriteFontFamily> {
unsafe {
self.collection
.GetFontFamily(index)
.ok()
.map(DWriteFontFamily)
}
}
fn family_by_name(&self, name: &str) -> Option<DWriteFontFamily> {
let mut index = 0;
#[expect(
clippy::default_trait_access,
reason = "The type this resolves to doesn't seem to be nameable."
)]
let mut exists = Default::default();
let mut name_buf = smallvec::SmallVec::<[u16; 128]>::default();
name_buf.extend(name.encode_utf16());
name_buf.push(0);
unsafe {
self.collection
.FindFamilyName(PCWSTR::from_raw(name_buf.as_ptr()), &mut index, &mut exists)
.ok()?;
}
self.get(index)
}
fn families(&self) -> impl Iterator<Item = DWriteFontFamily> {
let this = self.collection.clone();
unsafe {
let count = this.GetFontFamilyCount();
(0..count).filter_map(move |index| this.GetFontFamily(index).ok().map(DWriteFontFamily))
}
}
fn family_name_for_text(&mut self, text: &str, locale: Option<&str>) -> Option<String> {
self.map_buf.clear();
self.map_buf.extend(text.encode_utf16());
let text_len = self.map_buf.len();
if let Some(locale) = locale {
self.map_buf.extend(locale.encode_utf16());
}
self.map_buf.push(0);
let (text, locale) = self.map_buf.split_at(text_len);
let source: IDWriteTextAnalysisSource = TextSource { text, locale }.into();
unsafe {
let mut cur_offset = 0;
while cur_offset < text_len {
let mut mapped_len = 0;
let mut mapped_font = None;
if let Some(fallback) = self.fallback.as_ref() {
if fallback
.MapCharacters(
&source,
cur_offset as u32,
(text_len - cur_offset) as u32,
&self.collection,
None,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
&mut mapped_len,
&mut mapped_font,
&mut 1.0,
)
.is_ok()
{
if let Some(font) = mapped_font {
let family = font.GetFontFamily().ok()?;
let names = family.GetFamilyNames().ok()?;
let name_len = names.GetStringLength(0).ok()? as usize;
let mut name_buf = smallvec::SmallVec::<[u16; 128]>::default();
name_buf.resize(name_len + 1, 0);
names.GetString(0, &mut name_buf).ok()?;
name_buf.pop();
return Some(String::from_utf16_lossy(&name_buf));
}
}
}
cur_offset += 1;
}
}
None
}
}
#[derive(Clone)]
struct DWriteFontFamily(IDWriteFontFamily);
impl DWriteFontFamily {
fn names(&self) -> Option<Vec<String>> {
let mut names = vec![];
unsafe {
let family_names = self.0.GetFamilyNames().ok()?;
let count = family_names.GetCount();
let mut buf = vec![];
for i in 0..count {
let Ok(len) = family_names.GetStringLength(i) else {
continue;
};
buf.clear();
buf.resize(len as usize + 1, 0);
if family_names.GetString(i, &mut buf).is_err() {
continue;
}
buf.pop();
names.push(String::from_utf16_lossy(&buf));
}
}
Some(names)
}
fn fonts(&self) -> impl Iterator<Item = DWriteFont> {
let this = self.0.clone();
unsafe {
let count = self.0.GetFontCount();
(0..count).filter_map(move |index| {
let font = this.GetFont(index).ok()?;
if font.GetSimulations().0 != 0 {
return None;
}
DWriteFont::new(&font)
})
}
}
}
#[derive(Clone)]
struct DWriteFont(IDWriteFontFace);
impl DWriteFont {
fn new(font: &IDWriteFont) -> Option<Self> {
unsafe { Some(Self(font.CreateFontFace().ok()?)) }
}
fn file_path(&self) -> Option<PathBuf> {
unsafe {
let mut file: Option<IDWriteFontFile> = None;
self.0.GetFiles(&mut 1, Some(&mut file)).ok()?;
let file = file?;
let mut ref_key: *mut c_void = core::ptr::null_mut();
let mut ref_key_size: u32 = 0;
file.GetReferenceKey(&mut ref_key, &mut ref_key_size).ok()?;
let loader = file.GetLoader().ok()?;
let local_loader: IDWriteLocalFontFileLoader = loader.cast().ok()?;
let file_path_len = local_loader
.GetFilePathLengthFromKey(ref_key, ref_key_size)
.ok()?;
let mut file_path_buf = vec![0; file_path_len as usize + 1];
local_loader
.GetFilePathFromKey(ref_key, ref_key_size, &mut file_path_buf)
.ok()?;
if let Some(&0) = file_path_buf.last() {
file_path_buf.pop();
}
Some(PathBuf::from(OsString::from_wide(&file_path_buf)))
}
}
fn index(&self) -> u32 {
unsafe { self.0.GetIndex() }
}
}
#[implement(IDWriteTextAnalysisSource)]
struct TextSource<'a> {
text: &'a [u16],
locale: &'a [u16],
}
impl IDWriteTextAnalysisSource_Impl for TextSource_Impl<'_> {
fn GetLocaleName(
&self,
textposition: u32,
textlength: *mut u32,
localename: *mut *mut u16,
) -> windows::core::Result<()> {
unsafe {
*textlength = (self.text.len() as u32).saturating_sub(textposition);
*localename = core::mem::transmute::<*const u16, *mut u16>(self.locale.as_ptr());
}
Ok(())
}
fn GetNumberSubstitution(
&self,
textposition: u32,
textlength: *mut u32,
numbersubstitution: OutRef<'_, IDWriteNumberSubstitution>,
) -> windows::core::Result<()> {
numbersubstitution.write(None)?;
unsafe {
*textlength = (self.text.len() as u32).saturating_sub(textposition);
}
Ok(())
}
fn GetParagraphReadingDirection(&self) -> DWRITE_READING_DIRECTION {
DWRITE_READING_DIRECTION_LEFT_TO_RIGHT
}
fn GetTextAtPosition(
&self,
textposition: u32,
textstring: *mut *mut u16,
textlength: *mut u32,
) -> windows::core::Result<()> {
unsafe {
let text = self.text.get(textposition as usize..).unwrap_or_default();
*textlength = text.len() as _;
*textstring = core::mem::transmute::<*const u16, *mut u16>(text.as_ptr());
}
Ok(())
}
fn GetTextBeforePosition(
&self,
textposition: u32,
textstring: *mut *mut u16,
textlength: *mut u32,
) -> windows::core::Result<()> {
unsafe {
let text = self.text.get(..textposition as usize).unwrap_or_default();
*textlength = text.len() as _;
*textstring = core::mem::transmute::<*const u16, *mut u16>(text.as_ptr());
}
Ok(())
}
}