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:
BIN
fonts/LXGWWenKai-Regular.ttf
Normal file
BIN
fonts/LXGWWenKai-Regular.ttf
Normal file
Binary file not shown.
BIN
fonts/SourceHanSerifSC-Regular.otf
Normal file
BIN
fonts/SourceHanSerifSC-Regular.otf
Normal file
Binary file not shown.
10
src/app.rs
10
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,
|
||||
@@ -299,6 +300,7 @@ BgType::Custom(ref path) if !path.is_empty() => {
|
||||
|
||||
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,
|
||||
@@ -311,6 +313,7 @@ BgType::Custom(ref path) if !path.is_empty() => {
|
||||
&file_path,
|
||||
&profile_names,
|
||||
&bookmarks,
|
||||
¤t_font,
|
||||
);
|
||||
jump_to_bookmark = jump;
|
||||
|
||||
@@ -336,6 +339,13 @@ BgType::Custom(ref path) if !path.is_empty() => {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
// 切换背景时,如果选自定义图片则弹出文件选择器
|
||||
|
||||
79
src/font.rs
79
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());
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user