fix(pagination): rewrite calculate_pages to track lines instead of chars, newlines consume vertical height not horizontal
This commit is contained in:
@@ -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(§ion.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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user