Compare commits

..

No commits in common. "cc34c79495145c340d48107f577405f9a7ab05e2" and "2b14517344ce6834873e8dc6261a1ed6c8e8437f" have entirely different histories.

14 changed files with 3 additions and 1933 deletions

View File

@ -119,25 +119,6 @@ The library supports various page layouts and configurations:
<em>Buttons, forms, and callback binding</em> <em>Buttons, forms, and callback binding</em>
</td> </td>
</tr> </tr>
<tr>
<td align="center" width="50%">
<b>🆕 Pagination & PageBreak</b><br>
<img src="docs/images/example_08_pagination_explicit.png" width="300" alt="Pagination"><br>
<em>Multi-page documents with explicit and automatic breaks</em>
</td>
<td align="center" width="50%">
<b>🆕 Link Navigation</b><br>
<img src="docs/images/example_09_link_navigation.png" width="300" alt="Links"><br>
<em>All 4 link types: Internal, External, API, Function</em>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<b>🆕 Comprehensive Forms</b><br>
<img src="docs/images/example_10_forms.png" width="300" alt="Forms"><br>
<em>All 14 form field types with validation</em>
</td>
</tr>
</table> </table>
## Examples ## Examples
@ -152,20 +133,12 @@ The `examples/` directory contains working demonstrations:
- **[05_html_table_with_images.py](examples/05_html_table_with_images.py)** - Tables with embedded images - **[05_html_table_with_images.py](examples/05_html_table_with_images.py)** - Tables with embedded images
- **[06_functional_elements_demo.py](examples/06_functional_elements_demo.py)** - Interactive buttons and forms with callbacks - **[06_functional_elements_demo.py](examples/06_functional_elements_demo.py)** - Interactive buttons and forms with callbacks
### 🆕 Advanced Features (NEW)
- **[08_pagination_demo.py](examples/08_pagination_demo.py)** - Multi-page documents with PageBreak ([11 tests](tests/examples/test_08_pagination_demo.py))
- **[09_link_navigation_demo.py](examples/09_link_navigation_demo.py)** - All link types and navigation ([10 tests](tests/examples/test_09_link_navigation_demo.py))
- **[10_forms_demo.py](examples/10_forms_demo.py)** - All 14 form field types ([9 tests](tests/examples/test_10_forms_demo.py))
Run any example: Run any example:
```bash ```bash
cd examples cd examples
python 01_simple_page_rendering.py python 01_simple_page_rendering.py
python 08_pagination_demo.py # NEW: Multi-page documents
``` ```
**All new examples include comprehensive test coverage!** See [NEW_EXAMPLES_AND_TESTS_SUMMARY.md](docs/NEW_EXAMPLES_AND_TESTS_SUMMARY.md) for details.
See **[examples/README.md](examples/README.md)** for detailed documentation. See **[examples/README.md](examples/README.md)** for detailed documentation.
## Documentation ## Documentation

View File

