diff --git a/crates/epub/src/lib.rs b/crates/epub/src/lib.rs index c151f530c..a5719a1c3 100644 --- a/crates/epub/src/lib.rs +++ b/crates/epub/src/lib.rs @@ -1065,7 +1065,8 @@ fn nav_list(entries: &[(String, String, u8)]) -> String { open.push(*level); } Some(_) => { - while open.len() > 1 && open.last().is_some_and(|t| t > level) { + // Close the levels whose parent is as deep as this entry or deeper. + while open.len() > 1 && open.len().checked_sub(2).and_then(|i| open.get(i)).is_some_and(|parent| *parent >= *level) { open.pop(); out.push_str(""); } @@ -1627,6 +1628,17 @@ mod tests { plain.add_text_frame(SpreadRef::Doc(0), Rect::new(36.0, 36.0, 300.0, 100.0), lid, "Opening\nrest", ParaFormat::default()).unwrap(); let nav = read(&plain, &EpubOptions::default(), "OEBPS/nav.xhtml"); assert!(nav.contains(&format!("

Contents

  1. {}
", link("s0", "Opening"))), "{nav}"); + // A heading shallower than the one before it but deeper than its parent stays under the + // parent: h1 A, h3 B, h2 C nest B and C under A. + let e = |t: &str, l: u8| (t.to_string(), t.to_string(), l); + assert_eq!( + nav_list(&[e("A", 1), e("B", 3), e("C", 2), e("D", 1)]), + format!("
  • {}
    1. {}
    2. {}
  • {}
  • ", link("A", "A"), link("B", "B"), link("C", "C"), link("D", "D")) + ); + assert_eq!( + nav_list(&[e("A", 1), e("B", 2), e("C", 3), e("D", 1)]), + format!("
  • {}
    1. {}
      1. {}
  • {}
  • ", link("A", "A"), link("B", "B"), link("C", "C"), link("D", "D")) + ); } #[test]