Skip to content
This repository was archived by the owner on Feb 19, 2026. It is now read-only.

Commit d2158bd

Browse files
committed
fix: font
1 parent fd4d53b commit d2158bd

File tree

2 files changed

+184
-3
lines changed

2 files changed

+184
-3
lines changed

flake.nix

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@
7171
libGL
7272
libglvnd
7373
mesa
74+
75+
# CJK-capable fonts so GUI can render Chinese by default
76+
noto-fonts
77+
noto-fonts-cjk-sans
78+
noto-fonts-cjk-serif
79+
noto-fonts-emoji
80+
source-han-sans
81+
source-han-serif
82+
wqy_zenhei
83+
wqy_microhei
7484
];
7585

7686
# Native build inputs for linking
@@ -83,6 +93,19 @@
8393
];
8494

8595
# Environment variables
96+
# Prepare a Fontconfig configuration that includes fonts from Nix store
97+
fontsDirs = builtins.map (f: "${f}/share/fonts") [
98+
pkgs.noto-fonts
99+
pkgs.noto-fonts-cjk-sans
100+
pkgs.noto-fonts-cjk-serif
101+
pkgs.noto-fonts-emoji
102+
pkgs.source-han-sans
103+
pkgs.source-han-serif
104+
pkgs.wqy_zenhei
105+
pkgs.wqy_microhei
106+
];
107+
fontsConf = pkgs.makeFontsConf { fontDirectories = fontsDirs; };
108+
86109
shellHook = ''
87110
echo "🚀 BMS Resource Toolbox Development Environment"
88111
echo "Rust version: $(rustc --version)"
@@ -96,6 +119,9 @@
96119
export LD_LIBRARY_PATH="${pkgs.wayland}/lib:${pkgs.libxkbcommon}/lib:${pkgs.libGL}/lib:${pkgs.fontconfig}/lib:${pkgs.freetype}/lib:$LD_LIBRARY_PATH"
97120
export XKB_CONFIG_ROOT="${pkgs.xkeyboard_config}/share/X11/xkb"
98121
122+
# Ensure fontconfig sees Nix-provided fonts
123+
export FONTCONFIG_FILE="${fontsConf}"
124+
99125
echo "Ready to run GUI application."
100126
echo "Run: cargo run"
101127
'';

gui/src/main.rs

Lines changed: 158 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use lang_core::Language;
2424
use log::info;
2525
use log::{LevelFilter, Log, Metadata, Record};
2626
use once_cell::sync::OnceCell;
27+
use std::process::Command as SysCommand;
2728

2829
// Call library side CLI and command execution
2930
use bms_resource_toolbox_cli::{Cli, run_command};
@@ -670,11 +671,22 @@ fn main() -> iced::Result {
670671
info!("Available options: replace_title_artist, append_title_artist, append_artist");
671672

672673
// Use daemon-based multi-window API with title/update/view
673-
iced::daemon(App::title, App::update, App::view)
674+
let mut daemon = iced::daemon(App::title, App::update, App::view)
674675
.subscription(App::subscription)
675676
.theme(App::theme)
676-
.scale_factor(App::scale_factor)
677-
.run()
677+
.scale_factor(App::scale_factor);
678+
679+
if let Some((font_bytes, source)) = pick_chinese_font_bytes() {
680+
// Log both to stderr (visible before GUI logger) and to log::info (visible later)
681+
eprintln!("[GUI] Using Chinese font from: {source}");
682+
info!("Using Chinese font from: {source}");
683+
daemon = daemon.font(font_bytes);
684+
} else {
685+
eprintln!("[GUI] No Chinese font detected; using renderer default font");
686+
info!("No Chinese font detected; using renderer default font");
687+
}
688+
689+
daemon.run()
678690
}
679691