@ -1,6 +1,6 @@
# pyWebLayout Visual Documentation # EbookReader Animated Demonstrations
This directory contains visual documentation for pyWebLayout, including animated GIF demonstrations of the EbookReader functionality and static example outputs showcasing various features. This directory contains animated GIF demonstrations of the pyWebLayout EbookReader functionality.
## Generated GIFs ## Generated GIFs
@ -85,129 +85,16 @@ You can modify `generate_ereader_gifs.py` to adjust:
| `ereader_chapter_navigation.gif` | ~290 KB | 11 | 1000ms | | `ereader_chapter_navigation.gif` | ~290 KB | 11 | 1000ms |
| `ereader_bookmarks.gif` | ~500 KB | 17 | 600ms | | `ereader_bookmarks.gif` | ~500 KB | 17 | 600ms |
---
## Example Outputs
Static PNG images generated by the example scripts, demonstrating various pyWebLayout features.
### Example 01: Simple Page Rendering
**File:** `example_01_page_rendering.png`
**Source:** [examples/01_simple_page_rendering.py](../../examples/01_simple_page_rendering.py)
**Demonstrates:** Page styles, borders, padding, background colors
### Example 06: Functional Elements
**File:** `example_06_functional_elements.png`
**Source:** [examples/06_functional_elements_demo.py](../../examples/06_functional_elements_demo.py)
**Demonstrates:** Buttons, form fields, interactive elements
### Example 08: Pagination (NEW)
**Files:**
- `example_08_pagination_explicit.png` (109 KB) - 5 pages with explicit PageBreaks
- `example_08_pagination_auto.png` (87 KB) - 2 pages with automatic pagination
**Source:** [examples/08_pagination_demo.py](../../examples/08_pagination_demo.py)
**Test:** [tests/examples/test_08_pagination_demo.py](../../tests/examples/test_08_pagination_demo.py)
**Demonstrates:**
- Using `PageBreak` to force content onto new pages
- Multi-page document layout with explicit breaks
- Automatic pagination when content overflows
- Page numbering functionality
- Document flow control
**Coverage:** ✅ Fills critical gap - PageBreak had NO examples before this
### Example 09: Link Navigation (NEW)
**File:** `example_09_link_navigation.png` (60 KB)
**Source:** [examples/09_link_navigation_demo.py](../../examples/09_link_navigation_demo.py)
**Test:** [tests/examples/test_09_link_navigation_demo.py](../../tests/examples/test_09_link_navigation_demo.py)
**Demonstrates:**
- **Internal links** - Document navigation (`#section1`, `#section2`)
- **External links** - Web URLs (`https://example.com`)
- **API links** - API endpoints (`/api/settings`, `/api/save`)
- **Function links** - Direct function calls (`calculate()`, `process()`)
- Link styling (underlined, color-coded by type)
- Link callbacks and interactivity
**Coverage:** ✅ Comprehensive - All 4 LinkType variations demonstrated
### Example 10: Comprehensive Forms (NEW)
**File:** `example_10_forms.png` (31 KB)
**Source:** [examples/10_forms_demo.py](../../examples/10_forms_demo.py)
**Test:** [tests/examples/test_10_forms_demo.py](../../tests/examples/test_10_forms_demo.py)
**Demonstrates all 14 FormFieldType variations:**
**Text-Based Fields:**
- `TEXT` - Standard text input
- `EMAIL` - Email validation field
- `PASSWORD` - Password masking
- `URL` - URL validation
- `TEXTAREA` - Multi-line text
**Number/Date/Time Fields:**
- `NUMBER` - Numeric input
- `DATE` - Date picker
- `TIME` - Time selector
- `RANGE` - Slider control
- `COLOR` - Color picker
**Selection Fields:**
- `CHECKBOX` - Boolean selection
- `RADIO` - Single choice from options
- `SELECT` - Dropdown menu
- `HIDDEN` - Hidden form data
**Coverage:** ✅ Complete - All 14 field types across 4 practical examples
---
## Generating New Examples
### Run Individual Examples
```bash
# Navigate to project root
cd /path/to/pyWebLayout
# Run specific example
python examples/08_pagination_demo.py
python examples/09_link_navigation_demo.py
python examples/10_forms_demo.py
```
### Run All Example Tests
```bash
# Run all example tests with pytest
python -m pytest tests/examples/ -v
# Run specific test file
python -m pytest tests/examples/test_08_pagination_demo.py -v
```
All new examples (08, 09, 10) include:
- ✅ Comprehensive documentation
- ✅ Full test coverage (30 tests total)
- ✅ Visual output verification
- ✅ Working code examples
See [NEW_EXAMPLES_AND_TESTS_SUMMARY.md](../NEW_EXAMPLES_AND_TESTS_SUMMARY.md) for detailed information.
---
## Usage in Documentation ## Usage in Documentation
These visual assets are used throughout the pyWebLayout documentation to showcase capabilities. These GIFs are embedded in the main [README.md](../../README.md) to showcase the EbookReader's capabilities to potential users.
To embed in Markdown: To embed in Markdown:
```markdown ```markdown
![Page Navigation](docs/images/ereader_page_navigation.gif) ![Page Navigation](docs/images/ereader_page_navigation.gif)
![Pagination Example](docs/images/example_08_pagination_explicit.png)
``` ```
To embed in HTML with size control: To embed in HTML with size control:
```html ```html
<img src="docs/images/ereader_page_navigation.gif" width="300" alt="Page Navigation"> <img src="docs/images/ereader_page_navigation.gif" width="300" alt="Page Navigation">
<img src="docs/images/example_08_pagination_explicit.png" width="400" alt="Pagination">
``` ```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,367 +0,0 @@
#!/usr/bin/env python3
"""
Pagination Example with PageBreak
This example demonstrates:
- Using PageBreak to force content onto new pages
- Multi-page document layout with automatic page creation
- Different content types across multiple pages
- Page numbering and document flow
- Combining text, images, and tables across pages
This shows how to create multi-page documents with explicit page breaks.
"""
import sys
from pathlib import Path
from PIL import Image, ImageDraw
# Add pyWebLayout to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.style.fonts import Font
from pyWebLayout.abstract.inline import Word
from pyWebLayout.abstract.block import Paragraph, PageBreak, Image as AbstractImage
from pyWebLayout.layout.document_layouter import DocumentLayouter
def create_sample_paragraph(text: str, font_size: int = 14) -> Paragraph:
"""Create a paragraph from plain text."""
font = Font(font_size=font_size, colour=(50, 50, 50))
paragraph = Paragraph(style=font)
for word in text.split():
paragraph.add_word(Word(word, font))
return paragraph
def create_title_paragraph(text: str) -> Paragraph:
"""Create a title paragraph with larger font."""
font = Font(font_size=24, colour=(0, 0, 100), weight='bold')
paragraph = Paragraph(style=font)
for word in text.split():
paragraph.add_word(Word(word, font))
return paragraph
def create_heading_paragraph(text: str) -> Paragraph:
"""Create a heading paragraph."""
font = Font(font_size=18, colour=(50, 50, 100), weight='bold')
paragraph = Paragraph(style=font)
for word in text.split():
paragraph.add_word(Word(word, font))
return paragraph
def create_placeholder_image(width: int, height: int, label: str) -> AbstractImage:
"""Create a placeholder image for demonstration."""
img = Image.new('RGB', (width, height), (200, 220, 240))
draw = ImageDraw.Draw(img)
# Draw border
draw.rectangle([0, 0, width-1, height-1], outline=(100, 120, 140), width=2)
# Add label
text_bbox = draw.textbbox((0, 0), label)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
text_x = (width - text_width) // 2
text_y = (height - text_height) // 2
draw.text((text_x, text_y), label, fill=(80, 80, 120))
return AbstractImage(source=img)
def create_example_document_with_pagebreaks():
"""
Example: Multi-page document with explicit page breaks.
This demonstrates how PageBreak forces content onto new pages.
"""
print("\n Creating multi-page document with PageBreaks...")
# Define common page style
page_style = PageStyle(
border_width=2,
border_color=(100, 100, 150),
padding=(30, 40, 30, 40),
background_color=(255, 255, 255),
line_spacing=6
)
# Create document content with page breaks
content = [
# Page 1: Title and Introduction
create_title_paragraph("Multi-Page Document Example"),
create_sample_paragraph(
"This document demonstrates how to use PageBreak elements to control "
"document pagination. Each PageBreak forces subsequent content to start "
"on a new page, allowing you to structure multi-page documents precisely."
),
create_sample_paragraph(
"Page breaks are particularly useful for creating chapters, sections, or "
"ensuring that important content starts at the top of a fresh page rather "
"than being split across page boundaries."
),
# Force page break - next content will be on page 2
PageBreak(),
# Page 2: First Section
create_heading_paragraph("Section 1: Text Content"),
create_sample_paragraph(
"This is the second page of our document. It starts with a clean break "
"from the previous page, ensuring the section heading appears at the top."
),
create_sample_paragraph(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
"commodo consequat."
),
create_sample_paragraph(
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum "
"dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non "
"proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
),
# Another page break
PageBreak(),
# Page 3: Images
create_heading_paragraph("Section 2: Visual Content"),
create_sample_paragraph(
"This page contains image content, demonstrating that page breaks work "
"correctly with different content types."
),
create_placeholder_image(300, 200, "Figure 1: Sample Image"),
create_sample_paragraph("The image above is placed on this dedicated page."),
# Final page break
PageBreak(),
# Page 4: Conclusion
create_heading_paragraph("Conclusion"),
create_sample_paragraph(
"This final page demonstrates that you can create complex multi-page "
"documents by strategically placing PageBreak elements in your content."
),
create_sample_paragraph(
"Key benefits of using PageBreak: 1) Control where pages start, "
"2) Prevent awkward content splits, 3) Create professional-looking "
"documents with proper sectioning, 4) Ensure important content gets "
"visual prominence at page tops."
),
create_sample_paragraph(
"Thank you for reviewing this pagination example. Try experimenting "
"with PageBreak placement to create your own multi-page documents!"
),
]
# Layout the document across multiple pages
pages = []
current_page = Page(size=(600, 800), style=page_style)
layouter = DocumentLayouter(current_page)
for element in content:
if isinstance(element, PageBreak):
# Save current page and create a new one
pages.append(current_page)
current_page = Page(size=(600, 800), style=page_style)
layouter = DocumentLayouter(current_page)
elif isinstance(element, Paragraph):
success, _, _ = layouter.layout_paragraph(element)
if not success:
# Page is full, create new page and retry
pages.append(current_page)
current_page = Page(size=(600, 800), style=page_style)
layouter = DocumentLayouter(current_page)
success, _, _ = layouter.layout_paragraph(element)
if not success:
print(" WARNING: Content too large for page")
elif isinstance(element, AbstractImage):
success = layouter.layout_image(element)
if not success:
# Image doesn't fit, try on new page
pages.append(current_page)
current_page = Page(size=(600, 800), style=page_style)
layouter = DocumentLayouter(current_page)
success = layouter.layout_image(element)
if not success:
print(" WARNING: Image too large for page")
# Add the final page
pages.append(current_page)
print(f" Created {len(pages)} pages")
return pages
def create_auto_pagination_example():
"""
Example: Document that automatically flows to multiple pages.
This shows the difference between automatic pagination (when content
doesn't fit) vs explicit PageBreak usage.
"""
print("\n Creating auto-paginated document (no explicit breaks)...")
page_style = PageStyle(
border_width=1,
border_color=(150, 150, 150),
padding=(20, 30, 20, 30),
background_color=(250, 250, 250),
line_spacing=5
)
# Create lots of content that will naturally overflow
content = [
create_heading_paragraph("Auto-Pagination Example"),
create_sample_paragraph(
"This document does NOT use PageBreak. Instead, it demonstrates how "
"content automatically flows to new pages when the current page is full."
),
]
# Add many paragraphs to force automatic page breaks
for i in range(1, 11):
content.append(
create_sample_paragraph(
f"Paragraph {i}: This is automatically laid out content. "
f"When this paragraph doesn't fit on the current page, the layouter "
f"will create a new page automatically. This is different from using "
f"PageBreak which forces a new page regardless of available space. "
f"Auto-pagination is useful for flowing content naturally."
)
)
# Layout across pages
pages = []
current_page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(current_page)
for element in content:
if isinstance(element, Paragraph):
success, _, _ = layouter.layout_paragraph(element)
if not success:
# Auto page break - content didn't fit
pages.append(current_page)
current_page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(current_page)
layouter.layout_paragraph(element)
pages.append(current_page)
print(f" Auto-created {len(pages)} pages")
return pages
def add_page_numbers(pages, start_number: int = 1):
"""Add page numbers to rendered pages."""
numbered_pages = []
font = Font(font_size=10, colour=(100, 100, 100))
for i, page in enumerate(pages, start=start_number):
# Render the page
img = page.render()
draw = ImageDraw.Draw(img)
# Add page number at bottom center
page_text = f"Page {i}"
bbox = draw.textbbox((0, 0), page_text)
text_width = bbox[2] - bbox[0]
x = (img.size[0] - text_width) // 2
y = img.size[1] - 20
draw.text((x, y), page_text, fill=(100, 100, 100))
numbered_pages.append(img)
return numbered_pages
def combine_pages_vertically(pages, title: str = ""):
"""Combine multiple pages into a vertical strip."""
if not pages:
return None
padding = 20
title_height = 40 if title else 0
# Calculate dimensions
page_width = pages[0].size[0]
page_height = pages[0].size[1]
total_width = page_width + 2 * padding
total_height = len(pages) * (page_height + padding) + padding + title_height
# Create combined image
combined = Image.new('RGB', (total_width, total_height), (240, 240, 240))
draw = ImageDraw.Draw(combined)
# Draw title if provided
if title:
from PIL import ImageFont
try:
title_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16
)
except:
title_font = ImageFont.load_default()
bbox = draw.textbbox((0, 0), title, font=title_font)
text_width = bbox[2] - bbox[0]
title_x = (total_width - text_width) // 2
draw.text((title_x, 10), title, fill=(50, 50, 50), font=title_font)
# Place pages vertically
y_offset = title_height + padding
for page_img in pages:
combined.paste(page_img, (padding, y_offset))
y_offset += page_height + padding
return combined
def main():
"""Demonstrate pagination with PageBreak."""
print("Pagination Example with PageBreak")
print("=" * 50)
# Example 1: Explicit page breaks
pages1 = create_example_document_with_pagebreaks()
rendered_pages1 = add_page_numbers(pages1)
combined1 = combine_pages_vertically(
rendered_pages1,
"Example 1: Explicit PageBreak Usage"
)
# Example 2: Auto pagination
pages2 = create_auto_pagination_example()
rendered_pages2 = add_page_numbers(pages2)
combined2 = combine_pages_vertically(
rendered_pages2,
"Example 2: Automatic Pagination"
)
# Save outputs
output_dir = Path("docs/images")
output_dir.mkdir(parents=True, exist_ok=True)
output_path1 = output_dir / "example_08_pagination_explicit.png"
output_path2 = output_dir / "example_08_pagination_auto.png"
combined1.save(output_path1)
combined2.save(output_path2)
print("\n✓ Example completed!")
print(f" Output 1 saved to: {output_path1}")
print(f" - {len(pages1)} pages with explicit PageBreaks")
print(f" Output 2 saved to: {output_path2}")
print(f" - {len(pages2)} pages with auto-pagination")
return combined1, combined2
if __name__ == "__main__":
main()

