feat: font selection with three bundled fonts

- Add FontDef registry (思源黑体 bundled, 霞鹜文楷 + 思源宋体 runtime)
- Runtime font loading from fonts/ dir with fallback to bundled default
- ComboBox in toolbar to switch fonts
- Persist font_name in settings
- Download LXGW WenKai Lite (13MB) and Source Han Serif SC (23MB)
This commit is contained in:
Developer
2026-05-16 22:07:52 +08:00
parent f2b5be312c
commit f76c59b6cf
7 changed files with 112 additions and 33 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -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<usize> = 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,
&current_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 {

View File

@@ -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<String> {
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")
}

View File

@@ -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)))
}),
)

View File

@@ -165,6 +165,7 @@ pub struct ReaderAction {
pub toggle_bookmark: bool,
pub switch_bg: Option<BgType>,
pub switch_to_profile: Option<String>,
pub switch_font: Option<String>,
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<usize>) {
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;
}

View File

@@ -221,6 +221,7 @@ pub struct Settings {
pub reading_positions: std::collections::HashMap<String, ReadingPosition>,
pub bookmarks: std::collections::HashMap<String, Vec<Bookmark>>,
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]