diff --git a/Cargo.lock b/Cargo.lock index fc56480..3fa88ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1362,6 +1362,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "fontconfig" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b19c4bca8c705ea23bfb3e3403a9e699344d1ee3205b631f03fe4dbf1e52429f" +dependencies = [ + "yeslogic-fontconfig-sys", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -2898,6 +2907,7 @@ dependencies = [ "egui", "embed-resource", "env_logger", + "fontconfig", "indicatif", "log", "rand", @@ -5587,6 +5597,17 @@ version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 98019cd..a0f34c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,13 @@ tokio = { version = "1", features = ["macros", "net", "rt-multi-thread"] } webbrowser = { version = "1.0.4", optional = true } zip = { version = "7.2.0", features = ["deflate-flate2"], default-features = false } +[target.'cfg(target_os = "linux")'.dependencies] +fontconfig = { version = "0.10.0", optional = true } + [features] default = ["gui"] -gui = ["dep:eframe", "dep:egui", "dep:rfd", "dep:webbrowser", "dep:rand", "dep:current_locale"] +gui = ["dep:eframe", "dep:egui", "dep:rfd", "dep:webbrowser", "dep:rand", "dep:current_locale", "dep:fontconfig"] [build-dependencies] embed-resource = "3.0.5" diff --git a/res/font/fonts.json b/res/font/fonts.json new file mode 100644 index 0000000..ab03532 --- /dev/null +++ b/res/font/fonts.json @@ -0,0 +1,56 @@ +{ + "windows": { + "zh-CN": [ + "msyh.ttc", + "msyhbd.ttc", + "simsun.ttc", + "simhei.ttf", + "simkai.ttf", + "simfang.ttf", + "msjh.ttc", + "msjhbd.ttc" + ], + "ja": [ + "meiryo.ttc", + "meiryob.ttc", + "msgothic.ttc", + "msmincho.ttc", + "YuGothM.ttc", + "YuGothB.ttc", + "YuMincho.ttc" + ] + }, + "linux": { + "zh-CN": [ + "Noto Sans CJK SC", + "WenQuanYi Micro Hei", + "WenQuanYi Zen Hei", + "AR PL UMing CN", + "AR PL UKai CN" + ], + "ja": [ + "Noto Sans CJK JP", + "Noto Sans CJK SC", + "Noto Sans CJK TC", + "Noto Sans JP", + "IPAGothic", + "IPA Gothic", + "VL Gothic" + ] + }, + "macos": { + "zh-CN": [ + "PingFang.ttc", + "STHeiti Light.ttc", + "STHeiti Medium.ttc", + "Hiragino Sans GB.ttc", + "Arial Unicode.ttf" + ], + "ja": [ + "ヒラギノ角ゴシック W3.ttc", + "ヒラギノ明朝 ProN.ttc", + "ヒラギノ丸ゴ ProN W4.ttc", + "Hiragino Sans GB.ttc" + ] + } +} diff --git a/src/ui/font_loader.rs b/src/ui/font_loader.rs new file mode 100644 index 0000000..3c1c611 --- /dev/null +++ b/src/ui/font_loader.rs @@ -0,0 +1,132 @@ +use egui::FontData; +use egui::FontFamily::{Monospace, Proportional}; +use egui::epaint::text::FontPriority::Lowest; +use egui::epaint::text::{FontInsert, InsertFontFamily}; +use serde::Deserialize; +use std::collections::HashMap; +use log::warn; + +const FONT_LIST: &str = include_str!("../../res/font/fonts.json"); + +#[cfg(target_os = "windows")] +const WINDOWS_FONT_PATH: &str = r"C:\Windows\Fonts\"; +#[cfg(target_os = "macos")] +const MACOS_FONT_PATH: &str = "/System/Library/Fonts/"; +#[cfg(target_os = "macos")] +const MACOS_FONT_PATH_SHARED: &str = "/Library/Fonts/"; + +#[derive(Deserialize)] +struct SystemFontList { + #[cfg(target_os = "windows")] + windows: PlatformFonts, + #[cfg(target_os = "linux")] + linux: PlatformFonts, + #[cfg(target_os = "macos")] + macos: PlatformFonts, +} + +type PlatformFonts = HashMap>; + +pub fn load_system_font_to_egui(ctx: &egui::Context) { + let system_font = find_system_font(); + + if system_font.is_empty() { + warn!("No system font found, some languages may not display properly."); + return; + } + + for font in system_font { + let font_insert = FontInsert::new( + &font.0, + font.1, + vec![ + InsertFontFamily { + family: Proportional, + priority: Lowest, // low priority to not override existing fonts + }, + InsertFontFamily { + family: Monospace, + priority: Lowest, + }, + ], + ); + + ctx.add_font(font_insert); + } +} + +fn find_system_font() -> HashMap { + let sys_font_list: SystemFontList = + serde_json::from_str(FONT_LIST).expect("failed to parse font list"); + + let mut result: HashMap = HashMap::new(); + + #[cfg(target_os = "windows")] + { + load_fonts_from_paths(&sys_font_list.windows, &[WINDOWS_FONT_PATH], &mut result); + } + + #[cfg(target_os = "macos")] + { + load_fonts_from_paths( + &sys_font_list.macos, + &[MACOS_FONT_PATH, MACOS_FONT_PATH_SHARED], + &mut result, + ); + } + + #[cfg(target_os = "linux")] + { + // use fontconfig for linux fo find fonts + load_fonts_from_fontconfig(&sys_font_list.linux, &mut result); + } + + result +} + +#[cfg(any(target_os = "windows", target_os = "macos"))] +fn load_fonts_from_paths( + platform_fonts: &PlatformFonts, + search_paths: &[&str], + result: &mut HashMap, +) { + for (language, font_files) in platform_fonts { + let mut loaded = false; + for font_file in font_files { + for search_path in search_paths { + let font_path = format!("{}{}", search_path, font_file); + if let Ok(font_data) = std::fs::read(&font_path) { + result.insert(language.to_string(), FontData::from_owned(font_data)); + loaded = true; + break; + } + } + if loaded { + break; + } + } + } +} + +#[cfg(all(target_os = "linux", feature = "gui"))] +fn load_fonts_from_fontconfig( + platform_fonts: &PlatformFonts, + result: &mut HashMap, +) { + use fontconfig::Fontconfig; + + if let Some(fc) = Fontconfig::new() { + platform_fonts.iter().for_each(|(language, font_names)| { + for font_name in font_names { + if let Some(font) = fc.find(font_name, None) { + if let Ok(data) = std::fs::read(font.path) { + result.insert(language.to_string(), FontData::from_owned(data)); + break; + } + } + } + }) + } else { + warn!("Failed to init Fontconfig") + } +} diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 85403b0..d000eb4 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -31,6 +31,8 @@ use egui::{ }; use std::hash::Hash; +use crate::ui::font_loader::load_system_font_to_egui; + #[derive(PartialEq, Clone, Copy, Debug)] enum Mode { Client, @@ -80,7 +82,12 @@ async fn create_window() -> Result<(), InstallerError> { eframe::run_native( &("Ornithe Installer ".to_owned() + crate::VERSION), options, - Box::new(|_cc| Ok(Box::new(app))), + Box::new(|_cc| { + // load needed system fonts + load_system_font_to_egui(&_cc.egui_ctx); + + Ok(Box::new(app)) + }), )?; Ok(()) } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 30a4ba7..603a84d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -5,6 +5,9 @@ pub mod cli; #[cfg(feature = "gui")] pub mod gui; +#[cfg(feature = "gui")] +mod font_loader; + fn home_dir() -> Option { #[allow(deprecated)] std::env::home_dir()