diff --git a/fonts/LXGWWenKai-Regular.ttf b/fonts/LXGWWenKai-Regular.ttf new file mode 100644 index 0000000..8751570 Binary files /dev/null and b/fonts/LXGWWenKai-Regular.ttf differ diff --git a/fonts/SourceHanSerifSC-Regular.otf b/fonts/SourceHanSerifSC-Regular.otf new file mode 100644 index 0000000..cb41635 Binary files /dev/null and b/fonts/SourceHanSerifSC-Regular.otf differ diff --git a/src/app.rs b/src/app.rs index fc04537..651c5e1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -57,6 +57,7 @@ impl App { let settings_dir = persistence::settings_dir(); let settings = persistence::load_settings(&settings_dir).unwrap_or_default(); cc.egui_ctx.set_style(theme::create_style(&settings.theme)); + crate::font::setup_fonts(&cc.egui_ctx, &settings.font_name); Self { state: AppState { book: None, @@ -297,21 +298,23 @@ BgType::Custom(ref path) if !path.is_empty() => { let bookmarks = self.settings.bookmarks.get(&file_path).cloned().unwrap_or_default(); let mut jump_to_bookmark: Option = None; - egui::CentralPanel::default().show(ctx, |ui| { - let book = self.state.book.as_mut().unwrap(); - let (action, jump) = crate::reader::reading_view( - ui, - book, - &mut self.state.current_section, - &mut self.state.current_page, - &mut self.state.sidebar_open, - &mut style, - &theme_copy, - bg_type.clone(), - &file_path, - &profile_names, - &bookmarks, - ); + egui::CentralPanel::default().show(ctx, |ui| { + let book = self.state.book.as_mut().unwrap(); + let current_font = self.settings.font_name.clone(); + let (action, jump) = crate::reader::reading_view( + ui, + book, + &mut self.state.current_section, + &mut self.state.current_page, + &mut self.state.sidebar_open, + &mut style, + &theme_copy, + bg_type.clone(), + &file_path, + &profile_names, + &bookmarks, + ¤t_font, + ); jump_to_bookmark = jump; if action.go_back { @@ -336,7 +339,14 @@ BgType::Custom(ref path) if !path.is_empty() => { } } - if let Some(new_bg) = action.switch_bg { + if let Some(ref new_font) = action.switch_font { + if new_font != &self.settings.font_name { + let actual = crate::font::setup_fonts(ui.ctx(), new_font); + self.settings.font_name = actual; + } + } + + if let Some(new_bg) = action.switch_bg { self.settings.bg_type = new_bg; // 切换背景时,如果选自定义图片则弹出文件选择器 if let BgType::Custom(_) = &self.settings.bg_type { diff --git a/src/font.rs b/src/font.rs index 5a11e1c..33c608f 100644 --- a/src/font.rs +++ b/src/font.rs @@ -1,18 +1,71 @@ -pub fn setup_fonts(ctx: &eframe::egui::Context) { - let mut fonts = eframe::egui::FontDefinitions::default(); +use eframe::egui; +use std::path::PathBuf; - let font_data = include_bytes!("../fonts/NotoSansSC-Regular.ttf"); - fonts.font_data.insert( - "NotoSansSC".to_string(), - eframe::egui::FontData::from_static(font_data).into(), - ); - - if let Some(proportional) = fonts.families.get_mut(&eframe::egui::FontFamily::Proportional) { - proportional.insert(0, "NotoSansSC".to_string()); - } - if let Some(monospace) = fonts.families.get_mut(&eframe::egui::FontFamily::Monospace) { - monospace.insert(0, "NotoSansSC".to_string()); - } - - ctx.set_fonts(fonts); +pub struct FontDef { + pub display_name: &'static str, + pub data_key: &'static str, + pub ttf_filename: &'static str, + pub embedded: bool, +} + +pub const ALL_FONTS: &[FontDef] = &[ + FontDef { display_name: "思源黑体", data_key: "NotoSansSC", ttf_filename: "NotoSansSC-Regular.ttf", embedded: true }, + FontDef { display_name: "霞鹜文楷", data_key: "LXGWWenKai", ttf_filename: "LXGWWenKai-Regular.ttf", embedded: false }, + FontDef { display_name: "思源宋体", data_key: "SourceHanSerifSC", ttf_filename: "SourceHanSerifSC-Regular.otf", embedded: false }, +]; + +pub fn font_display_names() -> Vec { + ALL_FONTS.iter().map(|f| f.display_name.to_string()).collect() +} + +pub fn setup_fonts(ctx: &egui::Context, selected_name: &str) -> String { + let font_def = ALL_FONTS.iter().find(|f| f.display_name == selected_name) + .unwrap_or(&ALL_FONTS[0]); + + let (data_key, font_data) = if font_def.embedded { + (font_def.data_key.to_owned(), egui::FontData::from_static(include_bytes!("../fonts/NotoSansSC-Regular.ttf"))) + } else { + let fonts_dir = find_fonts_dir(); + let path = fonts_dir.join(font_def.ttf_filename); + match std::fs::read(&path) { + Ok(bytes) => (font_def.data_key.to_owned(), egui::FontData::from_owned(bytes)), + Err(_) => { + eprintln!("Font '{}' not found (looked in {:?}), falling back to 思源黑体", font_def.ttf_filename, fonts_dir); + let fallback = &ALL_FONTS[0]; + (fallback.data_key.to_owned(), egui::FontData::from_static(include_bytes!("../fonts/NotoSansSC-Regular.ttf"))) + } + } + }; + + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert(data_key.clone(), font_data.into()); + if let Some(proportional) = fonts.families.get_mut(&egui::FontFamily::Proportional) { + proportional.insert(0, data_key.clone()); + } + if let Some(monospace) = fonts.families.get_mut(&egui::FontFamily::Monospace) { + monospace.insert(0, data_key.clone()); + } + ctx.set_fonts(fonts); + + if font_def.embedded || data_key == font_def.data_key { + font_def.display_name.to_owned() + } else { + ALL_FONTS[0].display_name.to_owned() + } +} + +fn find_fonts_dir() -> PathBuf { + let cwd_fonts = PathBuf::from("fonts"); + if cwd_fonts.is_dir() { + return cwd_fonts; + } + if let Ok(exe) = std::env::current_exe() { + if let Some(parent) = exe.parent() { + let p = parent.join("fonts"); + if p.is_dir() { + return p; + } + } + } + PathBuf::from("fonts") } diff --git a/src/main.rs b/src/main.rs index 255b9d7..2df69c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,6 @@ fn main() -> eframe::Result { "ePub Reader", native_options, Box::new(|cc| { - font::setup_fonts(&cc.egui_ctx); Ok(Box::new(app::App::new(cc))) }), ) diff --git a/src/reader.rs b/src/reader.rs index 54ac5ac..e5af8ed 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -165,6 +165,7 @@ pub struct ReaderAction { pub toggle_bookmark: bool, pub switch_bg: Option, pub switch_to_profile: Option, + pub switch_font: Option, pub page_next: bool, pub page_prev: bool, } @@ -181,6 +182,7 @@ pub fn reading_view( _file_path: &str, profile_names: &[String], bookmarks: &[crate::theme::Bookmark], + current_font: &str, ) -> (ReaderAction, Option) { let mut action = ReaderAction { go_back: false, @@ -188,6 +190,7 @@ pub fn reading_view( toggle_bookmark: false, switch_bg: None, switch_to_profile: None, + switch_font: None, page_next: false, page_prev: false, }; @@ -279,6 +282,17 @@ egui::ComboBox::from_id_salt("bg_type_selector") } } }); + egui::ComboBox::from_id_salt("font_selector") + .width(100.0) + .selected_text(current_font) + .show_ui(ui, |ui| { + for name in crate::font::font_display_names() { + let selected = name == current_font; + if ui.selectable_label(selected, &name).clicked() { + action.switch_font = Some(name); + } + } + }); if ui.button("☰").on_hover_text("打开/关闭目录").clicked() { *sidebar_open = !*sidebar_open; } diff --git a/src/theme.rs b/src/theme.rs index 3fdccb1..d9ee771 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -221,6 +221,7 @@ pub struct Settings { pub reading_positions: std::collections::HashMap, pub bookmarks: std::collections::HashMap>, pub window_size: Option<(f32, f32)>, + pub font_name: String, } impl Default for Settings { @@ -235,6 +236,7 @@ impl Default for Settings { reading_positions: std::collections::HashMap::new(), bookmarks: std::collections::HashMap::new(), window_size: None, + font_name: "思源黑体".into(), } } } @@ -264,6 +266,7 @@ mod tests { let s = Settings::default(); assert_eq!(s.font_size, 20.0); assert_eq!(s.theme, Theme::Light); + assert_eq!(s.font_name, "思源黑体"); } #[test]