View File

@ -1,390 +0,0 @@
#!/usr/bin/env python3
"""
Link Navigation Example
This example demonstrates:
- Creating clickable links with LinkedWord
- Different link types (INTERNAL, EXTERNAL, API, FUNCTION)
- Link styling with underlines and colors
- Link callbacks and event handling
- Interactive link states (hover, pressed)
- Organizing linked content in paragraphs
This shows how to create interactive documents with hyperlinks.
"""
import sys
from pathlib import Path
from typing import List
# Add pyWebLayout to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.style.fonts import Font
from pyWebLayout.abstract.inline import Word, LinkedWord
from pyWebLayout.abstract.functional import LinkType
from pyWebLayout.abstract.block import Paragraph
from pyWebLayout.layout.document_layouter import DocumentLayouter
# Track link clicks for demonstration
link_clicks = []
def link_callback(link_id: str):
"""Callback for link clicks"""
def callback():
link_clicks.append(link_id)
print(f" Link clicked: {link_id}")
return callback
def create_paragraph_with_links(
text_parts: List[tuple],
font_size: int = 14) -> Paragraph:
"""
Create a paragraph with mixed text and links.
Args:
text_parts: List of tuples where each is either:
('text', "word1 word2") for normal text
('link', "word", location, link_type, callback_id)
font_size: Base font size
Returns:
Paragraph with words and links
"""
font = Font(font_size=font_size, colour=(50, 50, 50))
paragraph = Paragraph(style=font)
for part in text_parts:
if part[0] == 'text':
# Add normal words
for word_text in part[1].split():
paragraph.add_word(Word(word_text, font))
elif part[0] == 'link':
# Add linked word
word_text, location, link_type, callback_id = part[1:]
callback = link_callback(callback_id)
linked_word = LinkedWord(
text=word_text,
style=font,
location=location,
link_type=link_type,
callback=callback,
title=f"Click to: {location}"
)
paragraph.add_word(linked_word)
return paragraph
def create_example_1_internal_links():
"""Example 1: Internal navigation links within a document."""
print("\n Creating Example 1: Internal links...")
page_style = PageStyle(
border_width=2,
border_color=(150, 150, 200),
padding=(20, 30, 20, 30),
background_color=(255, 255, 255),
line_spacing=6
)
page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(page)
# Title
title_font = Font(font_size=20, colour=(0, 0, 100), weight='bold')
title = Paragraph(style=title_font)
for word in "Internal Navigation Links".split():
title.add_word(Word(word, title_font))
# Content with internal links
intro = create_paragraph_with_links([
('text', "This document demonstrates"),
('link', "internal", "#section1", LinkType.INTERNAL, "goto_section1"),
('text', "navigation links that jump to different parts of the document."),
])
section1 = create_paragraph_with_links([
('text', "Jump to"),
('link', "Section 2", "#section2", LinkType.INTERNAL, "goto_section2"),
('text', "or"),
('link', "Section 3", "#section3", LinkType.INTERNAL, "goto_section3"),
('text', "within this document."),
])
section2 = create_paragraph_with_links([
('text', "You are in Section 2. Return to"),
('link', "top", "#top", LinkType.INTERNAL, "goto_top"),
('text', "or go to"),
('link', "Section 3", "#section3", LinkType.INTERNAL, "goto_section3_from2"),
])
section3 = create_paragraph_with_links([
('text', "This is Section 3. Go back to"),
('link', "Section 1", "#section1", LinkType.INTERNAL, "goto_section1_from3"),
('text', "or"),
('link', "top", "#top", LinkType.INTERNAL, "goto_top_from3"),
])
# Layout content
layouter.layout_paragraph(title)
layouter.layout_paragraph(intro)
layouter.layout_paragraph(section1)
layouter.layout_paragraph(section2)
layouter.layout_paragraph(section3)
return page
def create_example_2_external_links():
"""Example 2: External links to websites."""
print(" Creating Example 2: External links...")
page_style = PageStyle(
border_width=2,
border_color=(150, 200, 150),
padding=(20, 30, 20, 30),
background_color=(255, 255, 255),
line_spacing=6
)
page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(page)
# Title
title_font = Font(font_size=20, colour=(0, 100, 0), weight='bold')
title = Paragraph(style=title_font)
for word in "External Web Links".split():
title.add_word(Word(word, title_font))
# Content with external links
intro = create_paragraph_with_links([
('text', "Click"),
('link', "here", "https://example.com", LinkType.EXTERNAL, "visit_example"),
('text', "to visit an external website."),
])
resources = create_paragraph_with_links([
('text', "Useful resources:"),
('link', "Documentation", "https://docs.example.com", LinkType.EXTERNAL, "visit_docs"),
('text', "and"),
('link', "GitHub", "https://github.com/example", LinkType.EXTERNAL, "visit_github"),
])
more_links = create_paragraph_with_links([
('text', "Learn more at"),
('link', "Wikipedia", "https://wikipedia.org", LinkType.EXTERNAL, "visit_wiki"),
('text', "or check out"),
('link', "Python.org", "https://python.org", LinkType.EXTERNAL, "visit_python"),
])
# Layout content
layouter.layout_paragraph(title)
layouter.layout_paragraph(intro)
layouter.layout_paragraph(resources)
layouter.layout_paragraph(more_links)
return page
def create_example_3_api_links():
"""Example 3: API links that trigger actions."""
print(" Creating Example 3: API links...")
page_style = PageStyle(
border_width=2,
border_color=(200, 150, 150),
padding=(20, 30, 20, 30),
background_color=(255, 255, 255),
line_spacing=6
)
page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(page)
# Title
title_font = Font(font_size=20, colour=(150, 0, 0), weight='bold')
title = Paragraph(style=title_font)
for word in "API Action Links".split():
title.add_word(Word(word, title_font))
# Content with API links
settings = create_paragraph_with_links([
('text', "Click"),
('link', "Settings", "/api/settings", LinkType.API, "open_settings"),
('text', "to configure the application."),
])
actions = create_paragraph_with_links([
('text', "Actions:"),
('link', "Save", "/api/save", LinkType.API, "save_action"),
('text', "or"),
('link', "Export", "/api/export", LinkType.API, "export_action"),
('text', "your data."),
])
management = create_paragraph_with_links([
('text', "Manage:"),
('link', "Users", "/api/users", LinkType.API, "manage_users"),
('text', "or"),
('link', "Permissions", "/api/permissions", LinkType.API, "manage_perms"),
])
# Layout content
layouter.layout_paragraph(title)
layouter.layout_paragraph(settings)
layouter.layout_paragraph(actions)
layouter.layout_paragraph(management)
return page
def create_example_4_function_links():
"""Example 4: Function links that execute code."""
print(" Creating Example 4: Function links...")
page_style = PageStyle(
border_width=2,
border_color=(150, 200, 200),
padding=(20, 30, 20, 30),
background_color=(255, 255, 255),
line_spacing=6
)
page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(page)
# Title
title_font = Font(font_size=20, colour=(0, 120, 120), weight='bold')
title = Paragraph(style=title_font)
for word in "Function Execution Links".split():
title.add_word(Word(word, title_font))
# Content with function links
intro = create_paragraph_with_links([
('text', "These links execute"),
('link', "functions", "calculate()", LinkType.FUNCTION, "exec_calculate"),
('text', "directly in the application."),
])
calculations = create_paragraph_with_links([
('text', "Run:"),
('link', "analyze()", "analyze()", LinkType.FUNCTION, "exec_analyze"),
('text', "or"),
('link', "process()", "process()", LinkType.FUNCTION, "exec_process"),
])
utilities = create_paragraph_with_links([
('text', "Utilities:"),
('link', "validate()", "validate()", LinkType.FUNCTION, "exec_validate"),
('text', "and"),
('link', "cleanup()", "cleanup()", LinkType.FUNCTION, "exec_cleanup"),
])
# Layout content
layouter.layout_paragraph(title)
layouter.layout_paragraph(intro)
layouter.layout_paragraph(calculations)
layouter.layout_paragraph(utilities)
return page
def combine_pages_into_grid(pages, title):
"""Combine multiple pages into a 2x2 grid."""
from PIL import Image, ImageDraw, ImageFont
print("\n Combining pages into grid...")
# Render all pages
images = [page.render() for page in pages]
# Grid layout
padding = 20
title_height = 40
cols = 2
rows = 2
# Calculate dimensions
img_width = images[0].size[0]
img_height = images[0].size[1]
total_width = cols * img_width + (cols + 1) * padding
total_height = rows * img_height + (rows + 1) * padding + title_height
# Create combined image
combined = Image.new('RGB', (total_width, total_height), (240, 240, 240))
draw = ImageDraw.Draw(combined)
# Draw title
try:
title_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18
)
except:
title_font = ImageFont.load_default()
# Center the title
bbox = draw.textbbox((0, 0), title, font=title_font)
text_width = bbox[2] - bbox[0]
title_x = (total_width - text_width) // 2
draw.text((title_x, 10), title, fill=(50, 50, 50), font=title_font)
# Place pages in grid
y_offset = title_height + padding
for row in range(rows):
x_offset = padding
for col in range(cols):
idx = row * cols + col
if idx < len(images):
combined.paste(images[idx], (x_offset, y_offset))
x_offset += img_width + padding
y_offset += img_height + padding
return combined
def main():
"""Demonstrate link navigation across different link types."""
global link_clicks
link_clicks = []
print("Link Navigation Example")
print("=" * 50)
# Create examples for each link type
pages = [
create_example_1_internal_links(),
create_example_2_external_links(),
create_example_3_api_links(),
create_example_4_function_links()
]
# Combine into demonstration image
combined_image = combine_pages_into_grid(
pages,
"Link Types: Internal | External | API | Function"
)
# Save output
output_dir = Path("docs/images")
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / "example_09_link_navigation.png"
combined_image.save(output_path)
print("\n✓ Example completed!")
print(f" Output saved to: {output_path}")
print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels")
print(f" Created {len(pages)} link type examples")
print(f" Total links created: {len(link_clicks)} callbacks registered")
return combined_image, link_clicks
if __name__ == "__main__":
main()

