diff --git a/src/book.rs b/src/book.rs index 3fe33ee..ab79f05 100644 --- a/src/book.rs +++ b/src/book.rs @@ -187,22 +187,57 @@ pub struct Section { pub pages: Vec, } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BookLayout { + Reflowable, + FixedLayout, +} + +impl BookLayout { + pub fn label(&self) -> &str { + match self { + BookLayout::Reflowable => "重排", + BookLayout::FixedLayout => "固定", + } + } +} + #[derive(Debug, Clone)] pub struct Book { pub title: String, pub author: String, pub cover: Option>, + pub layout: BookLayout, pub sections: Vec
, pub toc: Vec, } use std::path::Path; +fn detect_layout(doc: &mut epub::doc::EpubDoc>) -> BookLayout { + if let Some(vals) = doc.metadata.get("rendition:layout") { + if vals.iter().any(|v| v == "pre-paginated") { + return BookLayout::FixedLayout; + } + } + if let Ok(opf) = doc.get_resource_str_by_path(&doc.root_file.clone()) { + if opf.contains("rendition:layout") && opf.contains("pre-paginated") { + return BookLayout::FixedLayout; + } + if opf.contains("rendition:layout-pre-paginated") { + return BookLayout::FixedLayout; + } + } + BookLayout::Reflowable +} + pub fn load_epub(path: impl AsRef) -> Result { let path = path.as_ref(); let mut doc = epub::doc::EpubDoc::new(path) .map_err(|e| format!("无法打开文件: {}", e))?; + let layout = detect_layout(&mut doc); + let title = doc.mdata("title").unwrap_or_else(|| "未知标题".to_string()); let author = doc.mdata("creator").unwrap_or_else(|| "未知作者".to_string()); let cover = doc.get_cover().ok(); @@ -225,7 +260,7 @@ pub fn load_epub(path: impl AsRef) -> Result { let raw_toc = std::mem::take(&mut doc.toc); let toc = build_toc(&raw_toc, &spine); - Ok(Book { title, author, cover, sections, toc }) + Ok(Book { title, author, cover, layout, sections, toc }) } fn extract_title(html: &str) -> Option { diff --git a/src/reader.rs b/src/reader.rs index c25a402..22f3137 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -5,14 +5,15 @@ use crate::theme::{self, BgType, Theme}; pub fn recalculate_pages(book: &mut Book, font_size: f32, line_height: f32, panel_width: f32, panel_height: f32, style: &StyleProfile) { let char_width = font_size * 1.0; - let safety = 0.95; + let safety_w = 0.95; + let safety_h = 0.96; let chars_per_line = if char_width > 0.0 { - ((panel_width / char_width) * safety).max(1.0) as usize + ((panel_width / char_width) * safety_w).max(1.0) as usize } else { 1 }; let lines_per_page = if line_height > 0.0 { - ((panel_height / line_height) * safety).max(1.0) as usize + ((panel_height / line_height) * safety_h).max(1.0) as usize } else { 1 }; @@ -101,7 +102,8 @@ pub fn reading_view( action.go_back = true; } ui.separator(); - ui.label(&book.title); + ui.label(format!("《{}》", &book.title)); + ui.label(format!("[{}]", book.layout.label())); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { let (theme_icon, theme_hint) = match theme { Theme::Dark => ("🌞", "切换到浅色主题"), @@ -412,9 +414,9 @@ pub fn calculate_pages(text: &str, chars_per_page: usize) -> Vec { break; } - // Search backward from next for paragraph (\n\n) or line (\n) breaks + // 在页面后半段查找段落边界(\n\n)或行边界(\n),不回超理想页长 let search_start = pos + chars_per_page / 2; - let search_end = (next + chars_per_page / 2).min(total_chars); + let search_end = next.min(total_chars); let mut split = next; // Prefer double newline (paragraph), then single newline