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.
42
src/app.rs
42
src/app.rs
@@ -57,6 +57,7 @@ impl App {
|
|||||||
let settings_dir = persistence::settings_dir();
|
let settings_dir = persistence::settings_dir();
|
||||||
let settings = persistence::load_settings(&settings_dir).unwrap_or_default();
|
let settings = persistence::load_settings(&settings_dir).unwrap_or_default();
|
||||||
cc.egui_ctx.set_style(theme::create_style(&settings.theme));
|
cc.egui_ctx.set_style(theme::create_style(&settings.theme));
|
||||||
|
crate::font::setup_fonts(&cc.egui_ctx, &settings.font_name);
|
||||||
Self {
|
Self {
|
||||||
state: AppState {
|
state: AppState {
|
||||||
book: None,
|
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 bookmarks = self.settings.bookmarks.get(&file_path).cloned().unwrap_or_default();
|
||||||
let mut jump_to_bookmark: Option<usize> = None;
|
let mut jump_to_bookmark: Option<usize> = None;
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
let book = self.state.book.as_mut().unwrap();
|
let book = self.state.book.as_mut().unwrap();
|
||||||
let (action, jump) = crate::reader::reading_view(
|
let current_font = self.settings.font_name.clone();
|
||||||
ui,
|
let (action, jump) = crate::reader::reading_view(
|
||||||
book,
|
ui,
|
||||||
&mut self.state.current_section,
|
book,
|
||||||
&mut self.state.current_page,
|
&mut self.state.current_section,
|
||||||
&mut self.state.sidebar_open,
|
&mut self.state.current_page,
|
||||||
&mut style,
|
&mut self.state.sidebar_open,
|
||||||
&theme_copy,
|
&mut style,
|
||||||
bg_type.clone(),
|
&theme_copy,
|
||||||
&file_path,
|
bg_type.clone(),
|
||||||
&profile_names,
|
&file_path,
|
||||||
&bookmarks,
|
&profile_names,
|
||||||
);
|
&bookmarks,
|
||||||
|
¤t_font,
|
||||||
|
);
|
||||||
jump_to_bookmark = jump;
|
jump_to_bookmark = jump;
|
||||||
|
|
||||||
if action.go_back {
|
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;
|
self.settings.bg_type = new_bg;
|
||||||
// 切换背景时,如果选自定义图片则弹出文件选择器
|
// 切换背景时,如果选自定义图片则弹出文件选择器
|
||||||
if let BgType::Custom(_) = &self.settings.bg_type {
|
if let BgType::Custom(_) = &self.settings.bg_type {
|
||||||
|
|||||||
85
src/font.rs
85
src/font.rs
@@ -1,18 +1,71 @@
|
|||||||
pub fn setup_fonts(ctx: &eframe::egui::Context) {
|
use eframe::egui;
|
||||||
let mut fonts = eframe::egui::FontDefinitions::default();
|
use std::path::PathBuf;
|
||||||
|
|
||||||
let font_data = include_bytes!("../fonts/NotoSansSC-Regular.ttf");
|
pub struct FontDef {
|
||||||
fonts.font_data.insert(
|
pub display_name: &'static str,
|
||||||
"NotoSansSC".to_string(),
|
pub data_key: &'static str,
|
||||||
eframe::egui::FontData::from_static(font_data).into(),
|
pub ttf_filename: &'static str,
|
||||||
);
|
pub embedded: bool,
|
||||||
|
}
|
||||||
if let Some(proportional) = fonts.families.get_mut(&eframe::egui::FontFamily::Proportional) {
|
|
||||||
proportional.insert(0, "NotoSansSC".to_string());
|
pub const ALL_FONTS: &[FontDef] = &[
|
||||||
}
|
FontDef { display_name: "思源黑体", data_key: "NotoSansSC", ttf_filename: "NotoSansSC-Regular.ttf", embedded: true },
|
||||||
if let Some(monospace) = fonts.families.get_mut(&eframe::egui::FontFamily::Monospace) {
|
FontDef { display_name: "霞鹜文楷", data_key: "LXGWWenKai", ttf_filename: "LXGWWenKai-Regular.ttf", embedded: false },
|
||||||
monospace.insert(0, "NotoSansSC".to_string());
|
FontDef { display_name: "思源宋体", data_key: "SourceHanSerifSC", ttf_filename: "SourceHanSerifSC-Regular.otf", embedded: false },
|
||||||
}
|
];
|
||||||
|
|
||||||
ctx.set_fonts(fonts);
|
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",
|
"ePub Reader",
|
||||||
native_options,
|
native_options,
|
||||||
Box::new(|cc| {
|
Box::new(|cc| {
|
||||||
font::setup_fonts(&cc.egui_ctx);
|
|
||||||
Ok(Box::new(app::App::new(cc)))
|
Ok(Box::new(app::App::new(cc)))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ pub struct ReaderAction {
|
|||||||
pub toggle_bookmark: bool,
|
pub toggle_bookmark: bool,
|
||||||
pub switch_bg: Option<BgType>,
|
pub switch_bg: Option<BgType>,
|
||||||
pub switch_to_profile: Option<String>,
|
pub switch_to_profile: Option<String>,
|
||||||
|
pub switch_font: Option<String>,
|
||||||
pub page_next: bool,
|
pub page_next: bool,
|
||||||
pub page_prev: bool,
|
pub page_prev: bool,
|
||||||
}
|
}
|
||||||
@@ -181,6 +182,7 @@ pub fn reading_view(
|
|||||||
_file_path: &str,
|
_file_path: &str,
|
||||||
profile_names: &[String],
|
profile_names: &[String],
|
||||||
bookmarks: &[crate::theme::Bookmark],
|
bookmarks: &[crate::theme::Bookmark],
|
||||||
|
current_font: &str,
|
||||||
) -> (ReaderAction, Option<usize>) {
|
) -> (ReaderAction, Option<usize>) {
|
||||||
let mut action = ReaderAction {
|
let mut action = ReaderAction {
|
||||||
go_back: false,
|
go_back: false,
|
||||||
@@ -188,6 +190,7 @@ pub fn reading_view(
|
|||||||
toggle_bookmark: false,
|
toggle_bookmark: false,
|
||||||
switch_bg: None,
|
switch_bg: None,
|
||||||
switch_to_profile: None,
|
switch_to_profile: None,
|
||||||
|
switch_font: None,
|
||||||
page_next: false,
|
page_next: false,
|
||||||
page_prev: 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() {
|
if ui.button("☰").on_hover_text("打开/关闭目录").clicked() {
|
||||||
*sidebar_open = !*sidebar_open;
|
*sidebar_open = !*sidebar_open;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ pub struct Settings {
|
|||||||
pub reading_positions: std::collections::HashMap<String, ReadingPosition>,
|
pub reading_positions: std::collections::HashMap<String, ReadingPosition>,
|
||||||
pub bookmarks: std::collections::HashMap<String, Vec<Bookmark>>,
|
pub bookmarks: std::collections::HashMap<String, Vec<Bookmark>>,
|
||||||
pub window_size: Option<(f32, f32)>,
|
pub window_size: Option<(f32, f32)>,
|
||||||
|
pub font_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
@@ -235,6 +236,7 @@ impl Default for Settings {
|
|||||||
reading_positions: std::collections::HashMap::new(),
|
reading_positions: std::collections::HashMap::new(),
|
||||||
bookmarks: std::collections::HashMap::new(),
|
bookmarks: std::collections::HashMap::new(),
|
||||||
window_size: None,
|
window_size: None,
|
||||||
|
font_name: "思源黑体".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,6 +266,7 @@ mod tests {
|
|||||||
let s = Settings::default();
|
let s = Settings::default();
|
||||||
assert_eq!(s.font_size, 20.0);
|
assert_eq!(s.font_size, 20.0);
|
||||||
assert_eq!(s.theme, Theme::Light);
|
assert_eq!(s.theme, Theme::Light);
|
||||||
|
assert_eq!(s.font_name, "思源黑体");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user