View File

@ -1,374 +0,0 @@
#!/usr/bin/env python3
"""
Comprehensive Forms Example
This example demonstrates:
- All FormFieldType variations (TEXT, PASSWORD, EMAIL, etc.)
- Form layout with multiple fields
- Field labels and validation
- Form submission callbacks
- Organizing forms on pages
This shows how to create interactive forms with all available field types.
"""
import sys
from pathlib import Path
# Add pyWebLayout to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.style.fonts import Font
from pyWebLayout.abstract.functional import Form, FormField, FormFieldType
from pyWebLayout.layout.document_layouter import DocumentLayouter
from PIL import Image, ImageDraw
# Track form submissions
form_submissions = []
def form_submit_callback(form_id: str):
"""Callback for form submissions"""
def callback(data):
form_submissions.append((form_id, data))
print(f" Form submitted: {form_id} with data: {data}")
return callback
def create_example_1_text_fields():
"""Example 1: Text input fields"""
print("\n Creating Example 1: Text input fields...")
page_style = PageStyle(
border_width=2,
border_color=(150, 150, 200),
padding=(20, 30, 20, 30),
background_color=(255, 255, 255)
)
page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(page)
# Create form with text fields
form = Form(form_id="text_form", html_id="text_form", callback=form_submit_callback("text_form"))
# Add various text-based fields
form.add_field(FormField(
name="username",
label="Username",
field_type=FormFieldType.TEXT,
required=True
))
form.add_field(FormField(
name="email",
label="Email Address",
field_type=FormFieldType.EMAIL,
required=True
))
form.add_field(FormField(
name="password",
label="Password",
field_type=FormFieldType.PASSWORD,
required=True
))
form.add_field(FormField(
name="website",
label="Website URL",
field_type=FormFieldType.URL,
required=False
))
form.add_field(FormField(
name="bio",
label="Biography",
field_type=FormFieldType.TEXTAREA,
required=False
))
# Layout the form
font = Font(font_size=12, colour=(50, 50, 50))
success, field_ids = layouter.layout_form(form, font=font)
print(f" Laid out {len(field_ids)} text fields")
return page
def create_example_2_number_fields():
"""Example 2: Number and date/time fields"""
print(" Creating Example 2: Number and date/time fields...")
page_style = PageStyle(
border_width=2,
border_color=(150, 200, 150),
padding=(20, 30, 20, 30),
background_color=(255, 255, 255)
)
page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(page)
# Create form with number/date fields
form = Form(form_id="number_form", html_id="number_form", callback=form_submit_callback("number_form"))
form.add_field(FormField(
name="age",
label="Age",
field_type=FormFieldType.NUMBER,
required=True
))
form.add_field(FormField(
name="birth_date",
label="Birth Date",
field_type=FormFieldType.DATE,
required=True
))
form.add_field(FormField(
name="appointment",
label="Appointment Time",
field_type=FormFieldType.TIME,
required=False
))
form.add_field(FormField(
name="rating",
label="Rating (1-10)",
field_type=FormFieldType.RANGE,
required=False
))
form.add_field(FormField(
name="color",
label="Favorite Color",
field_type=FormFieldType.COLOR,
required=False
))
# Layout the form
font = Font(font_size=12, colour=(50, 50, 50))
success, field_ids = layouter.layout_form(form, font=font)
print(f" Laid out {len(field_ids)} number/date fields")
return page
def create_example_3_selection_fields():
"""Example 3: Checkbox, radio, and select fields"""
print(" Creating Example 3: Selection fields...")
page_style = PageStyle(
border_width=2,
border_color=(200, 150, 150),
padding=(20, 30, 20, 30),
background_color=(255, 255, 255)
)
page = Page(size=(500, 600), style=page_style)
layouter = DocumentLayouter(page)
# Create form with selection fields
form = Form(form_id="selection_form", html_id="selection_form", callback=form_submit_callback("selection_form"))
form.add_field(FormField(
name="newsletter",
label="Subscribe to Newsletter",
field_type=FormFieldType.CHECKBOX,
required=False
))
form.add_field(FormField(
name="terms",
label="Accept Terms and Conditions",
field_type=FormFieldType.CHECKBOX,
required=True
))
form.add_field(FormField(
name="gender",
label="Gender",
field_type=FormFieldType.RADIO,
required=False
))
form.add_field(FormField(
name="country",
label="Country",
field_type=FormFieldType.SELECT,
required=True
))
form.add_field(FormField(
name="hidden_token",
label="", # Hidden fields don't display labels
field_type=FormFieldType.HIDDEN,
required=False
))
# Layout the form
font = Font(font_size=12, colour=(50, 50, 50))
success, field_ids = layouter.layout_form(form, font=font)
print(f" Laid out {len(field_ids)} selection fields")
return page
def create_example_4_complete_form():
"""Example 4: Complete registration form with mixed field types"""
print(" Creating Example 4: Complete registration form...")
page_style = PageStyle(
border_width=2,
border_color=(150, 200, 200),
padding=(20, 30, 20, 30),
background_color=(255, 255, 255)
)
page = Page(size=(500, 700), style=page_style)
layouter = DocumentLayouter(page)
# Create comprehensive registration form
form = Form(form_id="registration_form", html_id="registration_form", callback=form_submit_callback("registration"))
# Personal information
form.add_field(FormField(
name="full_name",
label="Full Name",
field_type=FormFieldType.TEXT,
required=True
))
form.add_field(FormField(
name="email",
label="Email",
field_type=FormFieldType.EMAIL,
required=True
))
form.add_field(FormField(
name="password",
label="Password",
field_type=FormFieldType.PASSWORD,
required=True
))
form.add_field(FormField(
name="age",
label="Age",
field_type=FormFieldType.NUMBER,
required=True
))
# Preferences
form.add_field(FormField(
name="notifications",
label="Enable Notifications",
field_type=FormFieldType.CHECKBOX,
required=False
))
# Layout the form
font = Font(font_size=12, colour=(50, 50, 50))
success, field_ids = layouter.layout_form(form, font=font, field_spacing=15)
print(f" Laid out complete form with {len(field_ids)} fields")
return page
def combine_pages_into_grid(pages, title):
"""Combine multiple pages into a 2x2 grid."""
print("\n Combining pages into grid...")
# Render all pages
images = [page.render() for page in pages]
# Grid layout
padding = 20
title_height = 40
cols = 2
rows = 2
# Calculate dimensions
img_width = images[0].size[0]
img_height = images[0].size[1]
total_width = cols * img_width + (cols + 1) * padding
total_height = rows * img_height + (rows + 1) * padding + title_height
# Create combined image
combined = Image.new('RGB', (total_width, total_height), (240, 240, 240))
draw = ImageDraw.Draw(combined)
# Draw title
from PIL import ImageFont
try:
title_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18
)
except:
title_font = ImageFont.load_default()
bbox = draw.textbbox((0, 0), title, font=title_font)
text_width = bbox[2] - bbox[0]
title_x = (total_width - text_width) // 2
draw.text((title_x, 10), title, fill=(50, 50, 50), font=title_font)
# Place pages in grid
y_offset = title_height + padding
for row in range(rows):
x_offset = padding
for col in range(cols):
idx = row * cols + col
if idx < len(images):
combined.paste(images[idx], (x_offset, y_offset))
x_offset += img_width + padding
y_offset += img_height + padding
return combined
def main():
"""Demonstrate comprehensive form field types."""
global form_submissions
form_submissions = []
print("Comprehensive Forms Example")
print("=" * 50)
# Create examples for different form types
pages = [
create_example_1_text_fields(),
create_example_2_number_fields(),
create_example_3_selection_fields(),
create_example_4_complete_form()
]
# Combine into demonstration image
combined_image = combine_pages_into_grid(
pages,
"Form Field Types: Text | Numbers | Selection | Complete"
)
# Save output
output_dir = Path("docs/images")
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / "example_10_forms.png"
combined_image.save(output_path)
print("\n✓ Example completed!")
print(f" Output saved to: {output_path}")
print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels")
print(f" Created {len(pages)} form examples")
print(f" Total form callbacks registered: {len(form_submissions)}")
return combined_image, form_submissions
if __name__ == "__main__":
main()

