From 9ca428c4e1a71cca9c07450826a604b2fa76974c Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sun, 22 Nov 2020 10:27:48 -0800 Subject: [PATCH] wezterm-font: remove font-loader dep on windows There are a number of cases where font-loader might panic on windows, and the optional font-loader dep causes problems with `cargo vendor` in #337, so this is a step to removing that dep. This commit makes direct GDI calls to enumerate monospace truetype fonts from the system and then applies our normal matching on the result. --- config/src/font.rs | 18 ++- wezterm-font/Cargo.toml | 8 +- .../src/locator/enum_font_families.rs | 133 ++++++++++++++++++ wezterm-font/src/locator/mod.rs | 13 +- 4 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 wezterm-font/src/locator/enum_font_families.rs diff --git a/config/src/font.rs b/config/src/font.rs index e1ac41cb3..a6892d9ad 100644 --- a/config/src/font.rs +++ b/config/src/font.rs @@ -225,6 +225,8 @@ impl_lua_conversion!(StyleRule); pub enum FontLocatorSelection { /// Use fontconfig APIs to resolve fonts (!macos, posix systems) FontConfig, + /// Use the EnumFontFamilies call on win32 systems + EnumFontFamilies, /// Use the fontloader crate to use a system specific method of /// resolving fonts FontLoader, @@ -238,10 +240,12 @@ lazy_static::lazy_static! { impl Default for FontLocatorSelection { fn default() -> Self { - if cfg!(all(unix, not(target_os = "macos"))) { - FontLocatorSelection::FontConfig - } else { + if cfg!(windows) { + FontLocatorSelection::EnumFontFamilies + } else if cfg!(target_os = "macos") { FontLocatorSelection::FontLoader + } else { + FontLocatorSelection::FontConfig } } } @@ -258,7 +262,12 @@ impl FontLocatorSelection { } pub fn variants() -> Vec<&'static str> { - vec!["FontConfig", "FontLoader", "ConfigDirsOnly"] + vec![ + "FontConfig", + "FontLoader", + "ConfigDirsOnly", + "EnumFontFamilies", + ] } } @@ -269,6 +278,7 @@ impl std::str::FromStr for FontLocatorSelection { "fontconfig" => Ok(Self::FontConfig), "fontloader" => Ok(Self::FontLoader), "configdirsonly" => Ok(Self::ConfigDirsOnly), + "enumfontfamilies" => Ok(Self::EnumFontFamilies), _ => Err(anyhow!( "{} is not a valid FontLocatorSelection variant, possible values are {:?}", s, diff --git a/wezterm-font/Cargo.toml b/wezterm-font/Cargo.toml index 3d0d8862f..80c09ac9e 100644 --- a/wezterm-font/Cargo.toml +++ b/wezterm-font/Cargo.toml @@ -29,11 +29,6 @@ window = { path = "../window" } [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies] fontconfig = { path = "../deps/fontconfig" } -# on linux, font-loader pulls in servo-font* crates which conflict with -# our newer font related deps, so we avoid it on linux -[target.'cfg(any(windows, target_os = "macos"))'.dependencies] -font-loader = { version = "0.8" } - [target."cfg(windows)".dependencies] dwrote = "0.9" winapi = "0.3" @@ -41,3 +36,6 @@ winapi = "0.3" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.7" core-text = "15.0" +# on linux, font-loader pulls in servo-font* crates which conflict with +# our newer font related deps, so we avoid it on linux +font-loader = { version = "0.8" } diff --git a/wezterm-font/src/locator/enum_font_families.rs b/wezterm-font/src/locator/enum_font_families.rs new file mode 100644 index 000000000..57cfe06e3 --- /dev/null +++ b/wezterm-font/src/locator/enum_font_families.rs @@ -0,0 +1,133 @@ +#![cfg(windows)] + +use crate::locator::{FontDataHandle, FontLocator}; +use config::FontAttributes; +use std::collections::HashSet; +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; +use winapi::ctypes::c_int; +use winapi::shared::minwindef::{DWORD, LPARAM}; +use winapi::um::wingdi::{ + CreateCompatibleDC, CreateFontIndirectW, DeleteDC, EnumFontFamiliesExW, GetFontData, + SelectObject, ENUMLOGFONTEXW, FIXED_PITCH, GDI_ERROR, LF_FULLFACESIZE, LOGFONTW, + OUT_TT_ONLY_PRECIS, TEXTMETRICW, TRUETYPE_FONTTYPE, +}; + +/// A FontLocator implemented using the system font loading +/// functions provided by the font-loader crate. +pub struct EnumFontFamiliesFontLocator {} + +struct Entry { + log_font: ENUMLOGFONTEXW, + name: String, +} + +impl Entry { + fn locator(&self) -> anyhow::Result { + unsafe { + let hdc = CreateCompatibleDC(std::ptr::null_mut()); + let font = CreateFontIndirectW(&self.log_font.elfLogFont); + SelectObject(hdc, font as *mut _); + let size = GetFontData(hdc, 0, 0, std::ptr::null_mut(), 0); + let result = match size { + _ if size > 0 && size != GDI_ERROR => { + let mut data = vec![0u8; size as usize]; + GetFontData(hdc, 0, 0, data.as_mut_ptr() as *mut _, size); + Ok(FontDataHandle::Memory { + data, + index: 0, + name: self.name.clone(), + }) + } + _ => Err(anyhow::anyhow!("Failed to get font data")), + }; + DeleteDC(hdc); + result + } + } +} + +#[allow(non_snake_case)] +unsafe extern "system" fn callback( + lpelfe: *const LOGFONTW, + _: *const TEXTMETRICW, + fonttype: DWORD, + lparam: LPARAM, +) -> c_int { + let log_font: &ENUMLOGFONTEXW = &*(lpelfe as *const ENUMLOGFONTEXW); + if fonttype == TRUETYPE_FONTTYPE && log_font.elfFullName[0] != b'@' as u16 { + let fonts = lparam as *mut Vec; + + let len = log_font + .elfFullName + .iter() + .position(|&c| c == 0) + .unwrap_or(LF_FULLFACESIZE); + if let Ok(name) = OsString::from_wide(&log_font.elfFullName[0..len]).into_string() { + (*fonts).push(Entry { + log_font: *log_font, + name, + }); + } + } + 1 // continue enumeration +} + +impl FontLocator for EnumFontFamiliesFontLocator { + fn load_fonts( + &self, + fonts_selection: &[FontAttributes], + loaded: &mut HashSet, + ) -> anyhow::Result> { + let mut log_font = LOGFONTW { + lfHeight: 0, + lfWidth: 0, + lfEscapement: 0, + lfOrientation: 0, + lfWeight: 0, + lfItalic: 0, + lfUnderline: 0, + lfStrikeOut: 0, + lfCharSet: 0, + lfOutPrecision: OUT_TT_ONLY_PRECIS as u8, + lfClipPrecision: 0, + lfQuality: 0, + lfPitchAndFamily: FIXED_PITCH as u8, + lfFaceName: [0u16; 32], + }; + + let mut sys_fonts: Vec = vec![]; + unsafe { + let hdc = CreateCompatibleDC(std::ptr::null_mut()); + EnumFontFamiliesExW( + hdc, + &mut log_font, + Some(callback), + &mut sys_fonts as *mut _ as LPARAM, + 0, + ); + DeleteDC(hdc); + } + + let mut handles = vec![]; + for font in sys_fonts { + if let Ok(handle) = font.locator() { + if let Ok(parsed) = crate::parser::ParsedFont::from_locator(&handle) { + handles.push((handle, parsed)); + } + } + } + + let mut fonts = Vec::new(); + for font_attr in fonts_selection { + for (handle, parsed) in &handles { + if crate::parser::font_info_matches(font_attr, parsed.names()) { + fonts.push(handle.clone()); + loaded.insert(font_attr.clone()); + } + } + } + + Ok(fonts) + } +} diff --git a/wezterm-font/src/locator/mod.rs b/wezterm-font/src/locator/mod.rs index 7b8320577..54d34ad65 100644 --- a/wezterm-font/src/locator/mod.rs +++ b/wezterm-font/src/locator/mod.rs @@ -2,9 +2,10 @@ use config::FontAttributes; use std::collections::HashSet; use std::path::PathBuf; +pub mod enum_font_families; #[cfg(all(unix, not(target_os = "macos")))] pub mod font_config; -#[cfg(any(target_os = "macos", windows))] +#[cfg(target_os = "macos")] pub mod font_loader; /// Represents the data behind a font. @@ -64,11 +65,17 @@ pub fn new_locator(locator: FontLocatorSelection) -> Box { panic!("fontconfig not compiled in"); } FontLocatorSelection::FontLoader => { - #[cfg(any(target_os = "macos", windows))] + #[cfg(target_os = "macos")] return Box::new(font_loader::FontLoaderFontLocator {}); - #[cfg(not(any(target_os = "macos", windows)))] + #[cfg(not(target_os = "macos"))] panic!("fontloader not compiled in"); } + FontLocatorSelection::EnumFontFamilies => { + #[cfg(windows)] + return Box::new(enum_font_families::EnumFontFamiliesFontLocator {}); + #[cfg(not(windows))] + panic!("EnumFontFamilies not compiled in"); + } FontLocatorSelection::ConfigDirsOnly => Box::new(NopSystemSource {}), } }