dreader-application/examples/demo_toc_overlay.py
2025-11-12 18:52:08 +00:00

297 lines
8.8 KiB
Python
Executable File

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