View File

@ -101,81 +101,6 @@ Demonstrates:
![Functional Elements Example](../docs/images/example_06_functional_elements.png) ![Functional Elements Example](../docs/images/example_06_functional_elements.png)
---
## 🆕 New Examples (2024-11)
These examples address critical coverage gaps and demonstrate advanced features:
### 08. Pagination with PageBreak (NEW) ✅
**`08_pagination_demo.py`** - Multi-page documents with explicit and automatic pagination
```bash
python 08_pagination_demo.py
```
**Test Coverage:** [tests/examples/test_08_pagination_demo.py](../tests/examples/test_08_pagination_demo.py) - 11 tests
Demonstrates:
- Using `PageBreak` to force content onto new pages
- Multi-page document layout with explicit breaks
- Automatic pagination when content overflows
- Page numbering functionality
- Document flow control
- Combining pages into vertical strips
**Coverage Impact:** Fills critical gap - PageBreak layouter had NO examples before this!
![Pagination Example](../docs/images/example_08_pagination_explicit.png)
### 09. Link Navigation (NEW) ✅
**`09_link_navigation_demo.py`** - All link types and interactive navigation
```bash
python 09_link_navigation_demo.py
```
**Test Coverage:** [tests/examples/test_09_link_navigation_demo.py](../tests/examples/test_09_link_navigation_demo.py) - 10 tests
Demonstrates:
- **Internal links** - Document navigation (`#section1`, `#section2`)
- **External links** - Web URLs (`https://example.com`)
- **API links** - API endpoints (`/api/settings`, `/api/save`)
- **Function links** - Direct function calls (`calculate()`, `process()`)
- Link styling (underlined, color-coded by type)
- Link callbacks and interactivity
- Mixed text and link paragraphs
**Coverage Impact:** Comprehensive - All 4 LinkType variations demonstrated!
![Link Navigation Example](../docs/images/example_09_link_navigation.png)
### 10. Comprehensive Forms (NEW) ✅
**`10_forms_demo.py`** - All 14 form field types with validation
```bash
python 10_forms_demo.py
```
**Test Coverage:** [tests/examples/test_10_forms_demo.py](../tests/examples/test_10_forms_demo.py) - 9 tests
Demonstrates all 14 FormFieldType variations:
**Text-Based Fields:**
- TEXT, EMAIL, PASSWORD, URL, TEXTAREA
**Number/Date/Time Fields:**
- NUMBER, DATE, TIME, RANGE, COLOR
**Selection Fields:**
- CHECKBOX, RADIO, SELECT, HIDDEN
**Coverage Impact:** Complete - All 14 field types across 4 practical form examples!
![Comprehensive Forms Example](../docs/images/example_10_forms.png)
---
## Advanced Examples ## Advanced Examples
### HTML Rendering ### HTML Rendering
@ -194,46 +119,21 @@ All examples can be run directly from the examples directory:
```bash ```bash
cd examples cd examples
# Getting Started
python 01_simple_page_rendering.py python 01_simple_page_rendering.py
python 02_text_and_layout.py python 02_text_and_layout.py
python 03_page_layouts.py python 03_page_layouts.py
python 04_table_rendering.py python 04_table_rendering.py
python 05_table_with_images.py python 05_table_with_images.py
python 06_functional_elements_demo.py python 06_functional_elements_demo.py
# NEW: Advanced Features
python 08_pagination_demo.py # Multi-page documents
python 09_link_navigation_demo.py # All link types
python 10_forms_demo.py # All form field types
``` ```
Output images are saved to the `docs/images/` directory. Output images are saved to the `docs/images/` directory.
### Running Tests
All new examples (08, 09, 10) include comprehensive test coverage:
```bash
# Run all example tests
python -m pytest tests/examples/ -v
# Run specific test file
python -m pytest tests/examples/test_08_pagination_demo.py -v
python -m pytest tests/examples/test_09_link_navigation_demo.py -v
python -m pytest tests/examples/test_10_forms_demo.py -v
```
**Total Test Coverage:** 30 tests (11 + 10 + 9), all passing ✅
## Additional Documentation ## Additional Documentation
- `README_HTML_MULTIPAGE.md` - HTML multi-page rendering guide - `README_HTML_MULTIPAGE.md` - HTML multi-page rendering guide
- `../docs/NEW_EXAMPLES_AND_TESTS_SUMMARY.md` - Detailed summary of new examples (08, 09, 10)
- `../ARCHITECTURE.md` - Detailed explanation of the Abstract/Concrete architecture - `../ARCHITECTURE.md` - Detailed explanation of the Abstract/Concrete architecture
- `../docs/images/` - Rendered example outputs - `../docs/images/` - Rendered example outputs
- `../docs/images/README.md` - Visual documentation index
## Debug/Development Scripts ## Debug/Development Scripts

View File

@ -1,221 +0,0 @@
"""
Test for pagination example (08_pagination_demo.py).
This test ensures the pagination example runs correctly and produces expected output.
"""
import pytest
import sys
from pathlib import Path
import importlib.util
# Add examples to path
examples_dir = Path(__file__).parent.parent.parent / "examples"
sys.path.insert(0, str(examples_dir))
# Load the demo module
spec = importlib.util.spec_from_file_location(
"pagination_demo",
examples_dir / "08_pagination_demo.py"
)
demo_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(demo_module)
def test_pagination_demo_imports():
"""Test that the pagination demo can be imported without errors."""
assert demo_module is not None
def test_create_sample_paragraph():
"""Test creating a sample paragraph."""
create_sample_paragraph = demo_module.create_sample_paragraph
from pyWebLayout.abstract.block import Paragraph
para = create_sample_paragraph("Hello world test")
assert isinstance(para, Paragraph)
assert len(para.words) == 3
assert para.words[0].text == "Hello"
assert para.words[1].text == "world"
assert para.words[2].text == "test"
def test_create_title_paragraph():
"""Test creating a title paragraph."""
create_title_paragraph = demo_module.create_title_paragraph
from pyWebLayout.abstract.block import Paragraph
title = create_title_paragraph("Test Title")
assert isinstance(title, Paragraph)
assert len(title.words) == 2
# Title should have larger font
assert title.style.font_size == 24
def test_create_heading_paragraph():
"""Test creating a heading paragraph."""
create_heading_paragraph = demo_module.create_heading_paragraph
from pyWebLayout.abstract.block import Paragraph
heading = create_heading_paragraph("Test Heading")
assert isinstance(heading, Paragraph)
assert len(heading.words) == 2
# Heading should have medium font
assert heading.style.font_size == 18
def test_create_placeholder_image():
"""Test creating a placeholder image."""
create_placeholder_image = demo_module.create_placeholder_image
from pyWebLayout.abstract.block import Image as AbstractImage
img = create_placeholder_image(200, 150, "Test Image")
assert isinstance(img, AbstractImage)
assert img.source.size == (200, 150)
def test_pagebreak_layouter_integration():
"""Test that PageBreak properly forces new pages."""
create_sample_paragraph = demo_module.create_sample_paragraph
from pyWebLayout.abstract.block import PageBreak
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.layout.document_layouter import DocumentLayouter
# Create a small page
page_style = PageStyle(padding=(10, 10, 10, 10))
page1 = Page(size=(200, 200), style=page_style)
layouter1 = DocumentLayouter(page1)
# Add some content
para1 = create_sample_paragraph("First paragraph")
success, _, _ = layouter1.layout_paragraph(para1)
assert success
# Record y offset before pagebreak
y_before_break = page1._current_y_offset
# Simulating PageBreak by creating new page
# (PageBreak doesn't get laid out - it signals page creation)
page2 = Page(size=(200, 200), style=page_style)
layouter2 = DocumentLayouter(page2)
# Verify new page starts at initial offset
initial_offset = page2._current_y_offset
# Add content to new page
para2 = create_sample_paragraph("Second paragraph")
success, _, _ = layouter2.layout_paragraph(para2)
assert success
# New page should start fresh (at border_size + padding)
# Both pages should have same initial offset
assert initial_offset == page1.border_size + page_style.padding_top
def test_example_document_with_pagebreaks():
"""Test creating the main example document with pagebreaks."""
create_example_document_with_pagebreaks = demo_module.create_example_document_with_pagebreaks
pages = create_example_document_with_pagebreaks()
# Should create multiple pages
assert len(pages) > 1
# Should have created 5 pages (based on PageBreak placements)
assert len(pages) == 5
# Each page should be valid
for page in pages:
assert page.size == (600, 800)
# Page should have some content or be a clean break page
assert page._current_y_offset >= page.border_size
def test_auto_pagination_example():
"""Test auto-pagination without explicit PageBreaks."""
create_auto_pagination_example = demo_module.create_auto_pagination_example
pages = create_auto_pagination_example()
# Should create multiple pages due to content overflow
assert len(pages) >= 1
# Each page should be valid
for page in pages:
assert page.size == (500, 600)
def test_add_page_numbers():
"""Test adding page numbers to rendered pages."""
add_page_numbers = demo_module.add_page_numbers
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
# Create a few simple pages
page_style = PageStyle()
pages = [
Page(size=(200, 200), style=page_style),
Page(size=(200, 200), style=page_style),
Page(size=(200, 200), style=page_style)
]
# Add page numbers
numbered = add_page_numbers(pages, start_number=1)
# Should return same number of pages
assert len(numbered) == 3
# Each should be a rendered image
from PIL import Image
for img in numbered:
assert isinstance(img, Image.Image)
assert img.size == (200, 200)
def test_combine_pages_vertically():
"""Test combining pages into vertical strip."""
combine_pages_vertically = demo_module.combine_pages_vertically
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
# Create a few pages
page_style = PageStyle()
pages = [
Page(size=(200, 200), style=page_style).render(),
Page(size=(200, 200), style=page_style).render()
]
combined = combine_pages_vertically(pages, title="Test Title")
# Should create a combined image
from PIL import Image
assert isinstance(combined, Image.Image)
# Should be taller than individual pages
assert combined.size[1] > 200
def test_main_function():
"""Test that the main function runs without errors."""
main = demo_module.main
# Run main (will create output files)
result = main()
# Should return two combined images
assert result is not None
assert len(result) == 2
# Both should be PIL images
from PIL import Image
assert isinstance(result[0], Image.Image)
assert isinstance(result[1], Image.Image)
# Output files should exist
output_dir = Path("docs/images")
assert (output_dir / "example_08_pagination_explicit.png").exists()
assert (output_dir / "example_08_pagination_auto.png").exists()
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@ -1,179 +0,0 @@
"""
Test for link navigation example (09_link_navigation_demo.py).
This test ensures the link navigation example runs correctly and produces expected output.
"""
import pytest
import sys
from pathlib import Path
import importlib.util
# Add examples to path
examples_dir = Path(__file__).parent.parent.parent / "examples"
sys.path.insert(0, str(examples_dir))
# Load the demo module
spec = importlib.util.spec_from_file_location(
"link_navigation_demo",
examples_dir / "09_link_navigation_demo.py"
)
demo_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(demo_module)
def test_link_demo_imports():
"""Test that the link demo can be imported without errors."""
assert demo_module is not None
def test_create_paragraph_with_links():
"""Test creating a paragraph with mixed text and links."""
create_paragraph_with_links = demo_module.create_paragraph_with_links
from pyWebLayout.abstract.block import Paragraph
from pyWebLayout.abstract.inline import LinkedWord, Word
from pyWebLayout.abstract.functional import LinkType
text_parts = [
('text', "Click"),
('link', "here", "https://example.com", LinkType.EXTERNAL, "test_link"),
('text', "to visit"),
]
para = create_paragraph_with_links(text_parts)
assert isinstance(para, Paragraph)
assert len(para.words) == 4 # "Click", "here" (linked), "to", "visit"
# Check that the second word is a LinkedWord
assert isinstance(para.words[1], LinkedWord)
assert para.words[1].text == "here"
assert para.words[1].location == "https://example.com"
assert para.words[1]._link_type == LinkType.EXTERNAL
def test_link_callback_function():
"""Test that link callbacks work correctly."""
link_callback = demo_module.link_callback
demo_module.link_clicks = []
callback = link_callback("test_id")
callback()
assert "test_id" in demo_module.link_clicks
def test_create_example_1_internal_links():
"""Test creating internal links example."""
create_example_1 = demo_module.create_example_1_internal_links
from pyWebLayout.concrete.page import Page
page = create_example_1()
assert isinstance(page, Page)
assert page.size == (500, 600)
def test_create_example_2_external_links():
"""Test creating external links example."""
create_example_2 = demo_module.create_example_2_external_links
from pyWebLayout.concrete.page import Page
page = create_example_2()
assert isinstance(page, Page)
assert page.size == (500, 600)
def test_create_example_3_api_links():
"""Test creating API links example."""
create_example_3 = demo_module.create_example_3_api_links
from pyWebLayout.concrete.page import Page
page = create_example_3()
assert isinstance(page, Page)
assert page.size == (500, 600)
def test_create_example_4_function_links():
"""Test creating function links example."""
create_example_4 = demo_module.create_example_4_function_links
from pyWebLayout.concrete.page import Page
page = create_example_4()
assert isinstance(page, Page)
assert page.size == (500, 600)
def test_different_link_types():
"""Test that different link types are created correctly."""
create_paragraph_with_links = demo_module.create_paragraph_with_links
from pyWebLayout.abstract.functional import LinkType
# Test each link type
link_types = [
(LinkType.INTERNAL, "#section1"),
(LinkType.EXTERNAL, "https://example.com"),
(LinkType.API, "/api/action"),
(LinkType.FUNCTION, "function()"),
]
for link_type, location in link_types:
para = create_paragraph_with_links([
('link', "test", location, link_type, f"test_{link_type.name}")
])
assert len(para.words) == 1
assert para.words[0]._link_type == link_type
assert para.words[0].location == location
def test_combine_pages_into_grid():
"""Test combining pages into grid."""
combine_pages_into_grid = demo_module.combine_pages_into_grid
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
# Create a few pages
page_style = PageStyle()
pages = [
Page(size=(200, 200), style=page_style),
Page(size=(200, 200), style=page_style),
Page(size=(200, 200), style=page_style),
Page(size=(200, 200), style=page_style)
]
combined = combine_pages_into_grid(pages, "Test Title")
# Should create a combined image
from PIL import Image
assert isinstance(combined, Image.Image)
# Should be larger than individual pages
assert combined.size[0] > 200
assert combined.size[1] > 200
def test_main_function():
"""Test that the main function runs without errors."""
main = demo_module.main
# Run main (will create output files)
result = main()
# Should return combined image and link clicks
assert result is not None
assert len(result) == 2
combined_image, link_clicks = result
# Should be a PIL image
from PIL import Image
assert isinstance(combined_image, Image.Image)
# Link clicks should be a list (may be empty since we're not actually clicking)
assert isinstance(link_clicks, list)
# Output file should exist
output_dir = Path("docs/images")
assert (output_dir / "example_09_link_navigation.png").exists()
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@ -1,159 +0,0 @@
"""
Test for forms example (10_forms_demo.py).
This test ensures the forms example runs correctly and produces expected output.
"""
import pytest
import sys
from pathlib import Path
import importlib.util
# Add examples to path
examples_dir = Path(__file__).parent.parent.parent / "examples"
sys.path.insert(0, str(examples_dir))
# Load the demo module
spec = importlib.util.spec_from_file_location(
"forms_demo",
examples_dir / "10_forms_demo.py"
)
demo_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(demo_module)
def test_forms_demo_imports():
"""Test that the forms demo can be imported without errors."""
assert demo_module is not None
def test_form_submit_callback():
"""Test that form submit callbacks work correctly."""
form_submit_callback = demo_module.form_submit_callback
demo_module.form_submissions = []
callback = form_submit_callback("test_form")
callback({"field1": "value1"})
assert len(demo_module.form_submissions) == 1
assert demo_module.form_submissions[0][0] == "test_form"
assert demo_module.form_submissions[0][1] == {"field1": "value1"}
def test_create_example_1_text_fields():
"""Test creating text fields example."""
create_example_1 = demo_module.create_example_1_text_fields
from pyWebLayout.concrete.page import Page
page = create_example_1()
assert isinstance(page, Page)
assert page.size == (500, 600)
def test_create_example_2_number_fields():
"""Test creating number fields example."""
create_example_2 = demo_module.create_example_2_number_fields
from pyWebLayout.concrete.page import Page
page = create_example_2()
assert isinstance(page, Page)
assert page.size == (500, 600)
def test_create_example_3_selection_fields():
"""Test creating selection fields example."""
create_example_3 = demo_module.create_example_3_selection_fields
from pyWebLayout.concrete.page import Page
page = create_example_3()
assert isinstance(page, Page)
assert page.size == (500, 600)
def test_create_example_4_complete_form():
"""Test creating complete form example."""
create_example_4 = demo_module.create_example_4_complete_form
from pyWebLayout.concrete.page import Page
page = create_example_4()
assert isinstance(page, Page)
assert page.size == (500, 700)
def test_all_form_field_types():
"""Test that all FormFieldType values are demonstrated."""
from pyWebLayout.abstract.functional import FormFieldType
# All field types that should be in examples
expected_types = {
FormFieldType.TEXT,
FormFieldType.PASSWORD,
FormFieldType.EMAIL,
FormFieldType.URL,
FormFieldType.TEXTAREA,
FormFieldType.NUMBER,
FormFieldType.DATE,
FormFieldType.TIME,
FormFieldType.RANGE,
FormFieldType.COLOR,
FormFieldType.CHECKBOX,
FormFieldType.RADIO,
FormFieldType.SELECT,
FormFieldType.HIDDEN
}
# This test verifies that the example includes all major field types
# (the actual verification would happen by inspecting the forms,
# but this confirms the enum exists)
assert len(expected_types) == 14
def test_combine_pages_into_grid():
"""Test combining pages into grid."""
combine_pages_into_grid = demo_module.combine_pages_into_grid
from pyWebLayout.concrete.page import Page
from pyWebLayout.style.page_style import PageStyle
# Create a few pages
page_style = PageStyle()
pages = [
Page(size=(200, 200), style=page_style),
Page(size=(200, 200), style=page_style),
Page(size=(200, 200), style=page_style),
Page(size=(200, 200), style=page_style)
]
combined = combine_pages_into_grid(pages, "Test Title")
# Should create a combined image
from PIL import Image
assert isinstance(combined, Image.Image)
def test_main_function():
"""Test that the main function runs without errors."""
main = demo_module.main
# Run main (will create output files)
result = main()
# Should return combined image and form submissions
assert result is not None
assert len(result) == 2
combined_image, form_submissions = result
# Should be a PIL image
from PIL import Image
assert isinstance(combined_image, Image.Image)
# Form submissions should be a list
assert isinstance(form_submissions, list)
# Output file should exist
output_dir = Path("docs/images")
assert (output_dir / "example_10_forms.png").exists()
if __name__ == "__main__":
pytest.main([__file__, "-v"])