diff --git a/src/book.rs b/src/book.rs index 117783d..3d670b9 100644 --- a/src/book.rs +++ b/src/book.rs @@ -43,6 +43,7 @@ pub struct TocEntry { pub struct Section { pub title: String, pub content: String, + /// Populated by pagination algorithm (pre-computed char offsets for page boundaries) pub pages: Vec, } @@ -66,7 +67,6 @@ pub fn load_epub(path: impl AsRef) -> Result { let author = doc.mdata("creator").unwrap_or_else(|| "未知作者".to_string()); let cover = doc.get_cover().ok(); let spine = doc.spine.clone(); - let raw_toc = std::mem::take(&mut doc.toc); let mut sections = Vec::new(); for (i, href) in spine.iter().enumerate() { @@ -82,6 +82,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 }) @@ -117,6 +118,7 @@ fn build_toc( let section = spine .iter() .position(|s| content_str.contains(s.trim_end_matches('/'))) + // unwrap_or(0) is safe: a real TOC entry should always match a spine item .unwrap_or(0); TocEntry { label: e.label.clone(), @@ -165,4 +167,38 @@ mod tests { fn test_strip_html_empty() { assert_eq!(strip_html(""), ""); } + + #[test] + fn test_extract_title_from_title_tag() { + let html = "My Book Title"; + assert_eq!(extract_title(html), Some("My Book Title".to_string())); + } + + #[test] + fn test_extract_title_from_h1() { + let html = "

Chapter One

text

"; + assert_eq!(extract_title(html), Some("Chapter One".to_string())); + } + + #[test] + fn test_extract_title_prefers_title() { + let html = "Book

Chapter

"; + assert_eq!(extract_title(html), Some("Book".to_string())); + } + + #[test] + fn test_extract_title_missing() { + assert_eq!(extract_title("

no title

"), None); + } + + #[test] + fn test_extract_title_empty() { + assert_eq!(extract_title(""), None); + } + + #[test] + fn test_build_toc_empty() { + let toc = build_toc(&[], &[]); + assert!(toc.is_empty()); + } }