fix(ereader): keep resume position when a block spans a page (S11)

render_page_forward discarded new_pos on the failure path, but a block that
only partially fitted has still advanced the position: paragraph_layouter
reports the word it stopped at, and _layout_paragraph_on_page packs it into
new_pos. Dropping it told the caller no progress was made, so navigation
dead-ended on any paragraph larger than a single page - the reader saw "end of
document" mid-book.

A 2877-word paragraph at 800x600 rendered 26 lines and reported the start
position back; it now paginates across 12 pages.

Also guard the navigation loop: EreaderManager.next_page treats no-progress as
end-of-document, which is only correct at the actual end. Anywhere else it now
logs the offending block index and skips that block, so a future layout bug
costs one block rather than the rest of the book.
This commit is contained in:
2026-08-06 21:06:29 +02:00
parent 583366ae1d
commit a57da8011e
3 changed files with 149 additions and 1 deletions
+8 -1
View File
@@ -344,7 +344,14 @@ class BidirectionalLayouter:
scaled_block, page, current_pos, font_scale)
if not success:
# Block doesn't fit, we're done with this page
# The block did not fit in its entirety. It may still have been
# laid out partially - a paragraph larger than one page places as
# many lines as fit and reports the word it stopped at. Keeping
# that resume point is what allows the next page to continue;
# discarding it tells the caller no progress was made, which
# dead-ends navigation on the block forever.
if self._position_compare(new_pos, current_pos) > 0:
current_pos = new_pos
break
# Add inter-block spacing after successfully laying out a block
+18
View File
@@ -9,6 +9,7 @@ into a unified, easy-to-use API.
from __future__ import annotations
from typing import List, Dict, Optional, Tuple, Any, Callable
import json
import logging
from pathlib import Path
from .ereader_layout import RenderingPosition, ChapterNavigator, ChapterInfo
@@ -20,6 +21,8 @@ from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.style.fonts import BundledFont
from pyWebLayout.layout.document_layouter import image_layouter
logger = logging.getLogger(__name__)
class BookmarkManager:
"""
@@ -417,6 +420,21 @@ class EreaderLayoutManager:
self._notify_position_changed()
return self.get_current_page()
# No progress. That is the correct answer only at the end of the
# document; anywhere else a block has failed to lay out and would trap
# the reader on this page. Skipping the block costs one block, not the
# rest of the book.
if self.current_position.block_index < len(self.blocks):
logger.error(
"Block %d made no layout progress; skipping it. This is a layout "
"bug - the block placed nothing and reported no resume point.",
self.current_position.block_index)
self.current_position = RenderingPosition(
chapter_index=self.current_position.chapter_index,
block_index=self.current_position.block_index + 1)
self._notify_position_changed()
return self.get_current_page()
return None # At end of document
def previous_page(self) -> Optional[Page]: