use std::{cell::RefCell, path::PathBuf};
mod dwrite;
mod source;
mod utils;
mod windows;
use dwrite::*;
use source::*;
use utils::*;
use windows::*;
use crate::{Fallback, Stretch, Style, Weight, XFontType};
pub struct XFont(Option<XFontInner>);
struct XFontInner {
factory: ComPtr<IDWriteFactory2>,
fallback: ComPtr<IDWriteFontFallback>,
collection: ComPtr<IDWriteFontCollection>,
}
impl Default for XFont {
fn default() -> Self {
unsafe {
let factory = DWriteFactory();
if factory.is_null() {
return Self(None);
}
let factory = &mut *factory;
let mut factroy2 = ptr::null_mut();
if S_OK != factory.QueryInterface(&IDWriteFactory2::uuidof(), &mut factroy2) {
return Self(None);
}
let Some(factory) = ComPtr::try_from_raw(factroy2 as *mut IDWriteFactory2) else {
return Self(None);
};
let mut collection: *mut IDWriteFontCollection = ptr::null_mut();
if S_OK != (*DWriteFactory()).GetSystemFontCollection(&mut collection, FALSE) {
return Self(None);
}
let Some(collection) = ComPtr::try_from_raw(collection) else {
return Self(None);
};
let mut fallback: *mut IDWriteFontFallback = ptr::null_mut();
if S_OK != factory.GetSystemFontFallback(&mut fallback) {
return Self(None);
}
let Some(fallback) = ComPtr::try_from_raw(fallback) else {
return Self(None);
};
Self(Some(XFontInner {
factory,
fallback,
collection,
}))
}
}
}
impl XFontType for XFont {
fn fallback(&self, text: &str, name: &str, weight: Weight, stretch: Stretch, style: Style) -> Fallback {
self.fallback_impl(text, &name, weight, stretch, style).unwrap_or_default()
}
fn system() -> String {
let mut metrics = NONCLIENTMETRICSW {
cbSize: std::mem::size_of::<NONCLIENTMETRICSW>() as _,
..Default::default()
};
unsafe {
if TRUE
== SystemParametersInfoW(
SPI_GETNONCLIENTMETRICS, size_of_val(&metrics) as _,
&mut metrics as *mut _ as _,
0,
) {
let wide_name = &metrics.lfMessageFont.lfFaceName;
let utf16_iter = wide_name.iter().take_while(|&&c| c != 0).map(|&c| c as u16);
return String::from_utf16_lossy(&utf16_iter.collect::<Vec<u16>>());
}
}
"Segoe UI".to_string()
}
}
impl XFont {
fn fallback_impl(&self, text: &str, name: &str, weight: Weight, stretch: Stretch, style: Style) -> Option<Fallback> {
let Some(inner) = &self.0 else {
return None;
};
thread_local! {
static MAP_BUF:RefCell<(Vec<u16>,Vec<u16>)> = RefCell::new((vec![],vec![]));
}
MAP_BUF.with_borrow_mut(move |(text_buf, name_buf)| {
unsafe {
name_buf.clear();
name_buf.extend(name.encode_utf16());
name_buf.push(0);
if text.is_empty() {
let mut index = 0;
let mut exists: BOOL = 0;
let hr = inner.collection.FindFamilyName(name_buf.as_mut_ptr(), &mut index, &mut exists);
if hr == S_OK && exists == TRUE {
let mut family: *mut IDWriteFontFamily = ptr::null_mut();
if S_OK == inner.collection.GetFontFamily(index, &mut family) {
let family = ComPtr::from_raw(family);
let mut out_font: *mut IDWriteFont = ptr::null_mut();
if S_OK
== family.GetFirstMatchingFont(conv_weight(weight), conv_stretch(stretch), conv_style(style), &mut out_font)
{
let mut font = ComPtr::from_raw(out_font);
return Some(build_fallback(&mut font));
}
}
}
}
text_buf.clear();
text_buf.extend(text.encode_utf16());
let text_len = text_buf.len();
text_buf.push(0);
let (text, locale) = text_buf.split_at_mut(text_len);
let mut source = TextSource::new(text, locale, false);
let mut source = &mut source;
let source = &mut source;
let source = source as *mut &mut TextSource<'_> as *mut c_void as *mut IDWriteTextAnalysisSource;
let mut out_len = 0;
let mut out_font: *mut IDWriteFont = ptr::null_mut();
let mut out_scale = 1.0;
if S_OK
== inner.fallback.MapCharacters(
source, 0, text_len as _,
inner.collection.as_raw(),
if name_buf.is_empty() {
ptr::null_mut()
} else {
name_buf.as_mut_ptr()
},
conv_weight(weight), conv_style(style),
conv_stretch(stretch),
&mut out_len,
&mut out_font,
&mut out_scale,
) {
if let Some(mut font) = ComPtr::try_from_raw(out_font) {
return Some(build_fallback(&mut font));
}
}
}
None
})
}
}
unsafe fn build_fallback(font: &mut ComPtr<IDWriteFont>) -> Fallback {
let Some((index, path)) = font_file(font) else {
return Default::default();
};
Fallback {
index: Some(index),
unique: None,
file: path,
}
}
fn conv_weight(weight: Weight) -> UINT32 {
match weight {
Weight::THIN => DWRITE_FONT_WEIGHT_THIN,
Weight::EXTRA_LIGHT => DWRITE_FONT_WEIGHT_EXTRA_LIGHT,
Weight::LIGHT => DWRITE_FONT_WEIGHT_LIGHT,
Weight::NORMAL => DWRITE_FONT_WEIGHT_NORMAL,
Weight::MEDIUM => DWRITE_FONT_WEIGHT_MEDIUM,
Weight::SEMIBOLD => DWRITE_FONT_WEIGHT_DEMI_BOLD,
Weight::BOLD => DWRITE_FONT_WEIGHT_BOLD,
Weight::EXTRA_BOLD => DWRITE_FONT_WEIGHT_EXTRA_BOLD,
Weight::BLACK => DWRITE_FONT_WEIGHT_BLACK,
_ => DWRITE_FONT_WEIGHT_REGULAR,
}
}
fn conv_stretch(stretch: Stretch) -> UINT32 {
match stretch {
Stretch::ULTRA_CONDENSED => DWRITE_FONT_STRETCH_ULTRA_CONDENSED, Stretch::EXTRA_CONDENSED => DWRITE_FONT_STRETCH_EXTRA_CONDENSED, Stretch::CONDENSED => DWRITE_FONT_STRETCH_CONDENSED, Stretch::SEMI_CONDENSED => DWRITE_FONT_STRETCH_SEMI_CONDENSED, Stretch::NORMAL => DWRITE_FONT_STRETCH_NORMAL, Stretch::SEMI_EXPANDED => DWRITE_FONT_STRETCH_SEMI_EXPANDED, Stretch::EXPANDED => DWRITE_FONT_STRETCH_EXPANDED, Stretch::EXTRA_EXPANDED => DWRITE_FONT_STRETCH_EXTRA_EXPANDED, Stretch::ULTRA_EXPANDED => DWRITE_FONT_STRETCH_ULTRA_EXPANDED, _ => DWRITE_FONT_STRETCH_NORMAL,
}
}
fn conv_style(style: Style) -> UINT32 {
match style {
Style::Normal => DWRITE_FONT_STYLE_NORMAL,
Style::Italic => DWRITE_FONT_STYLE_ITALIC,
Style::Oblique => DWRITE_FONT_STYLE_OBLIQUE,
}
}