391 lines
12 KiB
Python
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()
|