pyWebLayout/tests/layout/test_ereader_image_rendering.py
2025-11-12 12:03:27 +00:00

546 lines
18 KiB
Python

"""
Unit tests for Image block rendering in the ereader layout system.
Tests cover:
- Image block layout on pages
- Navigation with images (next/previous page)
- Images at different positions (start, middle, end)
- Cover page detection and handling
- Multi-page scenarios with images
"""
import unittest
import tempfile
import shutil
from pathlib import Path
from pyWebLayout.layout.ereader_manager import EreaderLayoutManager
from pyWebLayout.layout.ereader_layout import BidirectionalLayouter, RenderingPosition
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel, Image
from pyWebLayout.abstract.inline import Word
from pyWebLayout.concrete.page import Page
from pyWebLayout.style import Font
from pyWebLayout.style.page_style import PageStyle
class TestImageBlockLayout(unittest.TestCase):
"""Test basic Image block layout functionality."""
def setUp(self):
"""Set up test fixtures."""
self.base_font = Font(font_size=14)
self.page_size = (400, 600)
self.page_style = PageStyle(padding=(20, 20, 20, 20))
def test_layout_image_block_on_page(self):
"""Test that Image blocks can be laid out on pages."""
# Create a simple document with an image
blocks = [
Image(source="test.jpg", alt_text="Test Image", width=200, height=300)
]
layouter = BidirectionalLayouter(blocks, self.page_style)
position = RenderingPosition()
# Render page with image
page, next_pos = layouter.render_page_forward(position, font_scale=1.0)
# Should successfully render the page
self.assertIsNotNone(page)
self.assertIsInstance(page, Page)
# Position should advance past the image block
self.assertEqual(next_pos.block_index, 1)
def test_image_block_advances_position(self):
"""Test that rendering an image block correctly advances the position."""
blocks = [
Image(source="img1.jpg", alt_text="Image 1"),
Paragraph(self.base_font)
]
# Add some words to the paragraph
blocks[1].add_word(Word("Text after image", self.base_font))
layouter = BidirectionalLayouter(blocks, self.page_style)
position = RenderingPosition(block_index=0)
# Render page starting at image
page, next_pos = layouter.render_page_forward(position, font_scale=1.0)
# Position should either:
# 1. Move to next block if image was successfully laid out, OR
# 2. Stay at same position if image couldn't fit/render
# In either case, the layouter should handle it gracefully
self.assertIsNotNone(page)
self.assertGreaterEqual(next_pos.block_index, 0)
# If the image is at start and can't render, it may skip to next block anyway
# The important thing is the system doesn't crash
class TestImageNavigationScenarios(unittest.TestCase):
"""Test navigation scenarios with images in different positions."""
def setUp(self):
"""Set up test fixtures."""
self.base_font = Font(font_size=14)
self.page_size = (400, 600)
self.page_style = PageStyle(padding=(20, 20, 20, 20))
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
"""Clean up temporary files."""
shutil.rmtree(self.temp_dir, ignore_errors=True)
def _create_paragraph(self, text: str) -> Paragraph:
"""Helper to create a paragraph with text."""
para = Paragraph(self.base_font)
para.add_word(Word(text, self.base_font))
return para
def test_next_page_with_image_on_second_page(self):
"""Test navigating to next page when an image is on the second page."""
# Document structure: paragraph → image → paragraph
blocks = [
self._create_paragraph("First paragraph on page 1."),
Image(source="middle.jpg", alt_text="Middle Image"),
self._create_paragraph("Third paragraph after image.")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_image_nav",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Start at beginning
initial_pos = manager.current_position.block_index
self.assertEqual(initial_pos, 0)
# Navigate to next page
next_page = manager.next_page()
self.assertIsNotNone(next_page)
# Position should have advanced
self.assertGreater(manager.current_position.block_index, initial_pos)
def test_previous_page_with_image_on_previous_page(self):
"""Test navigating back when previous page contains an image."""
blocks = [
self._create_paragraph("First paragraph."),
Image(source="image1.jpg", alt_text="Image 1"),
self._create_paragraph("Third paragraph."),
self._create_paragraph("Fourth paragraph.")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_prev_image",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Navigate forward to get past the image
manager.next_page()
manager.next_page()
current_block = manager.current_position.block_index
self.assertGreater(current_block, 0)
# Navigate backward
prev_page = manager.previous_page()
self.assertIsNotNone(prev_page)
# Should have moved to an earlier position
self.assertLess(manager.current_position.block_index, current_block)
def test_multiple_images_in_sequence(self):
"""Test document with multiple consecutive images."""
blocks = [
self._create_paragraph("Introduction text."),
Image(source="img1.jpg", alt_text="Image 1"),
Image(source="img2.jpg", alt_text="Image 2"),
Image(source="img3.jpg", alt_text="Image 3"),
self._create_paragraph("Text after images.")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_multi_images",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Navigate through pages
pages_rendered = 0
max_pages = 10 # Safety limit
while pages_rendered < max_pages:
current_block = manager.current_position.block_index
# Try to go to next page
next_page = manager.next_page()
if next_page is None:
# Reached end
break
pages_rendered += 1
# Position should advance
self.assertGreaterEqual(
manager.current_position.block_index,
current_block,
f"Position should advance or stay same, page {pages_rendered}"
)
# Should have rendered at least 2 pages
self.assertGreaterEqual(pages_rendered, 1)
def test_image_at_document_start(self):
"""Test document starting with an image (not as cover)."""
blocks = [
Image(source="start.jpg", alt_text="Start Image"),
self._create_paragraph("Text after image.")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_image_start",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# First image should be detected as cover
self.assertTrue(manager.has_cover())
self.assertTrue(manager.is_on_cover())
# Navigate past cover
manager.next_page()
# Should now be at the text
self.assertFalse(manager.is_on_cover())
# Should have skipped the image block (cover)
self.assertEqual(manager.current_position.block_index, 1)
def test_image_at_document_end(self):
"""Test document ending with an image."""
blocks = [
self._create_paragraph("First paragraph."),
self._create_paragraph("Second paragraph."),
Image(source="end.jpg", alt_text="End Image")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_image_end",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Navigate to end
page_count = 0
max_pages = 10
while page_count < max_pages:
next_page = manager.next_page()
if next_page is None:
break
page_count += 1
# Should have successfully navigated through document including final image
self.assertGreater(page_count, 0)
def test_alternating_text_and_images(self):
"""Test document with alternating text and images."""
blocks = [
self._create_paragraph("Paragraph 1"),
Image(source="img1.jpg", alt_text="Image 1"),
self._create_paragraph("Paragraph 2"),
Image(source="img2.jpg", alt_text="Image 2"),
self._create_paragraph("Paragraph 3"),
Image(source="img3.jpg", alt_text="Image 3"),
self._create_paragraph("Paragraph 4")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_alternating",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Track blocks visited
blocks_visited = set()
max_pages = 15
for _ in range(max_pages):
blocks_visited.add(manager.current_position.block_index)
next_page = manager.next_page()
if next_page is None:
break
# Should have visited multiple different blocks
self.assertGreater(len(blocks_visited), 1)
class TestCoverPageWithImages(unittest.TestCase):
"""Test cover page detection and handling with images."""
def setUp(self):
"""Set up test fixtures."""
self.base_font = Font(font_size=14)
self.page_size = (400, 600)
self.page_style = PageStyle(padding=(20, 20, 20, 20))
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
"""Clean up temporary files."""
shutil.rmtree(self.temp_dir, ignore_errors=True)
def _create_paragraph(self, text: str) -> Paragraph:
"""Helper to create a paragraph with text."""
para = Paragraph(self.base_font)
para.add_word(Word(text, self.base_font))
return para
def test_cover_page_detected_from_first_image(self):
"""Test that first image is detected as cover."""
blocks = [
Image(source="cover.jpg", alt_text="Cover"),
self._create_paragraph("Chapter text.")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_cover_detection",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Should detect cover
self.assertTrue(manager.has_cover())
self.assertTrue(manager.is_on_cover())
def test_no_cover_when_first_block_is_text(self):
"""Test that cover is not detected when first block is text."""
blocks = [
self._create_paragraph("First paragraph."),
Image(source="image.jpg", alt_text="Not a cover"),
self._create_paragraph("Second paragraph.")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_no_cover",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Should NOT detect cover
self.assertFalse(manager.has_cover())
self.assertFalse(manager.is_on_cover())
def test_navigation_from_cover_skips_image_block(self):
"""Test that next_page from cover skips the cover image block."""
blocks = [
Image(source="cover.jpg", alt_text="Cover"),
self._create_paragraph("First content paragraph."),
self._create_paragraph("Second content paragraph.")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_cover_skip",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Start on cover
self.assertTrue(manager.is_on_cover())
self.assertEqual(manager.current_position.block_index, 0)
# Navigate past cover
manager.next_page()
# Should skip cover image block (index 0) and go to first content (index 1)
self.assertFalse(manager.is_on_cover())
self.assertEqual(manager.current_position.block_index, 1)
def test_previous_page_returns_to_cover(self):
"""Test that previous_page from first content returns to cover."""
blocks = [
Image(source="cover.jpg", alt_text="Cover"),
self._create_paragraph("Content text.")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_back_to_cover",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Navigate past cover
manager.next_page()
self.assertFalse(manager.is_on_cover())
# Go back
manager.previous_page()
# Should be back on cover
self.assertTrue(manager.is_on_cover())
def test_jump_to_cover_from_middle(self):
"""Test jumping to cover from middle of document."""
blocks = [
Image(source="cover.jpg", alt_text="Cover"),
self._create_paragraph("Paragraph 1"),
self._create_paragraph("Paragraph 2"),
self._create_paragraph("Paragraph 3")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_jump_cover",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Navigate to middle
manager.next_page()
manager.next_page()
self.assertFalse(manager.is_on_cover())
# Jump to cover
cover_page = manager.jump_to_cover()
self.assertIsNotNone(cover_page)
self.assertTrue(manager.is_on_cover())
class TestImageBlockPositionTracking(unittest.TestCase):
"""Test position tracking with Image blocks."""
def setUp(self):
"""Set up test fixtures."""
self.base_font = Font(font_size=14)
self.page_size = (400, 600)
self.page_style = PageStyle(padding=(20, 20, 20, 20))
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
"""Clean up temporary files."""
shutil.rmtree(self.temp_dir, ignore_errors=True)
def _create_paragraph(self, text: str) -> Paragraph:
"""Helper to create a paragraph with text."""
para = Paragraph(self.base_font)
para.add_word(Word(text, self.base_font))
return para
def test_position_info_includes_image_blocks(self):
"""Test that position info correctly handles image blocks."""
blocks = [
self._create_paragraph("Text 1"),
Image(source="img.jpg", alt_text="Image"),
self._create_paragraph("Text 2")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_pos_info",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Get initial position info
pos_info = manager.get_position_info()
self.assertIn('position', pos_info)
self.assertIn('block_index', pos_info['position'])
self.assertEqual(pos_info['position']['block_index'], 0)
def test_bookmark_image_position(self):
"""Test bookmarking at an image position."""
blocks = [
self._create_paragraph("Before image"),
Image(source="bookmarked.jpg", alt_text="Bookmarked Image"),
self._create_paragraph("After image")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_bookmark_image",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# Navigate to image position
manager.next_page()
# Add bookmark
bookmark_name = "image_location"
success = manager.add_bookmark(bookmark_name)
self.assertTrue(success)
# Navigate away
manager.next_page()
# Jump back to bookmark
page = manager.jump_to_bookmark(bookmark_name)
self.assertIsNotNone(page)
# Should be at or near the image position
# (exact position depends on how much fits on page)
self.assertGreater(manager.current_position.block_index, 0)
def test_reading_progress_with_images(self):
"""Test reading progress calculation with images in document."""
blocks = [
self._create_paragraph("Text 1"),
Image(source="img1.jpg", alt_text="Image 1"),
self._create_paragraph("Text 2"),
Image(source="img2.jpg", alt_text="Image 2"),
self._create_paragraph("Text 3")
]
manager = EreaderLayoutManager(
blocks=blocks,
page_size=self.page_size,
document_id="test_progress",
page_style=self.page_style,
bookmarks_dir=self.temp_dir
)
# At start
progress_start = manager.get_reading_progress()
self.assertEqual(progress_start, 0.0)
# Navigate through document
for _ in range(5):
if manager.next_page() is None:
break
# Progress should have increased
progress_end = manager.get_reading_progress()
self.assertGreater(progress_end, progress_start)
if __name__ == '__main__':
unittest.main()