680692
impl Default for App {
@@ -765,6 +777,149 @@ fn init_gui_logger() {
765777
log::set_max_level(LevelFilter::Info);
766778
}
767779

780+
/// Try to pick a Chinese-capable system font and return its bytes with 'static lifetime.
781+
/// Linux: uses `fc-list :lang=zh file` to find a font path and loads it.
782+
/// Other OS: tries a small set of well-known font paths.
783+
fn pick_chinese_font_bytes() -> Option<(&'static [u8], String)> {
784+
// Prefer Linux fontconfig
785+
#[cfg(target_os = "linux")]
786+
{
787+
use std::path::PathBuf;
788+
789+
// 1) Environment override (optional)
790+
if let Ok(custom) = std::env::var("BRT_ZH_FONT") {
791+
let p = PathBuf::from(custom);
792+
if let Ok(bytes) = fs::read(&p) {
793+
let leaked: &'static [u8] = Box::leak(bytes.into_boxed_slice());
794+
return Some((leaked, format!("env:BRT_ZH_FONT={}", p.display())));
795+
}
796+
}
797+
798+
// Helper to leak bytes
799+
fn leak(bytes: Vec<u8>) -> &'static [u8] {
800+
Box::leak(bytes.into_boxed_slice())
801+
}
802+
803+
// Blacklist fonts that commonly match but are not desirable for Chinese
804+
fn is_blacklisted_font_path(path: &str) -> bool {
805+
let lp = path.to_lowercase();
806+
// DejaVu frequently matches but lacks good CJK coverage
807+
lp.contains("dejavu") || lp.contains("noto")
808+
}
809+
810+
// 2) Try fc-match for a single best font file matching zh language
811+
if let Ok(out) = SysCommand::new("fc-match")
812+
.args(["-f", "%{file}\n", ":lang=zh"]) // best matching font file path for zh
813+
.output()
814+
{
815+
if out.status.success() {
816+
let path = String::from_utf8_lossy(&out.stdout).trim().to_string();
817+
if !path.is_empty() {
818+
if is_blacklisted_font_path(&path) {
819+
eprintln!("[GUI] fc-match returned blacklisted font, ignoring: {}", path);
820+
info!("fc-match returned blacklisted font, ignoring: {}", path);
821+
} else {
822+
match fs::read(&path) {
823+
Ok(bytes) => return Some((leak(bytes), format!("fc-match:{}", path))),
824+
Err(e) => {
825+
eprintln!("[GUI] Failed to read fc-match font {}: {}", path, e);
826+
info!("Failed to read fc-match font {}: {}", path, e);
827+
}
828+
}
829+
}
830+
}
831+
}
832+
}
833+
834+
// 3) Fallback to fc-list (formatted) and pick the first candidate
835+
if let Ok(out) = SysCommand::new("fc-list")
836+
.args(["-f", "%{file}\n", ":lang=zh"]) // clean file paths
837+
.output()
838+
{
839+
if out.status.success() {
840+
let stdout = String::from_utf8_lossy(&out.stdout);
841+
let candidates: Vec<String> = stdout
842+
.lines()
843+
.map(|l| l.trim().trim_end_matches(':').to_string())
844+
.filter(|p| !p.is_empty())
845+
.collect();
846+
847+
for path in candidates {
848+
if is_blacklisted_font_path(&path) {
849+
eprintln!("[GUI] fc-list candidate blacklisted, skipping: {}", path);
850+
info!("fc-list candidate blacklisted, skipping: {}", path);
851+
continue;
852+
}
853+
// Accept file returned by fontconfig for zh
854+
match fs::read(&path) {
855+
Ok(bytes) => {
856+
return Some((leak(bytes), format!("fc-list:{}", path)));
857+
}
858+
Err(e) => {
859+
eprintln!("[GUI] Failed to read font candidate {}: {}", path, e);
860+
info!("Failed to read font candidate {}: {}", path, e);
861+
}
862+
}
863+
}
864+
}
865+
}
866+
867+
// 4) As a last resort, probe a few common paths
868+
let try_paths = [
869+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
870+
"/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf",
871+
"/usr/share/fonts/opentype/noto/NotoSansSC-Regular.otf",
872+
"/usr/share/fonts/truetype/noto/NotoSansSC-Regular.ttf",
873+
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
874+
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
875+
"/usr/share/fonts/TTF/SourceHanSansCN-Regular.otf",
876+
];
877+
for p in try_paths {
878+
if let Ok(bytes) = fs::read(p) {
879+
return Some((leak(bytes), format!("fallback:{}", p)));
880+
}
881+
}
882+
None
883+
}
884+
885+
#[cfg(target_os = "windows")]
886+
{
887+
use std::path::PathBuf;
888+
let system_root = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
889+
let mut try_paths = vec![
890+
"Fonts\\msyh.ttc", // Microsoft YaHei
891+
"Fonts\\msyh.ttf",
892+
"Fonts\\simhei.ttf", // SimHei
893+
"Fonts\\simsun.ttc", // SimSun
894+
];
895+
for rel in try_paths.drain(..) {
896+
let p = PathBuf::from(&system_root).join(rel);
897+
if let Ok(bytes) = fs::read(&p) {
898+
let leaked: &'static [u8] = Box::leak(bytes.into_boxed_slice());
899+
return Some((leaked, format!("win:{}", p.display())));
900+
}
901+
}
902+
None
903+
}
904+
905+
#[cfg(target_os = "macos")]
906+
{
907+
use std::path::PathBuf;
908+
let try_paths = [
909+
"/System/Library/Fonts/STHeiti Light.ttc",
910+
"/System/Library/Fonts/PingFang.ttc",
911+
"/System/Library/Fonts/PingFang.ttf",
912+
];
913+
for p in try_paths {
914+
if let Ok(bytes) = fs::read(PathBuf::from(p)) {
915+
let leaked: &'static [u8] = Box::leak(bytes.into_boxed_slice());
916+
return Some((leaked, format!("mac:{}", p)));
917+
}
918+
}
919+
None
920+
}
921+
}
922+
768923
// Removed independent log window implementation, unified task log display in main window
769924

770925
// Removed single window old startup entry

0 commit comments

Comments
 (0)