@@ -24,6 +24,7 @@ use lang_core::Language;
2424use log:: info;
2525use log:: { LevelFilter , Log , Metadata , Record } ;
2626use once_cell:: sync:: OnceCell ;
27+ use std:: process:: Command as SysCommand ;
2728
2829// Call library side CLI and command execution
2930use 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
680692impl 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