pyWebLayout/examples/09_link_navigation_demo.py
2025-11-09 21:40:50 +01:00

391 lines
12 KiB
Python

#!/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()