#!/usr/bin/env python3 """ Demo script for TOC overlay feature. This script demonstrates the complete TOC overlay workflow: 1. Display reading page 2. Swipe up from bottom to open TOC overlay 3. Display TOC overlay with chapter list 4. Tap on a chapter to navigate 5. Close overlay and show new page Generates a GIF showing all these interactions. """ from pathlib import Path from dreader import EbookReader, TouchEvent, GestureType from PIL import Image, ImageDraw, ImageFont import time def add_gesture_annotation(image: Image.Image, text: str, position: str = "top") -> Image.Image: """ Add a text annotation to an image showing what gesture is being performed. Args: image: Base image text: Annotation text position: "top" or "bottom" Returns: Image with annotation """ # Create a copy annotated = image.copy() draw = ImageDraw.Draw(annotated) # Try to use a nice font, fall back to default try: font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 24) except: font = ImageFont.load_default() # Calculate text position bbox = draw.textbbox((0, 0), text, font=font) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] x = (image.width - text_width) // 2 if position == "top": y = 20 else: y = image.height - text_height - 20 # Draw background rectangle padding = 10 draw.rectangle( [x - padding, y - padding, x + text_width + padding, y + text_height + padding], fill=(0, 0, 0, 200) ) # Draw text draw.text((x, y), text, fill=(255, 255, 255), font=font) return annotated def add_swipe_arrow(image: Image.Image, start_y: int, end_y: int) -> Image.Image: """ Add a visual swipe arrow to show gesture direction. Args: image: Base image start_y: Starting Y position end_y: Ending Y position Returns: Image with arrow overlay """ annotated = image.copy() draw = ImageDraw.Draw(annotated) # Draw arrow in center of screen x = image.width // 2 # Draw line draw.line([(x, start_y), (x, end_y)], fill=(255, 100, 100), width=5) # Draw arrowhead arrow_size = 20 if end_y < start_y: # Upward arrow draw.polygon([ (x, end_y), (x - arrow_size, end_y + arrow_size), (x + arrow_size, end_y + arrow_size) ], fill=(255, 100, 100)) else: # Downward arrow draw.polygon([ (x, end_y), (x - arrow_size, end_y - arrow_size), (x + arrow_size, end_y - arrow_size) ], fill=(255, 100, 100)) return annotated def add_tap_indicator(image: Image.Image, x: int, y: int, label: str = "") -> Image.Image: """ Add a visual tap indicator to show where user tapped. Args: image: Base image x, y: Tap coordinates label: Optional label for the tap Returns: Image with tap indicator """ annotated = image.copy() draw = ImageDraw.Draw(annotated) # Draw circle at tap location radius = 30 draw.ellipse( [x - radius, y - radius, x + radius, y + radius], outline=(255, 100, 100), width=5 ) # Draw crosshair draw.line([(x - radius - 10, y), (x + radius + 10, y)], fill=(255, 100, 100), width=3) draw.line([(x, y - radius - 10), (x, y + radius + 10)], fill=(255, 100, 100), width=3) # Add label if provided if label: try: font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18) except: font = ImageFont.load_default() bbox = draw.textbbox((0, 0), label, font=font) text_width = bbox[2] - bbox[0] # Position label above tap point label_x = x - text_width // 2 label_y = y - radius - 40 draw.text((label_x, label_y), label, fill=(255, 100, 100), font=font) return annotated def main(): """Generate TOC overlay demo GIF""" print("=== TOC Overlay Demo ===") print() # Find a test EPUB epub_dir = Path(__file__).parent.parent / 'tests' / 'data' / 'library-epub' epubs = list(epub_dir.glob('*.epub')) if not epubs: print("Error: No test EPUB files found!") print(f"Looked in: {epub_dir}") return epub_path = epubs[0] print(f"Using book: {epub_path.name}") # Create reader reader = EbookReader(page_size=(800, 1200)) # Load book print("Loading book...") success = reader.load_epub(str(epub_path)) if not success: print("Error: Failed to load EPUB!") return print(f"Loaded: {reader.book_title} by {reader.book_author}") print(f"Chapters: {len(reader.get_chapters())}") print() # Prepare frames for GIF frames = [] frame_duration = [] # Duration in milliseconds for each frame # Frame 1: Initial reading page print("Frame 1: Initial reading page...") page1 = reader.get_current_page() annotated1 = add_gesture_annotation(page1, f"Reading: {reader.book_title}", "top") frames.append(annotated1) frame_duration.append(2000) # 2 seconds # Frame 2: Show swipe up gesture print("Frame 2: Swipe up gesture...") swipe_visual = add_swipe_arrow(page1, 1100, 900) annotated2 = add_gesture_annotation(swipe_visual, "Swipe up from bottom", "bottom") frames.append(annotated2) frame_duration.append(1000) # 1 second # Frame 3: TOC overlay appears print("Frame 3: TOC overlay opens...") event_swipe_up = TouchEvent(gesture=GestureType.SWIPE_UP, x=400, y=1100) response = reader.handle_touch(event_swipe_up) print(f" Response: {response.action}") # Get the overlay image by calling open_toc_overlay again # (handle_touch already opened it, but we need the image) overlay_image = reader.open_toc_overlay() annotated3 = add_gesture_annotation(overlay_image, "Table of Contents", "top") frames.append(annotated3) frame_duration.append(3000) # 3 seconds to read # Frame 4: Show tap on chapter III (index 6) print("Frame 4: Tap on chapter III...") chapters = reader.get_chapters() if len(chapters) >= 7: # Calculate tap position for chapter III (7th in list, index 6) # Based on actual measurements from pyWebLayout link query: # Chapter 6 "III" link is clickable at screen position (200, 378) tap_x = 200 tap_y = 378 tap_visual = add_tap_indicator(overlay_image, tap_x, tap_y, "III") annotated4 = add_gesture_annotation(tap_visual, "Tap chapter to navigate", "bottom") frames.append(annotated4) frame_duration.append(1500) # 1.5 seconds # Frame 5: Navigate to chapter III print(f"Frame 5: Jump to chapter III (tapping at {tap_x}, {tap_y})...") event_tap = TouchEvent(gesture=GestureType.TAP, x=tap_x, y=tap_y) response = reader.handle_touch(event_tap) print(f" Response: {response.action}") new_page = reader.get_current_page() # Use the chapter title from the response data (more accurate) if response.action == "chapter_selected" and "chapter_title" in response.data: chapter_title = response.data['chapter_title'] else: chapter_title = "Chapter" annotated5 = add_gesture_annotation(new_page, f"Navigated to: {chapter_title}", "top") frames.append(annotated5) frame_duration.append(2000) # 2 seconds else: print(" Skipping chapter selection (not enough chapters)") # Frame 6: Another page for context print("Frame 6: Next page...") reader.next_page() page_final = reader.get_current_page() annotated6 = add_gesture_annotation(page_final, "Reading continues...", "top") frames.append(annotated6) frame_duration.append(2000) # 2 seconds # Save as GIF output_path = Path(__file__).parent.parent / 'docs' / 'images' / 'toc_overlay_demo.gif' output_path.parent.mkdir(parents=True, exist_ok=True) print() print(f"Saving GIF with {len(frames)} frames...") frames[0].save( output_path, save_all=True, append_images=frames[1:], duration=frame_duration, loop=0, optimize=False ) print(f"✓ GIF saved to: {output_path}") print(f" Size: {output_path.stat().st_size / 1024:.1f} KB") print(f" Frames: {len(frames)}") print(f" Total duration: {sum(frame_duration) / 1000:.1f}s") # Also save individual frames for documentation frames_dir = output_path.parent / 'toc_overlay_frames' frames_dir.mkdir(exist_ok=True) for i, frame in enumerate(frames): frame_path = frames_dir / f'frame_{i+1:02d}.png' frame.save(frame_path) print(f"✓ Individual frames saved to: {frames_dir}") # Cleanup reader.close() print() print("=== Demo Complete ===") if __name__ == '__main__': main()