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 {
|
} else {
|
||||||
1
|
1
|
||||||
};
|
};
|
||||||
let chars_per_page = chars_per_line * lines_per_page;
|
|
||||||
for section in &mut book.sections {
|
for section in &mut book.sections {
|
||||||
let styled = style.apply_to_text(§ion.content);
|
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();
|
let mut pages = Vec::new();
|
||||||
pages.push(0);
|
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;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
let chars: Vec<char> = text.chars().collect();
|
let chars: Vec<char> = text.chars().collect();
|
||||||
let total_chars = chars.len();
|
let total = chars.len();
|
||||||
if total_chars <= chars_per_page {
|
if total == 0 {
|
||||||
pages.push(total_chars);
|
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pos: usize = 0;
|
let mut page_start: usize = 0;
|
||||||
while pos < total_chars {
|
let mut line_count: usize = 0;
|
||||||
let next = pos + chars_per_page;
|
let mut line_pos: usize = 0;
|
||||||
if next >= total_chars {
|
let mut i: usize = 0;
|
||||||
pages.push(total_chars);
|
|
||||||
break;
|
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),不回超理想页长
|
if line_count >= lines_per_page && i > page_start {
|
||||||
let search_start = pos + chars_per_page / 2;
|
let window_back = chars_per_line * lines_per_page / 3;
|
||||||
let search_end = next.min(total_chars);
|
let search_start = page_start.max(i.saturating_sub(window_back));
|
||||||
let mut split = next;
|
let mut split = i;
|
||||||
|
for j in (search_start..i).rev() {
|
||||||
// Prefer double newline (paragraph), then single newline
|
if j > 0 && chars[j] == '\n' && chars[j - 1] == '\n' {
|
||||||
let mut found = false;
|
split = j + 1;
|
||||||
for i in (search_start..search_end).rev() {
|
|
||||||
if chars[i] == '\n' && i > 0 && chars[i - 1] == '\n' {
|
|
||||||
split = i - 1;
|
|
||||||
found = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if split <= page_start {
|
||||||
for i in (search_start..search_end).rev() {
|
split = i;
|
||||||
if chars[i] == '\n' {
|
}
|
||||||
split = i + 1;
|
if split < total {
|
||||||
break;
|
pages.push(split);
|
||||||
|
page_start = split;
|
||||||
|
line_count = 0;
|
||||||
|
line_pos = 0;
|
||||||
|
i = split;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if split <= pos {
|
if *pages.last().unwrap() < total {
|
||||||
split = next;
|
pages.push(total);
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = split.min(total_chars);
|
|
||||||
pages.push(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
pages
|
pages
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,38 +459,38 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pagination_empty() {
|
fn test_pagination_empty() {
|
||||||
let pages = calculate_pages("", 100);
|
let pages = calculate_pages("", 10, 5);
|
||||||
assert_eq!(pages, vec![0]);
|
assert_eq!(pages, vec![0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pagination_shorter_than_page() {
|
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]);
|
assert_eq!(pages, vec![0, 11]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pagination_exact_fit() {
|
fn test_pagination_exact_fit() {
|
||||||
let pages = calculate_pages("ABCD", 4);
|
let pages = calculate_pages("ABCD", 2, 2);
|
||||||
assert_eq!(pages, vec![0, 4]);
|
assert_eq!(pages, vec![0, 4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pagination_multiple_pages() {
|
fn test_pagination_multiple_pages() {
|
||||||
let text = "A".repeat(100);
|
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]);
|
assert_eq!(pages, vec![0, 30, 60, 90, 100]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pagination_single_char() {
|
fn test_pagination_single_char() {
|
||||||
let pages = calculate_pages("A", 1);
|
let pages = calculate_pages("A", 10, 5);
|
||||||
assert_eq!(pages, vec![0, 1]);
|
assert_eq!(pages, vec![0, 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pagination_zero_chars_per_page() {
|
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]);
|
assert_eq!(pages, vec![0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user