From 61be919394a575f30b84e88f129b4c465541521b Mon Sep 17 00:00:00 2001 From: CrazyHPi Date: Fri, 20 Feb 2026 18:17:12 +0800 Subject: [PATCH 1/6] add font loader to load system fonts --- Cargo.lock | 21 ++++++++ Cargo.toml | 5 +- res/font/fonts.json | 32 ++++++++++++ src/ui/font_loader.rs | 114 ++++++++++++++++++++++++++++++++++++++++++ src/ui/gui.rs | 9 +++- src/ui/mod.rs | 1 + 6 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 res/font/fonts.json create mode 100644 src/ui/font_loader.rs 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..2d8af0d --- /dev/null +++ b/res/font/fonts.json @@ -0,0 +1,32 @@ +{ + "windows": { + "zh-CN": [ + "msyh.ttc", + "msyhbd.ttc" + ], + "ja": [ + "meiryo.ttc", + "msgothic.ttc" + ] + }, + "linux": { + "zh-CN": [ + "Noto Sans CJK SC", + "WenQuanYi Micro Hei" + ], + "ja": [ + "Noto Sans CJK JP", + "IPAGothic" + ] + }, + "macos": { + "zh-CN": [ + "PingFang.ttc", + "Hiragino Sans GB.ttc" + ], + "ja": [ + "ヒラギノ角ゴシック W3.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..df0ae43 --- /dev/null +++ b/src/ui/font_loader.rs @@ -0,0 +1,114 @@ +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; + +const FONT_LIST: &str = include_str!("../../res/font/fonts.json"); + +const WINDOWS_FONT_PATH: &str = r"C:\Windows\Fonts\"; +const MACOS_FONT_PATH: &str = "/System/Library/Fonts/"; +const MACOS_FONT_PATH_SHARED: &str = "/Library/Fonts/"; + +#[derive(Deserialize)] +struct SystemFontList { + windows: PlatformFonts, + linux: PlatformFonts, + macos: PlatformFonts, +} + +type PlatformFonts = HashMap>; + +pub fn load_system_font_to_egui(ctx: &egui::Context) -> Result<(), String> { + let system_font = find_system_font()?; + + 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); + } + Ok(()) +} + +fn find_system_font() -> Result, String> { + 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); + } + + Ok(result) +} + +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; + let fc = Fontconfig::new().unwrap(); + + for (_language, font_names) in platform_fonts { + 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; + } + } + } + } +} diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 85403b0..5a4c691 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).expect("Error loading system font"); + + Ok(Box::new(app)) + }), )?; Ok(()) } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 30a4ba7..e54f7a5 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -4,6 +4,7 @@ pub mod cli; #[cfg(feature = "gui")] pub mod gui; +mod font_loader; fn home_dir() -> Option { #[allow(deprecated)] From ed7684ad15da9e6c9355947f225340922c7c8c1c Mon Sep 17 00:00:00 2001 From: CrazyHPi Date: Fri, 20 Feb 2026 20:13:08 +0800 Subject: [PATCH 2/6] suppress unused warning --- src/ui/font_loader.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/font_loader.rs b/src/ui/font_loader.rs index df0ae43..2131fcc 100644 --- a/src/ui/font_loader.rs +++ b/src/ui/font_loader.rs @@ -7,10 +7,14 @@ use std::collections::HashMap; const FONT_LIST: &str = include_str!("../../res/font/fonts.json"); +#[allow(dead_code)] const WINDOWS_FONT_PATH: &str = r"C:\Windows\Fonts\"; +#[allow(dead_code)] const MACOS_FONT_PATH: &str = "/System/Library/Fonts/"; +#[allow(dead_code)] const MACOS_FONT_PATH_SHARED: &str = "/Library/Fonts/"; +#[allow(dead_code)] #[derive(Deserialize)] struct SystemFontList { windows: PlatformFonts, From 52bde3e2341da9285da8a487d24c445a5117f953 Mon Sep 17 00:00:00 2001 From: CrazyHPi Date: Fri, 20 Feb 2026 20:25:26 +0800 Subject: [PATCH 3/6] add chinese and japanese fonts from # 14 and #15 --- res/font/fonts.json | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/res/font/fonts.json b/res/font/fonts.json index 2d8af0d..ab03532 100644 --- a/res/font/fonts.json +++ b/res/font/fonts.json @@ -2,30 +2,54 @@ "windows": { "zh-CN": [ "msyh.ttc", - "msyhbd.ttc" + "msyhbd.ttc", + "simsun.ttc", + "simhei.ttf", + "simkai.ttf", + "simfang.ttf", + "msjh.ttc", + "msjhbd.ttc" ], "ja": [ "meiryo.ttc", - "msgothic.ttc" + "meiryob.ttc", + "msgothic.ttc", + "msmincho.ttc", + "YuGothM.ttc", + "YuGothB.ttc", + "YuMincho.ttc" ] }, "linux": { "zh-CN": [ "Noto Sans CJK SC", - "WenQuanYi Micro Hei" + "WenQuanYi Micro Hei", + "WenQuanYi Zen Hei", + "AR PL UMing CN", + "AR PL UKai CN" ], "ja": [ "Noto Sans CJK JP", - "IPAGothic" + "Noto Sans CJK SC", + "Noto Sans CJK TC", + "Noto Sans JP", + "IPAGothic", + "IPA Gothic", + "VL Gothic" ] }, "macos": { "zh-CN": [ "PingFang.ttc", - "Hiragino Sans GB.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" ] } From c99ee0d21eb11a8254f9c169cb692ee70dcc568d Mon Sep 17 00:00:00 2001 From: CrazyHPi Date: Sat, 21 Feb 2026 20:32:01 +0800 Subject: [PATCH 4/6] add feature gate to mod font_loader --- src/ui/font_loader.rs | 4 ---- src/ui/mod.rs | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ui/font_loader.rs b/src/ui/font_loader.rs index 2131fcc..df0ae43 100644 --- a/src/ui/font_loader.rs +++ b/src/ui/font_loader.rs @@ -7,14 +7,10 @@ use std::collections::HashMap; const FONT_LIST: &str = include_str!("../../res/font/fonts.json"); -#[allow(dead_code)] const WINDOWS_FONT_PATH: &str = r"C:\Windows\Fonts\"; -#[allow(dead_code)] const MACOS_FONT_PATH: &str = "/System/Library/Fonts/"; -#[allow(dead_code)] const MACOS_FONT_PATH_SHARED: &str = "/Library/Fonts/"; -#[allow(dead_code)] #[derive(Deserialize)] struct SystemFontList { windows: PlatformFonts, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index e54f7a5..603a84d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -4,6 +4,8 @@ pub mod cli; #[cfg(feature = "gui")] pub mod gui; + +#[cfg(feature = "gui")] mod font_loader; fn home_dir() -> Option { From bf9e104c707e942ef825d8cde6f4d9829643d456 Mon Sep 17 00:00:00 2001 From: CrazyHPi Date: Sat, 21 Feb 2026 20:39:45 +0800 Subject: [PATCH 5/6] proper err handle --- src/ui/font_loader.rs | 43 +++++++++++++++++++++++++++---------------- src/ui/gui.rs | 4 ++-- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/ui/font_loader.rs b/src/ui/font_loader.rs index df0ae43..84e8932 100644 --- a/src/ui/font_loader.rs +++ b/src/ui/font_loader.rs @@ -4,6 +4,7 @@ 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"); @@ -20,8 +21,13 @@ struct SystemFontList { type PlatformFonts = HashMap>; -pub fn load_system_font_to_egui(ctx: &egui::Context) -> Result<(), String> { - let system_font = find_system_font()?; +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( @@ -41,10 +47,9 @@ pub fn load_system_font_to_egui(ctx: &egui::Context) -> Result<(), String> { ctx.add_font(font_insert); } - Ok(()) } -fn find_system_font() -> Result, String> { +fn find_system_font() -> HashMap { let sys_font_list: SystemFontList = serde_json::from_str(FONT_LIST).expect("failed to parse font list"); @@ -70,7 +75,7 @@ fn find_system_font() -> Result, String> { load_fonts_from_fontconfig(&sys_font_list.linux, &mut result); } - Ok(result) + result } fn load_fonts_from_paths( @@ -78,13 +83,13 @@ fn load_fonts_from_paths( search_paths: &[&str], result: &mut HashMap, ) { - for (_language, font_files) in platform_fonts { + 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)); + result.insert(language.to_string(), FontData::from_owned(font_data)); loaded = true; break; } @@ -97,18 +102,24 @@ fn load_fonts_from_paths( } #[cfg(all(target_os = "linux", feature = "gui"))] -fn load_fonts_from_fontconfig(platform_fonts: &PlatformFonts, result: &mut HashMap) { +fn load_fonts_from_fontconfig( + platform_fonts: &PlatformFonts, + result: &mut HashMap, +) { use fontconfig::Fontconfig; - let fc = Fontconfig::new().unwrap(); - for (_language, font_names) in platform_fonts { - 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; + 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 5a4c691..a7cba9c 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -9,7 +9,7 @@ use egui::{ Align, Button, Checkbox, Color32, ComboBox, FontId, Frame, Layout, Margin, ProgressBar, RichText, Sense, Theme, UiBuilder, Vec2, Vec2b, }; -use log::{error, info}; +use log::{error, info, warn}; use rfd::{AsyncFileDialog, AsyncMessageDialog, MessageButtons, MessageDialogResult}; use tokio::{ sync::mpsc::{UnboundedReceiver, unbounded_channel}, @@ -84,7 +84,7 @@ async fn create_window() -> Result<(), InstallerError> { options, Box::new(|_cc| { // load needed system fonts - load_system_font_to_egui(&_cc.egui_ctx).expect("Error loading system font"); + load_system_font_to_egui(&_cc.egui_ctx); Ok(Box::new(app)) }), From 513a99dfae2af613b3b2455dd0764cf989e1068f Mon Sep 17 00:00:00 2001 From: CrazyHPi Date: Sat, 21 Feb 2026 23:58:01 +0800 Subject: [PATCH 6/6] fix build warnings --- src/ui/font_loader.rs | 7 +++++++ src/ui/gui.rs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ui/font_loader.rs b/src/ui/font_loader.rs index 84e8932..3c1c611 100644 --- a/src/ui/font_loader.rs +++ b/src/ui/font_loader.rs @@ -8,14 +8,20 @@ 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, } @@ -78,6 +84,7 @@ fn find_system_font() -> HashMap { result } +#[cfg(any(target_os = "windows", target_os = "macos"))] fn load_fonts_from_paths( platform_fonts: &PlatformFonts, search_paths: &[&str], diff --git a/src/ui/gui.rs b/src/ui/gui.rs index a7cba9c..d000eb4 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -9,7 +9,7 @@ use egui::{ Align, Button, Checkbox, Color32, ComboBox, FontId, Frame, Layout, Margin, ProgressBar, RichText, Sense, Theme, UiBuilder, Vec2, Vec2b, }; -use log::{error, info, warn}; +use log::{error, info}; use rfd::{AsyncFileDialog, AsyncMessageDialog, MessageButtons, MessageDialogResult}; use tokio::{ sync::mpsc::{UnboundedReceiver, unbounded_channel},