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