clean up
This commit is contained in:
parent
a014de854e
commit
f6ae60217f
221
VIEWPORT_SYSTEM_README.md
Normal file
221
VIEWPORT_SYSTEM_README.md
Normal file
@ -0,0 +1,221 @@
|
||||
# pyWebLayout Viewport System
|
||||
|
||||
The viewport system provides a movable window into large content areas, enabling efficient scrolling and memory usage for documents of any size. This complements your existing pagination system by allowing smooth scrolling within pages.
|
||||
|
||||
## Key Components
|
||||
|
||||
### 1. Viewport Class (`pyWebLayout.concrete.Viewport`)
|
||||
|
||||
A viewport that provides a movable window into a larger content area.
|
||||
|
||||
**Key Features:**
|
||||
- Only renders visible content (efficient memory usage)
|
||||
- Supports smooth scrolling in all directions
|
||||
- Provides hit testing for element interaction
|
||||
- Caches content bounds for performance
|
||||
- Auto-calculates content size or accepts explicit sizing
|
||||
|
||||
**Basic Usage:**
|
||||
```python
|
||||
from pyWebLayout.concrete import Viewport, ScrollablePageContent
|
||||
|
||||
# Create viewport
|
||||
viewport = Viewport(viewport_size=(800, 600))
|
||||
|
||||
# Add content
|
||||
content = ScrollablePageContent(content_width=800)
|
||||
content.add_child(some_renderable_element)
|
||||
viewport.add_content(content)
|
||||
|
||||
# Scroll and render
|
||||
viewport.scroll_to(0, 100)
|
||||
image = viewport.render()
|
||||
```
|
||||
|
||||
### 2. ScrollablePageContent Class
|
||||
|
||||
A specialized container designed to work with viewports for page content that can grow dynamically.
|
||||
|
||||
**Features:**
|
||||
- Auto-adjusts height as content is added
|
||||
- Optimized for vertical scrolling layouts
|
||||
- Maintains proper content positioning
|
||||
|
||||
### 3. Enhanced HTML Browser
|
||||
|
||||
The new `html_browser_with_viewport.py` demonstrates the viewport system in action:
|
||||
|
||||
**Enhanced Features:**
|
||||
- **Mouse Wheel Scrolling**: Smooth scrolling with configurable speed
|
||||
- **Keyboard Navigation**: Page Up/Down, Home/End, Arrow keys
|
||||
- **Scrollbar Integration**: Traditional scrollbar with viewport synchronization
|
||||
- **Text Selection**: Works across viewport boundaries
|
||||
- **Memory Efficient**: Only renders visible content
|
||||
|
||||
## Integration with Existing System
|
||||
|
||||
The viewport system complements your existing pagination system:
|
||||
|
||||
1. **Pages**: Still handle content layout and organization
|
||||
2. **Viewport**: Provides efficient viewing and scrolling within pages
|
||||
3. **Pagination**: Can be used for chapter/section navigation
|
||||
4. **Viewport Scrolling**: Handles smooth navigation within content
|
||||
|
||||
## Scrolling Methods
|
||||
|
||||
The viewport supports multiple scrolling methods:
|
||||
|
||||
```python
|
||||
# Direct positioning
|
||||
viewport.scroll_to(x, y)
|
||||
viewport.scroll_by(dx, dy)
|
||||
|
||||
# Convenience methods
|
||||
viewport.scroll_to_top()
|
||||
viewport.scroll_to_bottom()
|
||||
viewport.scroll_page_up()
|
||||
viewport.scroll_page_down()
|
||||
viewport.scroll_line_up(line_height)
|
||||
viewport.scroll_line_down(line_height)
|
||||
```
|
||||
|
||||
## Performance Benefits
|
||||
|
||||
### Memory Efficiency
|
||||
- Only visible elements are rendered
|
||||
- Large documents don't consume excessive memory
|
||||
- Content bounds are cached for fast intersection testing
|
||||
|
||||
### Rendering Efficiency
|
||||
- Only renders what's visible in the viewport
|
||||
- Supports partial element rendering (clipping)
|
||||
- Fast hit testing for interaction
|
||||
|
||||
### Scalability
|
||||
- Handles documents of any size
|
||||
- Performance doesn't degrade with content size
|
||||
- Efficient scrolling regardless of document length
|
||||
|
||||
## Browser Integration
|
||||
|
||||
The enhanced browser (`html_browser_with_viewport.py`) provides:
|
||||
|
||||
### User Interface
|
||||
- **Scrollbar**: Traditional scrollbar showing position and size
|
||||
- **Scroll Info**: Real-time scroll progress display
|
||||
- **Multiple Input Methods**: Mouse, keyboard, and scrollbar
|
||||
|
||||
### Interaction
|
||||
- **Hit Testing**: Click detection works within viewport
|
||||
- **Text Selection**: Select and copy text across viewport boundaries
|
||||
- **Link Navigation**: Clickable links work normally
|
||||
|
||||
### Navigation
|
||||
- **Smooth Scrolling**: Configurable scroll speed and behavior
|
||||
- **Page Navigation**: Full page scrolling with configurable overlap
|
||||
- **Precision Control**: Line-by-line scrolling for fine positioning
|
||||
|
||||
## API Reference
|
||||
|
||||
### Viewport Methods
|
||||
|
||||
```python
|
||||
# Scrolling
|
||||
viewport.scroll_to(x, y) # Absolute positioning
|
||||
viewport.scroll_by(dx, dy) # Relative movement
|
||||
viewport.scroll_to_top() # Jump to top
|
||||
viewport.scroll_to_bottom() # Jump to bottom
|
||||
viewport.scroll_page_up() # Page up
|
||||
viewport.scroll_page_down() # Page down
|
||||
viewport.scroll_line_up(pixels) # Line up
|
||||
viewport.scroll_line_down(pixels) # Line down
|
||||
|
||||
# Information
|
||||
viewport.get_scroll_info() # Detailed scroll state
|
||||
viewport.get_visible_elements() # Currently visible elements
|
||||
viewport.hit_test(point) # Find element at point
|
||||
|
||||
# Content Management
|
||||
viewport.add_content(renderable) # Add content
|
||||
viewport.clear_content() # Remove all content
|
||||
viewport.set_content_size(size) # Set explicit size
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
```python
|
||||
viewport.viewport_size # (width, height) of viewport
|
||||
viewport.content_size # (width, height) of content
|
||||
viewport.viewport_offset # (x, y) scroll position
|
||||
viewport.max_scroll_x # Maximum horizontal scroll
|
||||
viewport.max_scroll_y # Maximum vertical scroll
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Document Viewing
|
||||
Perfect for viewing long documents, articles, or books where you need to scroll through content smoothly.
|
||||
|
||||
### 2. Web Page Rendering
|
||||
Ideal for HTML rendering where pages can be very long but you only want to render the visible portion.
|
||||
|
||||
### 3. Large Data Visualization
|
||||
Useful for rendering large datasets or complex layouts where only a portion is visible at any time.
|
||||
|
||||
### 4. Mobile-Style Interfaces
|
||||
Enables smooth scrolling interfaces similar to mobile applications.
|
||||
|
||||
## Example: Basic Viewport Usage
|
||||
|
||||
```python
|
||||
from pyWebLayout.concrete import Viewport, ScrollablePageContent, Text
|
||||
from pyWebLayout.style.fonts import Font
|
||||
|
||||
# Create content
|
||||
content = ScrollablePageContent(content_width=800)
|
||||
|
||||
# Add lots of text
|
||||
font = Font(font_size=14)
|
||||
for i in range(100):
|
||||
text = Text(f"This is line {i+1} of content", font)
|
||||
content.add_child(text)
|
||||
|
||||
# Create viewport
|
||||
viewport = Viewport(viewport_size=(800, 600))
|
||||
viewport.add_content(content)
|
||||
|
||||
# Scroll and render
|
||||
viewport.scroll_to(0, 500) # Scroll down 500 pixels
|
||||
image = viewport.render() # Only renders visible content
|
||||
|
||||
# Get scroll information
|
||||
scroll_info = viewport.get_scroll_info()
|
||||
print(f"Scroll progress: {scroll_info['scroll_progress_y']:.1%}")
|
||||
```
|
||||
|
||||
## Example: Browser Integration
|
||||
|
||||
```python
|
||||
# In your HTML browser
|
||||
def handle_mouse_wheel(self, event):
|
||||
if event.delta > 0:
|
||||
self.viewport.scroll_line_up(20)
|
||||
else:
|
||||
self.viewport.scroll_line_down(20)
|
||||
self.update_display()
|
||||
|
||||
def handle_page_down(self, event):
|
||||
self.viewport.scroll_page_down()
|
||||
self.update_display()
|
||||
|
||||
def update_display(self):
|
||||
image = self.viewport.render()
|
||||
self.display_image(image)
|
||||
self.update_scrollbar()
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The viewport system provides a powerful and efficient way to handle large content areas while maintaining smooth user interaction. It integrates seamlessly with your existing pyWebLayout architecture and provides the foundation for building sophisticated document viewers and web browsers.
|
||||
|
||||
The system is designed to be both easy to use for simple cases and powerful enough for complex applications, making it a valuable addition to the pyWebLayout toolkit.
|
||||
@ -1,134 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Debug script to test EPUB pagination step by step
|
||||
"""
|
||||
|
||||
from pyWebLayout.io.readers.epub_reader import EPUBReader
|
||||
from pyWebLayout.concrete.page import Page
|
||||
from pyWebLayout.style.fonts import Font
|
||||
from pyWebLayout.abstract.document import Document, Chapter, Book
|
||||
from pyWebLayout.io.readers.html_extraction import parse_html_string
|
||||
|
||||
def debug_epub_content():
|
||||
"""Debug what content we're getting from EPUB"""
|
||||
|
||||
# Try to load a test EPUB (if available)
|
||||
epub_files = ['pg1342.epub', 'pg174-images-3.epub']
|
||||
|
||||
for epub_file in epub_files:
|
||||
try:
|
||||
print(f"\n=== Testing {epub_file} ===")
|
||||
|
||||
# Load EPUB
|
||||
reader = EPUBReader(epub_file)
|
||||
document = reader.read()
|
||||
|
||||
print(f"Document type: {type(document)}")
|
||||
print(f"Document title: {getattr(document, 'title', 'No title')}")
|
||||
|
||||
if isinstance(document, Book):
|
||||
print(f"Book title: {document.get_title()}")
|
||||
print(f"Book author: {document.get_author()}")
|
||||
print(f"Number of chapters: {len(document.chapters) if document.chapters else 0}")
|
||||
|
||||
# Get all blocks
|
||||
all_blocks = []
|
||||
if document.chapters:
|
||||
for i, chapter in enumerate(document.chapters[:2]): # Just first 2 chapters
|
||||
print(f"\nChapter {i+1}: {chapter.title}")
|
||||
print(f" Number of blocks: {len(chapter.blocks)}")
|
||||
|
||||
for j, block in enumerate(chapter.blocks[:3]): # First 3 blocks
|
||||
print(f" Block {j+1}: {type(block).__name__}")
|
||||
if hasattr(block, 'words') and callable(block.words):
|
||||
words = list(block.words())
|
||||
word_count = len(words)
|
||||
if word_count > 0:
|
||||
first_words = ' '.join([word.text for _, word in words[:10]])
|
||||
print(f" Words: {word_count} (first 10: {first_words}...)")
|
||||
else:
|
||||
print(f" No words found")
|
||||
else:
|
||||
print(f" No words method")
|
||||
|
||||
all_blocks.extend(chapter.blocks)
|
||||
|
||||
print(f"\nTotal blocks across all chapters: {len(all_blocks)}")
|
||||
|
||||
# Test block conversion
|
||||
print(f"\n=== Testing Block Conversion ===")
|
||||
page = Page(size=(700, 550))
|
||||
|
||||
converted_count = 0
|
||||
for i, block in enumerate(all_blocks[:10]): # Test first 10 blocks
|
||||
try:
|
||||
renderable = page._convert_block_to_renderable(block)
|
||||
if renderable:
|
||||
print(f"Block {i+1}: {type(block).__name__} -> {type(renderable).__name__}")
|
||||
if hasattr(renderable, '_size'):
|
||||
print(f" Size: {renderable._size}")
|
||||
converted_count += 1
|
||||
else:
|
||||
print(f"Block {i+1}: {type(block).__name__} -> None")
|
||||
except Exception as e:
|
||||
print(f"Block {i+1}: {type(block).__name__} -> ERROR: {e}")
|
||||
|
||||
print(f"Successfully converted {converted_count}/{min(10, len(all_blocks))} blocks")
|
||||
|
||||
# Test page filling
|
||||
print(f"\n=== Testing Page Filling ===")
|
||||
test_page = Page(size=(700, 550))
|
||||
blocks_added = 0
|
||||
|
||||
for i, block in enumerate(all_blocks[:20]): # Try to add first 20 blocks
|
||||
try:
|
||||
renderable = test_page._convert_block_to_renderable(block)
|
||||
if renderable:
|
||||
test_page.add_child(renderable)
|
||||
blocks_added += 1
|
||||
print(f"Added block {i+1}: {type(block).__name__}")
|
||||
|
||||
# Try layout
|
||||
test_page.layout()
|
||||
|
||||
# Calculate height
|
||||
max_bottom = 0
|
||||
for child in test_page._children:
|
||||
if hasattr(child, '_origin') and hasattr(child, '_size'):
|
||||
child_bottom = child._origin[1] + child._size[1]
|
||||
max_bottom = max(max_bottom, child_bottom)
|
||||
|
||||
print(f" Current page height: {max_bottom}")
|
||||
|
||||
if max_bottom > 510: # Page would be too full
|
||||
print(f" Page full after {blocks_added} blocks")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error adding block {i+1}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
break
|
||||
|
||||
print(f"Final page has {blocks_added} blocks")
|
||||
|
||||
# Try to render the page
|
||||
print(f"\n=== Testing Page Rendering ===")
|
||||
try:
|
||||
rendered_image = test_page.render()
|
||||
print(f"Page rendered successfully: {rendered_image.size}")
|
||||
except Exception as e:
|
||||
print(f"Page rendering failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
break # Stop after first successful file
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error with {epub_file}: {e}")
|
||||
continue
|
||||
|
||||
print("\n=== Debugging Complete ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_epub_content()
|
||||
1963
pyWebLayout/assets/fonts/DejaVuSans.ttf
Normal file
1963
pyWebLayout/assets/fonts/DejaVuSans.ttf
Normal file
File diff suppressed because one or more lines are too long
111
pyWebLayout/examples/demo_alignment_refactor.py
Normal file
111
pyWebLayout/examples/demo_alignment_refactor.py
Normal file
@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Demonstration of the refactored alignment handler system.
|
||||
This shows how the nested alignment logic has been replaced with a clean handler pattern.
|
||||
"""
|
||||
|
||||
from pyWebLayout.concrete.text import (
|
||||
Line, Text,
|
||||
LeftAlignmentHandler, CenterRightAlignmentHandler, JustifyAlignmentHandler
|
||||
)
|
||||
from pyWebLayout.style.layout import Alignment
|
||||
from pyWebLayout.style import Font
|
||||
|
||||
def demonstrate_handler_system():
|
||||
"""Demonstrate the new alignment handler system."""
|
||||
print("=" * 60)
|
||||
print("ALIGNMENT HANDLER SYSTEM DEMONSTRATION")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n1. HANDLER CREATION:")
|
||||
print(" The system now uses three specialized handlers:")
|
||||
|
||||
# Create handlers
|
||||
left_handler = LeftAlignmentHandler()
|
||||
center_handler = CenterRightAlignmentHandler(Alignment.CENTER)
|
||||
right_handler = CenterRightAlignmentHandler(Alignment.RIGHT)
|
||||
justify_handler = JustifyAlignmentHandler()
|
||||
|
||||
print(f" • LeftAlignmentHandler: {type(left_handler).__name__}")
|
||||
print(f" • CenterRightAlignmentHandler (Center): {type(center_handler).__name__}")
|
||||
print(f" • CenterRightAlignmentHandler (Right): {type(right_handler).__name__}")
|
||||
print(f" • JustifyAlignmentHandler: {type(justify_handler).__name__}")
|
||||
|
||||
print("\n2. AUTOMATIC HANDLER SELECTION:")
|
||||
print(" Lines automatically choose the correct handler based on alignment:")
|
||||
|
||||
font = Font()
|
||||
line_size = (300, 30)
|
||||
spacing = (5, 20)
|
||||
|
||||
alignments = [
|
||||
(Alignment.LEFT, "Left"),
|
||||
(Alignment.CENTER, "Center"),
|
||||
(Alignment.RIGHT, "Right"),
|
||||
(Alignment.JUSTIFY, "Justify")
|
||||
]
|
||||
|
||||
for alignment, name in alignments:
|
||||
line = Line(spacing, (0, 0), line_size, font, halign=alignment)
|
||||
handler_type = type(line._alignment_handler).__name__
|
||||
print(f" • {name:7} → {handler_type}")
|
||||
|
||||
print("\n3. HYPHENATION INTEGRATION:")
|
||||
print(" Each handler has its own hyphenation strategy:")
|
||||
|
||||
# Sample text objects and test conditions
|
||||
sample_text = [Text("Hello", font), Text("World", font)]
|
||||
word_width = 80
|
||||
available_width = 70 # Word doesn't fit
|
||||
min_spacing = 5
|
||||
|
||||
handlers = [
|
||||
("Left", left_handler),
|
||||
("Center", center_handler),
|
||||
("Right", right_handler),
|
||||
("Justify", justify_handler)
|
||||
]
|
||||
|
||||
for name, handler in handlers:
|
||||
should_hyphenate = handler.should_try_hyphenation(
|
||||
sample_text, word_width, available_width, min_spacing)
|
||||
print(f" • {name:7}: should_hyphenate = {should_hyphenate}")
|
||||
|
||||
print("\n4. SPACING CALCULATIONS:")
|
||||
print(" Each handler calculates spacing and positioning differently:")
|
||||
|
||||
for name, handler in handlers:
|
||||
spacing_calc, x_position = handler.calculate_spacing_and_position(
|
||||
sample_text, 300, 5, 20)
|
||||
print(f" • {name:7}: spacing={spacing_calc:2d}, position={x_position:3d}")
|
||||
|
||||
print("\n5. WORD ADDITION WITH INTELLIGENT HYPHENATION:")
|
||||
print(" The system now tries different hyphenation options for optimal spacing:")
|
||||
|
||||
# Test with a word that might benefit from hyphenation
|
||||
test_line = Line(spacing, (0, 0), (200, 30), font, halign=Alignment.JUSTIFY)
|
||||
test_words = ["This", "is", "a", "demonstration", "of", "smart", "hyphenation"]
|
||||
|
||||
for word in test_words:
|
||||
result = test_line.add_word(word)
|
||||
if result:
|
||||
print(f" • Word '{word}' → remainder: '{result}' (line full)")
|
||||
break
|
||||
else:
|
||||
print(f" • Added '{word}' successfully")
|
||||
|
||||
print(f" • Final line contains {len(test_line.text_objects)} text objects")
|
||||
|
||||
print("\n6. BENEFITS OF THE NEW SYSTEM:")
|
||||
print(" ✓ Separation of concerns - each alignment has its own handler")
|
||||
print(" ✓ Extensible - easy to add new alignment types")
|
||||
print(" ✓ Intelligent hyphenation - considers spacing quality")
|
||||
print(" ✓ Clean code - no more nested if/else alignment logic")
|
||||
print(" ✓ Testable - each handler can be tested independently")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("REFACTORING COMPLETE - ALIGNMENT HANDLERS WORKING!")
|
||||
print("=" * 60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
demonstrate_handler_system()
|
||||
59
pyWebLayout/examples/test_page.html
Normal file
59
pyWebLayout/examples/test_page.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Page for pyWebLayout Browser</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>pyWebLayout Browser Test Page</h1>
|
||||
<h3>Images</h3>
|
||||
<p>Here's a sample image:</p>
|
||||
<img src="tests/data/sample_image.jpg" alt="Sample Image" width="200" height="150">
|
||||
<h2>Text Formatting</h2>
|
||||
<p>This is a paragraph with <b>bold text</b>, <i>italic text</i>, and <u>underlined text</u>.</p>
|
||||
|
||||
<h3>Links</h3>
|
||||
<p>Here are some test links:</p>
|
||||
<ul>
|
||||
<li><a href="https://www.google.com" title="Google">External link to Google</a></li>
|
||||
<li><a href="#section1" title="Section 1">Internal link to Section 1</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Headers</h3>
|
||||
<h1>H1 Header</h1>
|
||||
<h2>H2 Header</h2>
|
||||
<h3>H3 Header</h3>
|
||||
<h4>H4 Header</h4>
|
||||
<h5>H5 Header</h5>
|
||||
<h6>H6 Header</h6>
|
||||
|
||||
<h3>Line Breaks and Paragraphs</h3>
|
||||
<p>This is the first paragraph.</p>
|
||||
<br>
|
||||
<p>This is the second paragraph after a line break.</p>
|
||||
|
||||
<p>
|
||||
It transpired after a confused five minutes that the man had heard Gatsby’s name around his office in a connection which he either wouldn’t reveal or didn’t fully understand. This was his day off and with laudable initiative he had hurried out “to see.”
|
||||
</p>
|
||||
<p>
|
||||
It was a random shot, and yet the reporter’s instinct was right. Gatsby’s notoriety, spread about by the hundreds who had accepted his hospitality and so become authorities upon his past, had increased all summer until he fell just short of being news. Contemporary legends such as the “underground pipeline to Canada” attached themselves to him, and there was one persistent story that he didn’t live in a house at all, but in a boat that looked like a house and was moved secretly up and down the Long Island shore. Just why these inventions were a source of satisfaction to James Gatz of North Dakota, isn’t easy to say.
|
||||
</p>
|
||||
<p>
|
||||
James Gatz—that was really, or at least legally, his name. He had changed it at the age of seventeen and at the specific moment that witnessed the beginning of his career—when he saw Dan Cody’s yacht drop anchor over the most insidious flat on Lake Superior. It was James Gatz who had been loafing along the beach that afternoon in a torn green jersey and a pair of canvas pants, but it was already Jay Gatsby who borrowed a rowboat, pulled out to the <i>Tuolomee</i>, and informed Cody that a wind might catch him and break him up in half an hour.
|
||||
</p>
|
||||
<p>
|
||||
I suppose he’d had the name ready for a long time, even then. His parents were shiftless and unsuccessful farm people—his imagination had never really accepted them as his parents at all. The truth was that Jay Gatsby of West Egg, Long Island, sprang from his Platonic conception of himself. He was a son of God—a phrase which, if it means anything, means just that—and he must be about His Father’s business, the service of a vast, vulgar, and meretricious beauty. So he invented just the sort of Jay Gatsby that a seventeen-year-old boy would be likely to invent, and to this conception he was faithful to the end.
|
||||
</p>
|
||||
|
||||
<h3 id="section1">Section 1</h3>
|
||||
<p>This is the content of section 1. You can link to this section using the internal link above.</p>
|
||||
|
||||
<h3>Images</h3>
|
||||
<p>Here's a sample image:</p>
|
||||
<img src="tests/data/sample_image.jpg" alt="Sample Image" width="200" height="150">
|
||||
|
||||
<h3>Mixed Content</h3>
|
||||
<p>This paragraph contains <b>bold</b> and <i>italic</i> text, as well as an <a href="https://www.example.com">external link</a>.</p>
|
||||
|
||||
<p><strong>Strong text</strong> and <em>emphasized text</em> should also work.</p>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test to check if the refactored text alignment system works.
|
||||
"""
|
||||
|
||||
try:
|
||||
from pyWebLayout.concrete.text import Line, Text, AlignmentHandler, LeftAlignmentHandler
|
||||
from pyWebLayout.style.layout import Alignment
|
||||
from pyWebLayout.style import Font
|
||||
print("✓ All imports successful")
|
||||
|
||||
# Create a simple font
|
||||
font = Font()
|
||||
print("✓ Font created")
|
||||
|
||||
# Create a line with left alignment
|
||||
line = Line((5, 20), (0, 0), (200, 30), font, halign=Alignment.LEFT)
|
||||
print("✓ Line created with left alignment")
|
||||
print(f" Handler type: {type(line._alignment_handler).__name__}")
|
||||
|
||||
# Try adding a word
|
||||
result = line.add_word("Hello")
|
||||
print(f"✓ Added word 'Hello', result: {result}")
|
||||
print(f" Line now has {len(line.text_objects)} text objects")
|
||||
|
||||
# Test different alignments
|
||||
alignments = [Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.JUSTIFY]
|
||||
for align in alignments:
|
||||
test_line = Line((5, 20), (0, 0), (200, 30), font, halign=align)
|
||||
handler_name = type(test_line._alignment_handler).__name__
|
||||
print(f"✓ {align.name} alignment uses {handler_name}")
|
||||
|
||||
print("✓ All tests passed!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple verification that the line splitting bug is fixed.
|
||||
"""
|
||||
|
||||
print("=" * 60)
|
||||
print("VERIFYING LINE SPLITTING BUG FIX")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
from unittest.mock import patch, Mock
|
||||
from pyWebLayout.concrete.text import Line
|
||||
from pyWebLayout.style import Font
|
||||
|
||||
font = Font(font_path=None, font_size=12, colour=(0, 0, 0))
|
||||
|
||||
print("\n1. Testing Line.add_word hyphenation behavior:")
|
||||
|
||||
# Mock pyphen for testing
|
||||
with patch('pyWebLayout.abstract.inline.pyphen') as mock_pyphen_module:
|
||||
mock_dic = Mock()
|
||||
mock_pyphen_module.Pyphen.return_value = mock_dic
|
||||
mock_dic.inserted.return_value = "can-vas"
|
||||
|
||||
# Create a narrow line that will force hyphenation
|
||||
line = Line((3, 6), (0, 0), (50, 20), font)
|
||||
|
||||
print(" Adding 'canvas' to narrow line...")
|
||||
overflow = line.add_word("canvas")
|
||||
|
||||
if line.renderable_words:
|
||||
first_part = line.renderable_words[0].word.text
|
||||
print(f" ✓ First part added to line: '{first_part}'")
|
||||
else:
|
||||
print(" ✗ No words added to line")
|
||||
|
||||
print(f" ✓ Overflow returned: '{overflow}'")
|
||||
|
||||
if overflow == "vas":
|
||||
print(" ✓ SUCCESS: Overflow contains only the next part ('vas')")
|
||||
else:
|
||||
print(f" ✗ FAILED: Expected 'vas', got '{overflow}'")
|
||||
|
||||
print("\n2. Testing paragraph layout behavior:")
|
||||
|
||||
try:
|
||||
from pyWebLayout.abstract.block import Paragraph
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
from pyWebLayout.typesetting.paragraph_layout import ParagraphLayout
|
||||
|
||||
with patch('pyWebLayout.abstract.inline.pyphen') as mock_pyphen_module:
|
||||
mock_dic = Mock()
|
||||
mock_pyphen_module.Pyphen.return_value = mock_dic
|
||||
mock_dic.inserted.return_value = "can-vas"
|
||||
|
||||
# Create a paragraph with words that will cause hyphenation
|
||||
paragraph = Paragraph(style=font)
|
||||
for word_text in ["a", "pair", "of", "canvas", "pants"]:
|
||||
word = Word(word_text, font)
|
||||
paragraph.add_word(word)
|
||||
|
||||
# Layout with narrow width to force wrapping
|
||||
layout = ParagraphLayout(
|
||||
line_width=70,
|
||||
line_height=20,
|
||||
word_spacing=(3, 6)
|
||||
)
|
||||
|
||||
lines = layout.layout_paragraph(paragraph)
|
||||
|
||||
print(f" ✓ Created paragraph with 5 words")
|
||||
print(f" ✓ Laid out into {len(lines)} lines:")
|
||||
|
||||
all_words = []
|
||||
for i, line in enumerate(lines):
|
||||
line_words = [word.word.text for word in line.renderable_words]
|
||||
line_text = ' '.join(line_words)
|
||||
all_words.extend(line_words)
|
||||
print(f" Line {i+1}: '{line_text}'")
|
||||
|
||||
# Check that we didn't lose any content
|
||||
original_chars = set(''.join(["a", "pair", "of", "canvas", "pants"]))
|
||||
rendered_chars = set(''.join(word.replace('-', '') for word in all_words))
|
||||
|
||||
if original_chars == rendered_chars:
|
||||
print(" ✓ SUCCESS: All characters preserved in layout")
|
||||
else:
|
||||
print(" ✗ FAILED: Some characters were lost")
|
||||
print(f" Missing: {original_chars - rendered_chars}")
|
||||
|
||||
except ImportError as e:
|
||||
print(f" Warning: Could not test paragraph layout: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("VERIFICATION COMPLETE")
|
||||
print("=" * 60)
|
||||
print("The line splitting bug fixes have been implemented:")
|
||||
print("1. Line.add_word() now returns only the next hyphenated part")
|
||||
print("2. Paragraph layout preserves overflow text correctly")
|
||||
print("3. No text should be lost during line wrapping")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during verification: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@ -1,189 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive test to verify both the line-level hyphenation fix
|
||||
and the paragraph-level overflow fix are working correctly.
|
||||
"""
|
||||
|
||||
from unittest.mock import patch, Mock
|
||||
from pyWebLayout.concrete.text import Line
|
||||
from pyWebLayout.abstract.block import Paragraph
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
from pyWebLayout.typesetting.paragraph_layout import ParagraphLayout
|
||||
from pyWebLayout.style import Font
|
||||
|
||||
def test_complete_fix():
|
||||
"""Test that both line-level and paragraph-level fixes work together"""
|
||||
print("Testing complete line splitting fix...")
|
||||
|
||||
font = Font(font_path=None, font_size=12, colour=(0, 0, 0))
|
||||
|
||||
# Test 1: Direct line hyphenation fix
|
||||
print("\n1. Testing direct line hyphenation fix:")
|
||||
|
||||
with patch('pyWebLayout.abstract.inline.pyphen') as mock_pyphen_module:
|
||||
mock_dic = Mock()
|
||||
mock_pyphen_module.Pyphen.return_value = mock_dic
|
||||
mock_dic.inserted.return_value = "can-vas"
|
||||
|
||||
line = Line((3, 6), (0, 0), (50, 20), font)
|
||||
overflow = line.add_word("canvas")
|
||||
|
||||
first_part = line.renderable_words[0].word.text if line.renderable_words else "None"
|
||||
|
||||
print(f" Word: 'canvas' -> hyphenated to 'can-vas'")
|
||||
print(f" First part in line: '{first_part}'")
|
||||
print(f" Overflow: '{overflow}'")
|
||||
|
||||
if overflow == "vas":
|
||||
print(" ✓ Line-level fix working: overflow contains only next part")
|
||||
else:
|
||||
print(" ✗ Line-level fix failed")
|
||||
return False
|
||||
|
||||
# Test 2: Paragraph-level overflow handling
|
||||
print("\n2. Testing paragraph-level overflow handling:")
|
||||
|
||||
with patch('pyWebLayout.abstract.inline.pyphen') as mock_pyphen_module:
|
||||
mock_dic = Mock()
|
||||
mock_pyphen_module.Pyphen.return_value = mock_dic
|
||||
|
||||
# Mock different hyphenation patterns
|
||||
def mock_inserted(text, hyphen='-'):
|
||||
patterns = {
|
||||
"canvas": "can-vas",
|
||||
"vas": "vas", # No hyphenation needed for short words
|
||||
"pants": "pants",
|
||||
}
|
||||
return patterns.get(text, text)
|
||||
|
||||
mock_dic.inserted.side_effect = mock_inserted
|
||||
|
||||
# Create a paragraph with the problematic sentence
|
||||
paragraph = Paragraph(style=font)
|
||||
words_text = ["and", "a", "pair", "of", "canvas", "pants", "but", "it"]
|
||||
|
||||
for word_text in words_text:
|
||||
word = Word(word_text, font)
|
||||
paragraph.add_word(word)
|
||||
|
||||
# Layout the paragraph with narrow lines to force wrapping
|
||||
layout = ParagraphLayout(
|
||||
line_width=60, # Narrow to force wrapping
|
||||
line_height=20,
|
||||
word_spacing=(3, 6)
|
||||
)
|
||||
|
||||
lines = layout.layout_paragraph(paragraph)
|
||||
|
||||
print(f" Created paragraph with words: {words_text}")
|
||||
print(f" Rendered into {len(lines)} lines:")
|
||||
|
||||
all_rendered_text = []
|
||||
for i, line in enumerate(lines):
|
||||
line_words = [word.word.text for word in line.renderable_words]
|
||||
line_text = ' '.join(line_words)
|
||||
all_rendered_text.extend(line_words)
|
||||
print(f" Line {i+1}: {line_text}")
|
||||
|
||||
# Check that no text was lost
|
||||
original_text_parts = []
|
||||
for word in words_text:
|
||||
if word == "canvas":
|
||||
# Should be split into "can-" and "vas"
|
||||
original_text_parts.extend(["can-", "vas"])
|
||||
else:
|
||||
original_text_parts.append(word)
|
||||
|
||||
print(f" Expected text parts: {original_text_parts}")
|
||||
print(f" Actual text parts: {all_rendered_text}")
|
||||
|
||||
# Reconstruct text by removing hyphens and joining
|
||||
expected_clean = ''.join(word.rstrip('-') for word in original_text_parts)
|
||||
actual_clean = ''.join(word.rstrip('-') for word in all_rendered_text)
|
||||
|
||||
print(f" Expected clean text: '{expected_clean}'")
|
||||
print(f" Actual clean text: '{actual_clean}'")
|
||||
|
||||
if expected_clean == actual_clean:
|
||||
print(" ✓ Paragraph-level fix working: no text lost in overflow")
|
||||
else:
|
||||
print(" ✗ Paragraph-level fix failed: text was lost")
|
||||
return False
|
||||
|
||||
# Test 3: Real-world scenario with the specific "canvas" case
|
||||
print("\n3. Testing real-world canvas scenario:")
|
||||
|
||||
with patch('pyWebLayout.abstract.inline.pyphen') as mock_pyphen_module:
|
||||
mock_dic = Mock()
|
||||
mock_pyphen_module.Pyphen.return_value = mock_dic
|
||||
mock_dic.inserted.return_value = "can-vas"
|
||||
|
||||
# Test the specific reported issue
|
||||
paragraph = Paragraph(style=font)
|
||||
sentence = "and a pair of canvas pants but"
|
||||
words = sentence.split()
|
||||
|
||||
for word_text in words:
|
||||
word = Word(word_text, font)
|
||||
paragraph.add_word(word)
|
||||
|
||||
layout = ParagraphLayout(
|
||||
line_width=120, # Width that causes "canvas" to hyphenate at line end
|
||||
line_height=20,
|
||||
word_spacing=(3, 6)
|
||||
)
|
||||
|
||||
lines = layout.layout_paragraph(paragraph)
|
||||
|
||||
print(f" Original sentence: '{sentence}'")
|
||||
print(f" Rendered into {len(lines)} lines:")
|
||||
|
||||
rendered_lines_text = []
|
||||
for i, line in enumerate(lines):
|
||||
line_words = [word.word.text for word in line.renderable_words]
|
||||
line_text = ' '.join(line_words)
|
||||
rendered_lines_text.append(line_text)
|
||||
print(f" Line {i+1}: '{line_text}'")
|
||||
|
||||
# Check if we see the pattern "can-" at end of line and "vas" at start of next
|
||||
found_proper_split = False
|
||||
for i in range(len(rendered_lines_text) - 1):
|
||||
current_line = rendered_lines_text[i]
|
||||
next_line = rendered_lines_text[i + 1]
|
||||
|
||||
if "can-" in current_line and ("vas" in next_line or next_line.startswith("vas")):
|
||||
found_proper_split = True
|
||||
print(f" ✓ Found proper canvas split: '{current_line}' -> '{next_line}'")
|
||||
break
|
||||
|
||||
if found_proper_split:
|
||||
print(" ✓ Real-world scenario working: 'vas' is preserved")
|
||||
else:
|
||||
# Check if all original words are preserved (even without hyphenation)
|
||||
all_words_preserved = True
|
||||
for word in words:
|
||||
found = False
|
||||
for line_text in rendered_lines_text:
|
||||
if word in line_text or word.rstrip('-') in line_text.replace('-', ''):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
print(f" ✗ Word '{word}' not found in rendered output")
|
||||
all_words_preserved = False
|
||||
|
||||
if all_words_preserved:
|
||||
print(" ✓ All words preserved (even if hyphenation pattern differs)")
|
||||
else:
|
||||
print(" ✗ Some words were lost")
|
||||
return False
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("ALL TESTS PASSED - COMPLETE LINE SPLITTING FIX WORKS!")
|
||||
print("="*60)
|
||||
print("✓ Line-level hyphenation returns only next part")
|
||||
print("✓ Paragraph-level overflow handling preserves all text")
|
||||
print("✓ Real-world scenarios work correctly")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_complete_fix()
|
||||
@ -1,197 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the new external pagination system.
|
||||
|
||||
This script tests the new BlockPaginator and handler architecture
|
||||
to ensure it works correctly with different block types.
|
||||
"""
|
||||
|
||||
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
from pyWebLayout.concrete.page import Page
|
||||
from pyWebLayout.style.fonts import Font
|
||||
from pyWebLayout.typesetting.block_pagination import BlockPaginator, PaginationResult
|
||||
|
||||
|
||||
def create_test_paragraph(text: str, font: Font = None) -> Paragraph:
|
||||
"""Create a test paragraph with the given text."""
|
||||
if font is None:
|
||||
font = Font(font_size=16)
|
||||
|
||||
paragraph = Paragraph(font)
|
||||
words = text.split()
|
||||
|
||||
for word_text in words:
|
||||
word = Word(word_text, font)
|
||||
paragraph.add_word(word)
|
||||
|
||||
return paragraph
|
||||
|
||||
|
||||
def create_test_heading(text: str, level: HeadingLevel = HeadingLevel.H1) -> Heading:
|
||||
"""Create a test heading with the given text."""
|
||||
font = Font(font_size=20)
|
||||
heading = Heading(level, font)
|
||||
|
||||
words = text.split()
|
||||
for word_text in words:
|
||||
word = Word(word_text, font)
|
||||
heading.add_word(word)
|
||||
|
||||
return heading
|
||||
|
||||
|
||||
def test_paragraph_pagination():
|
||||
"""Test paragraph pagination with line breaking."""
|
||||
print("Testing paragraph pagination...")
|
||||
|
||||
# Create a long paragraph
|
||||
long_text = " ".join(["This is a very long paragraph that should be broken across multiple lines."] * 10)
|
||||
paragraph = create_test_paragraph(long_text)
|
||||
|
||||
# Create a page with limited height
|
||||
page = Page(size=(400, 200)) # Small page
|
||||
|
||||
# Test the pagination handler
|
||||
paginator = BlockPaginator()
|
||||
result = paginator.paginate_block(paragraph, page, available_height=100)
|
||||
|
||||
print(f"Paragraph pagination result:")
|
||||
print(f" Success: {result.success}")
|
||||
print(f" Height used: {result.height_used}")
|
||||
print(f" Has remainder: {result.remainder is not None}")
|
||||
print(f" Can continue: {result.can_continue}")
|
||||
|
||||
return result.success
|
||||
|
||||
|
||||
def test_page_filling():
|
||||
"""Test filling a page with multiple blocks."""
|
||||
print("\nTesting page filling with multiple blocks...")
|
||||
|
||||
# Create test blocks
|
||||
blocks = [
|
||||
create_test_heading("Chapter 1: Introduction"),
|
||||
create_test_paragraph("This is the first paragraph of the chapter. It contains some introductory text."),
|
||||
create_test_paragraph("This is the second paragraph. It has more content and should flow nicely."),
|
||||
create_test_heading("Section 1.1: Overview", HeadingLevel.H2),
|
||||
create_test_paragraph("This is a paragraph under the section. It has even more content that might not fit on the same page."),
|
||||
create_test_paragraph("This is another long paragraph that definitely won't fit. " * 20),
|
||||
]
|
||||
|
||||
# Create a page
|
||||
page = Page(size=(600, 400))
|
||||
|
||||
# Fill the page
|
||||
next_index, remainder_blocks = page.fill_with_blocks(blocks)
|
||||
|
||||
print(f"Page filling result:")
|
||||
print(f" Blocks processed: {next_index} out of {len(blocks)}")
|
||||
print(f" Remainder blocks: {len(remainder_blocks)}")
|
||||
print(f" Page children: {len(page._children)}")
|
||||
|
||||
# Try to render the page
|
||||
try:
|
||||
page_image = page.render()
|
||||
print(f" Page rendered successfully: {page_image.size}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" Page rendering failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_multi_page_creation():
|
||||
"""Test creating multiple pages from a list of blocks."""
|
||||
print("\nTesting multi-page creation...")
|
||||
|
||||
# Create many test blocks
|
||||
blocks = []
|
||||
for i in range(10):
|
||||
blocks.append(create_test_heading(f"Chapter {i+1}"))
|
||||
for j in range(3):
|
||||
long_text = f"This is paragraph {j+1} of chapter {i+1}. " * 15
|
||||
blocks.append(create_test_paragraph(long_text))
|
||||
|
||||
print(f"Created {len(blocks)} blocks total")
|
||||
|
||||
# Create pages until all blocks are processed
|
||||
pages = []
|
||||
remaining_blocks = blocks
|
||||
page_count = 0
|
||||
|
||||
while remaining_blocks and page_count < 20: # Safety limit
|
||||
page = Page(size=(600, 400))
|
||||
next_index, remainder_blocks = page.fill_with_blocks(remaining_blocks)
|
||||
|
||||
if page._children:
|
||||
pages.append(page)
|
||||
page_count += 1
|
||||
|
||||
# Update remaining blocks
|
||||
if remainder_blocks:
|
||||
remaining_blocks = remainder_blocks
|
||||
elif next_index < len(remaining_blocks):
|
||||
remaining_blocks = remaining_blocks[next_index:]
|
||||
else:
|
||||
remaining_blocks = []
|
||||
|
||||
# Safety check
|
||||
if not page._children and remaining_blocks:
|
||||
print(f" Warning: Infinite loop detected, stopping")
|
||||
break
|
||||
|
||||
print(f"Multi-page creation result:")
|
||||
print(f" Pages created: {len(pages)}")
|
||||
print(f" Remaining blocks: {len(remaining_blocks)}")
|
||||
|
||||
# Try to render a few pages
|
||||
rendered_count = 0
|
||||
for i, page in enumerate(pages[:3]): # Test first 3 pages
|
||||
try:
|
||||
page_image = page.render()
|
||||
rendered_count += 1
|
||||
print(f" Page {i+1} rendered: {page_image.size}")
|
||||
except Exception as e:
|
||||
print(f" Page {i+1} rendering failed: {e}")
|
||||
|
||||
return len(pages) > 0 and rendered_count > 0
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all pagination tests."""
|
||||
print("=== Testing New Pagination System ===")
|
||||
|
||||
results = []
|
||||
|
||||
try:
|
||||
results.append(test_paragraph_pagination())
|
||||
except Exception as e:
|
||||
print(f"Paragraph pagination test failed: {e}")
|
||||
results.append(False)
|
||||
|
||||
try:
|
||||
results.append(test_page_filling())
|
||||
except Exception as e:
|
||||
print(f"Page filling test failed: {e}")
|
||||
results.append(False)
|
||||
|
||||
try:
|
||||
results.append(test_multi_page_creation())
|
||||
except Exception as e:
|
||||
print(f"Multi-page creation test failed: {e}")
|
||||
results.append(False)
|
||||
|
||||
print(f"\n=== Test Results ===")
|
||||
print(f"Paragraph pagination: {'PASS' if results[0] else 'FAIL'}")
|
||||
print(f"Page filling: {'PASS' if results[1] else 'FAIL'}")
|
||||
print(f"Multi-page creation: {'PASS' if results[2] else 'FAIL'}")
|
||||
|
||||
overall_result = all(results)
|
||||
print(f"Overall: {'PASS' if overall_result else 'FAIL'}")
|
||||
|
||||
return overall_result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
exit(0 if success else 1)
|
||||
@ -1,46 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Page for pyWebLayout Browser</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>pyWebLayout Browser Test Page</h1>
|
||||
<h3>Images</h3>
|
||||
<p>Here's a sample image:</p>
|
||||
<img src="tests/data/sample_image.jpg" alt="Sample Image" width="200" height="150">
|
||||
<h2>Text Formatting</h2>
|
||||
<p>This is a paragraph with <b>bold text</b>, <i>italic text</i>, and <u>underlined text</u>.</p>
|
||||
|
||||
<h3>Links</h3>
|
||||
<p>Here are some test links:</p>
|
||||
<ul>
|
||||
<li><a href="https://www.google.com" title="Google">External link to Google</a></li>
|
||||
<li><a href="#section1" title="Section 1">Internal link to Section 1</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Headers</h3>
|
||||
<h1>H1 Header</h1>
|
||||
<h2>H2 Header</h2>
|
||||
<h3>H3 Header</h3>
|
||||
<h4>H4 Header</h4>
|
||||
<h5>H5 Header</h5>
|
||||
<h6>H6 Header</h6>
|
||||
|
||||
<h3>Line Breaks and Paragraphs</h3>
|
||||
<p>This is the first paragraph.</p>
|
||||
<br>
|
||||
<p>This is the second paragraph after a line break.</p>
|
||||
|
||||
<h3 id="section1">Section 1</h3>
|
||||
<p>This is the content of section 1. You can link to this section using the internal link above.</p>
|
||||
|
||||
<h3>Images</h3>
|
||||
<p>Here's a sample image:</p>
|
||||
<img src="tests/data/sample_image.jpg" alt="Sample Image" width="200" height="150">
|
||||
|
||||
<h3>Mixed Content</h3>
|
||||
<p>This paragraph contains <b>bold</b> and <i>italic</i> text, as well as an <a href="https://www.example.com">external link</a>.</p>
|
||||
|
||||
<p><strong>Strong text</strong> and <em>emphasized text</em> should also work.</p>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,145 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test of pagination logic without EPUB dependencies
|
||||
"""
|
||||
|
||||
from pyWebLayout.concrete.page import Page
|
||||
from pyWebLayout.concrete.text import Text
|
||||
from pyWebLayout.style.fonts import Font
|
||||
from pyWebLayout.abstract.block import Paragraph
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
|
||||
def create_test_paragraph(text_content: str) -> Paragraph:
|
||||
"""Create a test paragraph with the given text"""
|
||||
paragraph = Paragraph()
|
||||
words = text_content.split()
|
||||
font = Font(font_size=16)
|
||||
|
||||
for word_text in words:
|
||||
word = Word(word_text, font)
|
||||
paragraph.add_word(word)
|
||||
|
||||
return paragraph
|
||||
|
||||
def test_simple_pagination():
|
||||
"""Test pagination with simple content"""
|
||||
print("=== Simple Pagination Test ===")
|
||||
|
||||
# Create test content - several paragraphs
|
||||
test_paragraphs = [
|
||||
"This is the first paragraph. It contains some text that should be rendered properly on the page. We want to see if this content appears correctly when we paginate.",
|
||||
"Here is a second paragraph with different content. This paragraph should also appear on the page if there's enough space, or on the next page if the first paragraph fills it up.",
|
||||
"The third paragraph continues with more text. This is testing whether our pagination logic works correctly and doesn't lose content.",
|
||||
"Fourth paragraph here. We're adding more content to test how the pagination handles multiple blocks of text.",
|
||||
"Fifth paragraph with even more content. This should help us see if the pagination is working as expected.",
|
||||
"Sixth paragraph continues the pattern. We want to make sure no text gets lost during pagination.",
|
||||
"Seventh paragraph adds more content. This is important for testing the fill-until-full logic.",
|
||||
"Eighth paragraph here with more text to test pagination thoroughly."
|
||||
]
|
||||
|
||||
# Convert to abstract blocks
|
||||
blocks = []
|
||||
for i, text in enumerate(test_paragraphs):
|
||||
paragraph = create_test_paragraph(text)
|
||||
blocks.append(paragraph)
|
||||
print(f"Created paragraph {i+1}: {len(text.split())} words")
|
||||
|
||||
print(f"\nTotal blocks created: {len(blocks)}")
|
||||
|
||||
# Test page creation and filling
|
||||
pages = []
|
||||
current_page = Page(size=(700, 550))
|
||||
|
||||
print(f"\n=== Testing Block Addition ===")
|
||||
|
||||
for i, block in enumerate(blocks):
|
||||
print(f"\nTesting block {i+1}...")
|
||||
|
||||
# Convert block to renderable
|
||||
try:
|
||||
renderable = current_page._convert_block_to_renderable(block)
|
||||
if not renderable:
|
||||
print(f" Block {i+1}: Could not convert to renderable")
|
||||
continue
|
||||
|
||||
print(f" Block {i+1}: Converted to {type(renderable).__name__}")
|
||||
|
||||
# Store current state
|
||||
children_backup = current_page._children.copy()
|
||||
|
||||
# Try adding to page
|
||||
current_page.add_child(renderable)
|
||||
|
||||
# Try layout
|
||||
try:
|
||||
current_page.layout()
|
||||
|
||||
# Calculate height
|
||||
max_bottom = 0
|
||||
for child in current_page._children:
|
||||
if hasattr(child, '_origin') and hasattr(child, '_size'):
|
||||
child_bottom = child._origin[1] + child._size[1]
|
||||
max_bottom = max(max_bottom, child_bottom)
|
||||
|
||||
print(f" Page height after adding: {max_bottom}")
|
||||
|
||||
# Check if page is too full
|
||||
if max_bottom > 510: # Leave room for padding
|
||||
print(f" Page full! Starting new page...")
|
||||
|
||||
# Rollback the last addition
|
||||
current_page._children = children_backup
|
||||
|
||||
# Finalize current page
|
||||
pages.append(current_page)
|
||||
print(f" Finalized page {len(pages)} with {len(current_page._children)} children")
|
||||
|
||||
# Start new page
|
||||
current_page = Page(size=(700, 550))
|
||||
current_page.add_child(renderable)
|
||||
current_page.layout()
|
||||
|
||||
# Calculate new page height
|
||||
max_bottom = 0
|
||||
for child in current_page._children:
|
||||
if hasattr(child, '_origin') and hasattr(child, '_size'):
|
||||
child_bottom = child._origin[1] + child._size[1]
|
||||
max_bottom = max(max_bottom, child_bottom)
|
||||
|
||||
print(f" New page height: {max_bottom}")
|
||||
else:
|
||||
print(f" Block fits, continuing...")
|
||||
|
||||
except Exception as e:
|
||||
print(f" Layout error: {e}")
|
||||
current_page._children = children_backup
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
except Exception as e:
|
||||
print(f" Conversion error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Add final page if it has content
|
||||
if current_page._children:
|
||||
pages.append(current_page)
|
||||
print(f"\nFinalized final page {len(pages)} with {len(current_page._children)} children")
|
||||
|
||||
print(f"\n=== Pagination Results ===")
|
||||
print(f"Total pages created: {len(pages)}")
|
||||
|
||||
for i, page in enumerate(pages):
|
||||
print(f"Page {i+1}: {len(page._children)} blocks")
|
||||
|
||||
# Try to render each page
|
||||
try:
|
||||
rendered_image = page.render()
|
||||
print(f" Rendered successfully: {rendered_image.size}")
|
||||
except Exception as e:
|
||||
print(f" Render error: {e}")
|
||||
|
||||
return pages
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_simple_pagination()
|
||||
191
tests/test_new_pagination_system.py
Normal file
191
tests/test_new_pagination_system.py
Normal file
@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unit tests for the new external pagination system.
|
||||
|
||||
Tests the BlockPaginator and handler architecture to ensure it works correctly
|
||||
with different block types using the unittest framework.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
from pyWebLayout.concrete.page import Page
|
||||
from pyWebLayout.style.fonts import Font
|
||||
from pyWebLayout.typesetting.block_pagination import BlockPaginator, PaginationResult
|
||||
|
||||
|
||||
class TestNewPaginationSystem(unittest.TestCase):
|
||||
"""Test cases for the new pagination system."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.font = Font(font_size=16)
|
||||
self.heading_font = Font(font_size=20)
|
||||
|
||||
def create_test_paragraph(self, text: str, font: Font = None) -> Paragraph:
|
||||
"""Create a test paragraph with the given text."""
|
||||
if font is None:
|
||||
font = self.font
|
||||
|
||||
paragraph = Paragraph(font)
|
||||
words = text.split()
|
||||
|
||||
for word_text in words:
|
||||
word = Word(word_text, font)
|
||||
paragraph.add_word(word)
|
||||
|
||||
return paragraph
|
||||
|
||||
def create_test_heading(self, text: str, level: HeadingLevel = HeadingLevel.H1) -> Heading:
|
||||
"""Create a test heading with the given text."""
|
||||
heading = Heading(level, self.heading_font)
|
||||
|
||||
words = text.split()
|
||||
for word_text in words:
|
||||
word = Word(word_text, self.heading_font)
|
||||
heading.add_word(word)
|
||||
|
||||
return heading
|
||||
|
||||
def test_paragraph_pagination(self):
|
||||
"""Test paragraph pagination with line breaking."""
|
||||
# Create a long paragraph
|
||||
long_text = " ".join(["This is a very long paragraph that should be broken across multiple lines."] * 10)
|
||||
paragraph = self.create_test_paragraph(long_text)
|
||||
|
||||
# Create a page with limited height
|
||||
page = Page(size=(400, 200)) # Small page
|
||||
|
||||
# Test the pagination handler
|
||||
paginator = BlockPaginator()
|
||||
result = paginator.paginate_block(paragraph, page, available_height=100)
|
||||
|
||||
# Assertions
|
||||
self.assertIsInstance(result, PaginationResult)
|
||||
self.assertIsInstance(result.success, bool)
|
||||
self.assertIsInstance(result.height_used, (int, float))
|
||||
self.assertGreaterEqual(result.height_used, 0)
|
||||
|
||||
def test_page_filling(self):
|
||||
"""Test filling a page with multiple blocks."""
|
||||
# Create test blocks
|
||||
blocks = [
|
||||
self.create_test_heading("Chapter 1: Introduction"),
|
||||
self.create_test_paragraph("This is the first paragraph of the chapter. It contains some introductory text."),
|
||||
self.create_test_paragraph("This is the second paragraph. It has more content and should flow nicely."),
|
||||
self.create_test_heading("Section 1.1: Overview", HeadingLevel.H2),
|
||||
self.create_test_paragraph("This is a paragraph under the section. It has even more content that might not fit on the same page."),
|
||||
self.create_test_paragraph("This is another long paragraph that definitely won't fit. " * 20),
|
||||
]
|
||||
|
||||
# Create a page
|
||||
page = Page(size=(600, 400))
|
||||
|
||||
# Fill the page
|
||||
next_index, remainder_blocks = page.fill_with_blocks(blocks)
|
||||
|
||||
# Assertions
|
||||
self.assertIsInstance(next_index, int)
|
||||
self.assertGreaterEqual(next_index, 0)
|
||||
self.assertLessEqual(next_index, len(blocks))
|
||||
self.assertIsInstance(remainder_blocks, list)
|
||||
self.assertGreaterEqual(len(page._children), 0)
|
||||
|
||||
# Try to render the page
|
||||
try:
|
||||
page_image = page.render()
|
||||
self.assertIsNotNone(page_image)
|
||||
self.assertEqual(len(page_image.size), 2) # Should have width and height
|
||||
except Exception as e:
|
||||
self.fail(f"Page rendering failed: {e}")
|
||||
|
||||
def test_multi_page_creation(self):
|
||||
"""Test creating multiple pages from a list of blocks."""
|
||||
# Create many test blocks
|
||||
blocks = []
|
||||
for i in range(5): # Reduced for faster testing
|
||||
blocks.append(self.create_test_heading(f"Chapter {i+1}"))
|
||||
for j in range(2): # Reduced for faster testing
|
||||
long_text = f"This is paragraph {j+1} of chapter {i+1}. " * 10
|
||||
blocks.append(self.create_test_paragraph(long_text))
|
||||
|
||||
self.assertGreater(len(blocks), 0)
|
||||
|
||||
# Create pages until all blocks are processed
|
||||
pages = []
|
||||
remaining_blocks = blocks
|
||||
page_count = 0
|
||||
|
||||
while remaining_blocks and page_count < 10: # Safety limit
|
||||
page = Page(size=(600, 400))
|
||||
next_index, remainder_blocks = page.fill_with_blocks(remaining_blocks)
|
||||
|
||||
if page._children:
|
||||
pages.append(page)
|
||||
page_count += 1
|
||||
|
||||
# Update remaining blocks
|
||||
if remainder_blocks:
|
||||
remaining_blocks = remainder_blocks
|
||||
elif next_index < len(remaining_blocks):
|
||||
remaining_blocks = remaining_blocks[next_index:]
|
||||
else:
|
||||
remaining_blocks = []
|
||||
|
||||
# Safety check to prevent infinite loops
|
||||
if not page._children and remaining_blocks:
|
||||
break
|
||||
|
||||
# Assertions
|
||||
self.assertGreater(len(pages), 0, "Should create at least one page")
|
||||
self.assertLessEqual(page_count, 10, "Should not exceed safety limit")
|
||||
|
||||
# Try to render a few pages
|
||||
rendered_count = 0
|
||||
for page in pages[:2]: # Test first 2 pages
|
||||
try:
|
||||
page_image = page.render()
|
||||
rendered_count += 1
|
||||
self.assertIsNotNone(page_image)
|
||||
except Exception as e:
|
||||
self.fail(f"Page rendering failed: {e}")
|
||||
|
||||
self.assertGreater(rendered_count, 0, "Should render at least one page")
|
||||
|
||||
def test_empty_blocks_list(self):
|
||||
"""Test handling of empty blocks list."""
|
||||
page = Page(size=(600, 400))
|
||||
next_index, remainder_blocks = page.fill_with_blocks([])
|
||||
|
||||
self.assertEqual(next_index, 0)
|
||||
self.assertEqual(len(remainder_blocks), 0)
|
||||
self.assertEqual(len(page._children), 0)
|
||||
|
||||
def test_single_block(self):
|
||||
"""Test handling of single block."""
|
||||
blocks = [self.create_test_paragraph("Single paragraph test.")]
|
||||
page = Page(size=(600, 400))
|
||||
|
||||
next_index, remainder_blocks = page.fill_with_blocks(blocks)
|
||||
|
||||
self.assertEqual(next_index, 1)
|
||||
self.assertEqual(len(remainder_blocks), 0)
|
||||
self.assertGreater(len(page._children), 0)
|
||||
|
||||
def test_pagination_result_properties(self):
|
||||
"""Test PaginationResult object properties."""
|
||||
paragraph = self.create_test_paragraph("Test paragraph for pagination result.")
|
||||
page = Page(size=(400, 200))
|
||||
|
||||
paginator = BlockPaginator()
|
||||
result = paginator.paginate_block(paragraph, page, available_height=100)
|
||||
|
||||
# Test that result has expected properties
|
||||
self.assertTrue(hasattr(result, 'success'))
|
||||
self.assertTrue(hasattr(result, 'height_used'))
|
||||
self.assertTrue(hasattr(result, 'remainder'))
|
||||
self.assertTrue(hasattr(result, 'can_continue'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
0
tests/test_simple_alignment.py
Normal file
0
tests/test_simple_alignment.py
Normal file
232
tests/test_simple_pagination.py
Normal file
232
tests/test_simple_pagination.py
Normal file
@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unit tests for simple pagination logic without EPUB dependencies.
|
||||
Tests basic pagination functionality using the unittest framework.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from pyWebLayout.concrete.page import Page
|
||||
from pyWebLayout.concrete.text import Text
|
||||
from pyWebLayout.style.fonts import Font
|
||||
from pyWebLayout.abstract.block import Paragraph
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
|
||||
|
||||
class TestSimplePagination(unittest.TestCase):
|
||||
"""Test cases for simple pagination functionality."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.font = Font(font_size=16)
|
||||
self.page_size = (700, 550)
|
||||
self.max_page_height = 510 # Leave room for padding
|
||||
|
||||
def create_test_paragraph(self, text_content: str) -> Paragraph:
|
||||
"""Create a test paragraph with the given text."""
|
||||
paragraph = Paragraph()
|
||||
words = text_content.split()
|
||||
|
||||
for word_text in words:
|
||||
word = Word(word_text, self.font)
|
||||
paragraph.add_word(word)
|
||||
|
||||
return paragraph
|
||||
|
||||
def test_single_paragraph_pagination(self):
|
||||
"""Test pagination with a single paragraph."""
|
||||
text = "This is a simple paragraph for testing pagination functionality."
|
||||
paragraph = self.create_test_paragraph(text)
|
||||
|
||||
page = Page(size=self.page_size)
|
||||
|
||||
# Convert block to renderable
|
||||
renderable = page._convert_block_to_renderable(paragraph)
|
||||
self.assertIsNotNone(renderable, "Should convert paragraph to renderable")
|
||||
|
||||
# Add to page
|
||||
page.add_child(renderable)
|
||||
self.assertEqual(len(page._children), 1)
|
||||
|
||||
# Layout should work
|
||||
try:
|
||||
page.layout()
|
||||
except Exception as e:
|
||||
self.fail(f"Layout failed: {e}")
|
||||
|
||||
# Render should work
|
||||
try:
|
||||
rendered_image = page.render()
|
||||
self.assertIsNotNone(rendered_image)
|
||||
self.assertEqual(rendered_image.size, self.page_size)
|
||||
except Exception as e:
|
||||
self.fail(f"Render failed: {e}")
|
||||
|
||||
def test_multiple_paragraphs_same_page(self):
|
||||
"""Test adding multiple small paragraphs to the same page."""
|
||||
paragraphs = [
|
||||
"First short paragraph.",
|
||||
"Second short paragraph.",
|
||||
"Third short paragraph."
|
||||
]
|
||||
|
||||
page = Page(size=self.page_size)
|
||||
|
||||
for i, text in enumerate(paragraphs):
|
||||
paragraph = self.create_test_paragraph(text)
|
||||
renderable = page._convert_block_to_renderable(paragraph)
|
||||
self.assertIsNotNone(renderable, f"Should convert paragraph {i+1}")
|
||||
|
||||
page.add_child(renderable)
|
||||
|
||||
self.assertEqual(len(page._children), len(paragraphs))
|
||||
|
||||
# Layout should work with multiple children
|
||||
try:
|
||||
page.layout()
|
||||
except Exception as e:
|
||||
self.fail(f"Layout with multiple paragraphs failed: {e}")
|
||||
|
||||
# Calculate page height
|
||||
max_bottom = self.calculate_page_height(page)
|
||||
self.assertLessEqual(max_bottom, self.max_page_height, "Page should not exceed height limit")
|
||||
|
||||
def test_page_overflow_detection(self):
|
||||
"""Test detection of page overflow."""
|
||||
# Create a very long paragraph that should cause overflow
|
||||
long_text = " ".join(["This is a very long paragraph with many words."] * 20)
|
||||
paragraph = self.create_test_paragraph(long_text)
|
||||
|
||||
page = Page(size=self.page_size)
|
||||
renderable = page._convert_block_to_renderable(paragraph)
|
||||
page.add_child(renderable)
|
||||
|
||||
try:
|
||||
page.layout()
|
||||
max_bottom = self.calculate_page_height(page)
|
||||
|
||||
# Very long content might exceed page height
|
||||
# This is expected behavior for testing overflow detection
|
||||
self.assertIsInstance(max_bottom, (int, float))
|
||||
|
||||
except Exception as e:
|
||||
# Layout might fail with very long content, which is acceptable
|
||||
self.assertIsInstance(e, Exception)
|
||||
|
||||
def test_page_height_calculation(self):
|
||||
"""Test page height calculation method."""
|
||||
page = Page(size=self.page_size)
|
||||
|
||||
# Empty page should have height 0
|
||||
height = self.calculate_page_height(page)
|
||||
self.assertEqual(height, 0)
|
||||
|
||||
# Add content and check height increases
|
||||
paragraph = self.create_test_paragraph("Test content for height calculation.")
|
||||
renderable = page._convert_block_to_renderable(paragraph)
|
||||
page.add_child(renderable)
|
||||
page.layout()
|
||||
|
||||
height_with_content = self.calculate_page_height(page)
|
||||
self.assertGreater(height_with_content, 0)
|
||||
|
||||
def test_multi_page_scenario(self):
|
||||
"""Test creating multiple pages from content."""
|
||||
# Create test content
|
||||
test_paragraphs = [
|
||||
"This is the first paragraph with some content.",
|
||||
"Here is a second paragraph with different content.",
|
||||
"The third paragraph continues with more text.",
|
||||
"Fourth paragraph here with additional content.",
|
||||
"Fifth paragraph with even more content for testing."
|
||||
]
|
||||
|
||||
pages = []
|
||||
current_page = Page(size=self.page_size)
|
||||
|
||||
for i, text in enumerate(test_paragraphs):
|
||||
paragraph = self.create_test_paragraph(text)
|
||||
renderable = current_page._convert_block_to_renderable(paragraph)
|
||||
|
||||
if renderable:
|
||||
# Store current state for potential rollback
|
||||
children_backup = current_page._children.copy()
|
||||
|
||||
# Add to current page
|
||||
current_page.add_child(renderable)
|
||||
|
||||
try:
|
||||
current_page.layout()
|
||||
max_bottom = self.calculate_page_height(current_page)
|
||||
|
||||
# Check if page is too full
|
||||
if max_bottom > self.max_page_height and len(current_page._children) > 1:
|
||||
# Rollback and start new page
|
||||
current_page._children = children_backup
|
||||
pages.append(current_page)
|
||||
|
||||
# Start new page with current content
|
||||
current_page = Page(size=self.page_size)
|
||||
current_page.add_child(renderable)
|
||||
current_page.layout()
|
||||
|
||||
except Exception:
|
||||
# Layout failed, rollback
|
||||
current_page._children = children_backup
|
||||
|
||||
# Add final page if it has content
|
||||
if current_page._children:
|
||||
pages.append(current_page)
|
||||
|
||||
# Assertions
|
||||
self.assertGreater(len(pages), 0, "Should create at least one page")
|
||||
|
||||
# Test rendering all pages
|
||||
for i, page in enumerate(pages):
|
||||
with self.subTest(page=i+1):
|
||||
self.assertGreater(len(page._children), 0, f"Page {i+1} should have content")
|
||||
|
||||
try:
|
||||
rendered_image = page.render()
|
||||
self.assertIsNotNone(rendered_image)
|
||||
self.assertEqual(rendered_image.size, self.page_size)
|
||||
except Exception as e:
|
||||
self.fail(f"Page {i+1} render failed: {e}")
|
||||
|
||||
def test_empty_paragraph_handling(self):
|
||||
"""Test handling of empty paragraphs."""
|
||||
empty_paragraph = self.create_test_paragraph("")
|
||||
page = Page(size=self.page_size)
|
||||
|
||||
# Empty paragraph should still be convertible
|
||||
renderable = page._convert_block_to_renderable(empty_paragraph)
|
||||
|
||||
if renderable: # Some implementations might return None for empty content
|
||||
page.add_child(renderable)
|
||||
try:
|
||||
page.layout()
|
||||
rendered_image = page.render()
|
||||
self.assertIsNotNone(rendered_image)
|
||||
except Exception as e:
|
||||
self.fail(f"Empty paragraph handling failed: {e}")
|
||||
|
||||
def test_conversion_error_handling(self):
|
||||
"""Test handling of blocks that can't be converted."""
|
||||
paragraph = self.create_test_paragraph("Test content")
|
||||
page = Page(size=self.page_size)
|
||||
|
||||
# This should normally work
|
||||
renderable = page._convert_block_to_renderable(paragraph)
|
||||
self.assertIsNotNone(renderable, "Normal paragraph should convert successfully")
|
||||
|
||||
def calculate_page_height(self, page):
|
||||
"""Helper method to calculate current page height."""
|
||||
max_bottom = 0
|
||||
for child in page._children:
|
||||
if hasattr(child, '_origin') and hasattr(child, '_size'):
|
||||
child_bottom = child._origin[1] + child._size[1]
|
||||
max_bottom = max(max_bottom, child_bottom)
|
||||
return max_bottom
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple verification script to demonstrate that the line splitting bug is fixed.
|
||||
"""
|
||||
|
||||
from unittest.mock import patch, Mock
|
||||
from pyWebLayout.concrete.text import Line
|
||||
from pyWebLayout.style import Font
|
||||
|
||||
def test_fix():
|
||||
"""Test that the line splitting fix works correctly"""
|
||||
print("Testing line splitting fix...")
|
||||
|
||||
font = Font(font_path=None, font_size=12, colour=(0, 0, 0))
|
||||
|
||||
# Test case 1: Multi-part hyphenation
|
||||
print("\n1. Testing multi-part hyphenation overflow:")
|
||||
|
||||
with patch('pyWebLayout.abstract.inline.pyphen') as mock_pyphen_module:
|
||||
mock_dic = Mock()
|
||||
mock_pyphen_module.Pyphen.return_value = mock_dic
|
||||
mock_dic.inserted.return_value = "super-cali-fragi-listic-expiali-docious"
|
||||
|
||||
line = Line((5, 10), (0, 0), (100, 20), font)
|
||||
overflow = line.add_word("supercalifragilisticexpialidocious")
|
||||
|
||||
first_part = line.renderable_words[0].word.text if line.renderable_words else "None"
|
||||
|
||||
print(f" Original word: 'supercalifragilisticexpialidocious'")
|
||||
print(f" Hyphenated to: 'super-cali-fragi-listic-expiali-docious'")
|
||||
print(f" First part added to line: '{first_part}'")
|
||||
print(f" Overflow returned: '{overflow}'")
|
||||
|
||||
# Verify the fix
|
||||
if overflow == "cali-":
|
||||
print(" ✓ FIXED: Overflow returns only next part")
|
||||
else:
|
||||
print(" ✗ BROKEN: Overflow returns multiple parts joined")
|
||||
return False
|
||||
|
||||
# Test case 2: Simple two-part hyphenation
|
||||
print("\n2. Testing simple two-part hyphenation:")
|
||||
|
||||
with patch('pyWebLayout.abstract.inline.pyphen') as mock_pyphen_module:
|
||||
mock_dic = Mock()
|
||||
mock_pyphen_module.Pyphen.return_value = mock_dic
|
||||
mock_dic.inserted.return_value = "very-long"
|
||||
|
||||
line = Line((5, 10), (0, 0), (40, 20), font)
|
||||
overflow = line.add_word("verylong")
|
||||
|
||||
first_part = line.renderable_words[0].word.text if line.renderable_words else "None"
|
||||
|
||||
print(f" Original word: 'verylong'")
|
||||
print(f" Hyphenated to: 'very-long'")
|
||||
print(f" First part added to line: '{first_part}'")
|
||||
print(f" Overflow returned: '{overflow}'")
|
||||
|
||||
# Verify the fix
|
||||
if overflow == "long":
|
||||
print(" ✓ FIXED: Overflow returns only next part")
|
||||
else:
|
||||
print(" ✗ BROKEN: Overflow behavior incorrect")
|
||||
return False
|
||||
|
||||
# Test case 3: No overflow case
|
||||
print("\n3. Testing word that fits completely:")
|
||||
|
||||
line = Line((5, 10), (0, 0), (200, 20), font)
|
||||
overflow = line.add_word("short")
|
||||
|
||||
first_part = line.renderable_words[0].word.text if line.renderable_words else "None"
|
||||
|
||||
print(f" Word: 'short'")
|
||||
print(f" Added to line: '{first_part}'")
|
||||
print(f" Overflow: {overflow}")
|
||||
|
||||
if overflow is None:
|
||||
print(" ✓ CORRECT: No overflow for word that fits")
|
||||
else:
|
||||
print(" ✗ BROKEN: Unexpected overflow")
|
||||
return False
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("ALL TESTS PASSED - LINE SPLITTING BUG IS FIXED!")
|
||||
print("="*50)
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_fix()
|
||||
Loading…
x
Reference in New Issue
Block a user