fix(pagination): rewrite calculate_pages to track lines instead of chars, newlines consume vertical height not horizontal

This commit is contained in:
Developer
2026-05-15 21:26:34 +08:00
parent 669650147b
commit 9d09e991f4

View File

@@ -17,10 +17,9 @@ pub fn recalculate_pages(book: &mut Book, font_size: f32, line_height: f32, pane
} else {
1
};
let chars_per_page = chars_per_line * lines_per_page;
for section in &mut book.sections {
let styled = style.apply_to_text(&section.content);
section.pages = calculate_pages(&styled, chars_per_page);
section.pages = calculate_pages(&styled, chars_per_line, lines_per_page);
}
}
@@ -391,60 +390,66 @@ fn render_bookmarks(
}
}
pub fn calculate_pages(text: &str, chars_per_page: usize) -> Vec<usize> {
pub fn calculate_pages(text: &str, chars_per_line: usize, lines_per_page: usize) -> Vec<usize> {
let mut pages = Vec::new();
pages.push(0);
if text.is_empty() || chars_per_page == 0 {
if text.is_empty() || chars_per_line == 0 || lines_per_page == 0 {
return pages;
}
let chars: Vec<char> = text.chars().collect();
let total_chars = chars.len();
if total_chars <= chars_per_page {
pages.push(total_chars);
let total = chars.len();
if total == 0 {
return pages;
}
let mut pos: usize = 0;
while pos < total_chars {
let next = pos + chars_per_page;
if next >= total_chars {
pages.push(total_chars);
break;
let mut page_start: usize = 0;
let mut line_count: usize = 0;
let mut line_pos: usize = 0;
let mut i: usize = 0;
while i < total {
let ch = chars[i];
if ch == '\n' {
line_count += 1;
line_pos = 0;
i += 1;
} else if line_pos >= chars_per_line {
line_count += 1;
line_pos = 0;
} else {
line_pos += 1;
i += 1;
}
// 在页面后半段查找段落边界(\n\n)或行边界(\n),不回超理想页长
let search_start = pos + chars_per_page / 2;
let search_end = next.min(total_chars);
let mut split = next;
// Prefer double newline (paragraph), then single newline
let mut found = false;
for i in (search_start..search_end).rev() {
if chars[i] == '\n' && i > 0 && chars[i - 1] == '\n' {
split = i - 1;
found = true;
break;
}
}
if !found {
for i in (search_start..search_end).rev() {
if chars[i] == '\n' {
split = i + 1;
if line_count >= lines_per_page && i > page_start {
let window_back = chars_per_line * lines_per_page / 3;
let search_start = page_start.max(i.saturating_sub(window_back));
let mut split = i;
for j in (search_start..i).rev() {
if j > 0 && chars[j] == '\n' && chars[j - 1] == '\n' {
split = j + 1;
break;
}
}
if split <= page_start {
split = i;
}
if split < total {
pages.push(split);
page_start = split;
line_count = 0;
line_pos = 0;
i = split;
}
}
if split <= pos {
split = next;
}
pos = split.min(total_chars);
pages.push(pos);
}
if *pages.last().unwrap() < total {
pages.push(total);
}
pages
}
@@ -454,38 +459,38 @@ mod tests {
#[test]
fn test_pagination_empty() {
let pages = calculate_pages("", 100);
let pages = calculate_pages("", 10, 5);
assert_eq!(pages, vec![0]);
}
#[test]
fn test_pagination_shorter_than_page() {
let pages = calculate_pages("Hello World", 100);
let pages = calculate_pages("Hello World", 10, 10);
assert_eq!(pages, vec![0, 11]);
}
#[test]
fn test_pagination_exact_fit() {
let pages = calculate_pages("ABCD", 4);
let pages = calculate_pages("ABCD", 2, 2);
assert_eq!(pages, vec![0, 4]);
}
#[test]
fn test_pagination_multiple_pages() {
let text = "A".repeat(100);
let pages = calculate_pages(&text, 30);
let pages = calculate_pages(&text, 10, 3);
assert_eq!(pages, vec![0, 30, 60, 90, 100]);
}
#[test]
fn test_pagination_single_char() {
let pages = calculate_pages("A", 1);
let pages = calculate_pages("A", 10, 5);
assert_eq!(pages, vec![0, 1]);
}
#[test]
fn test_pagination_zero_chars_per_page() {
let pages = calculate_pages("test", 0);
let pages = calculate_pages("test", 0, 5);
assert_eq!(pages, vec![0]);
}
}