From 8f6205dcfedfca2341c3ba0d836882d75dba5093 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 9 Nov 2025 22:06:06 +0100 Subject: [PATCH] update docs --- HAL_IMPLEMENTATION_SPEC.md | 1303 ++++++++++++++++++++++++++++++++++++ README.md | 185 ++++- REQUIREMENTS.md | 1029 ++++------------------------ dreader/hal_pygame.py | 7 +- examples/LIBRARY_DEMO.md | 164 ----- examples/README_EREADER.md | 421 ------------ 6 files changed, 1606 insertions(+), 1503 deletions(-) create mode 100644 HAL_IMPLEMENTATION_SPEC.md delete mode 100644 examples/LIBRARY_DEMO.md delete mode 100644 examples/README_EREADER.md diff --git a/HAL_IMPLEMENTATION_SPEC.md b/HAL_IMPLEMENTATION_SPEC.md new file mode 100644 index 0000000..f615d2c --- /dev/null +++ b/HAL_IMPLEMENTATION_SPEC.md @@ -0,0 +1,1303 @@ +# DReader Hardware Abstraction Layer (HAL) Implementation Specification + +**Version**: 1.0 +**Date**: 2025-11-09 +**Target**: Hardware driver developers implementing platform-specific HALs + +--- + +## Table of Contents + +1. [Overview](#1-overview) +2. [Architecture](#2-architecture) +3. [HAL Interface Specification](#3-hal-interface-specification) +4. [Touch Event & Gesture System](#4-touch-event--gesture-system) +5. [Implementation Requirements](#5-implementation-requirements) +6. [Testing & Validation](#6-testing--validation) +7. [Reference Implementation](#7-reference-implementation) +8. [Platform-Specific Considerations](#8-platform-specific-considerations) +9. [Example Implementations](#9-example-implementations) + +--- + +## 1. Overview + +### 1.1 Purpose + +The DReader HAL provides a **platform-independent abstraction** for: +- **Display output**: Rendering PIL images to hardware screen +- **Touch input**: Capturing touch/mouse events and converting to gestures +- **Hardware control**: Brightness, power management, etc. + +### 1.2 Design Goals + +- **Portability**: Same application code runs on different hardware platforms +- **Async-first**: All operations are async for non-blocking I/O +- **Testability**: Easy desktop testing before deploying to device +- **Simplicity**: Minimal interface with clear contracts + +### 1.3 Key Responsibilities + +**What the HAL MUST do:** +- Display PIL Images on physical screen +- Detect touch events and classify gestures +- Return standardized `TouchEvent` objects +- Handle platform-specific initialization/cleanup + +**What the HAL does NOT do:** +- Application logic (handled by `EbookReader` and `DReaderApplication`) +- Gesture routing (handled by `GestureRouter`) +- State management (handled by `StateManager`) +- Content rendering (handled by pyWebLayout) + +--- + +## 2. Architecture + +### 2.1 System Layers + +``` +┌─────────────────────────────────────────────────────────┐ +│ DReaderApplication (main.py) │ +│ - State management, mode switching, event routing │ +└───────────────────────┬─────────────────────────────────┘ + │ + │ Uses DisplayHAL interface + ↓ +┌─────────────────────────────────────────────────────────┐ +│ DisplayHAL (Abstract Interface) │ +│ - show_image(PIL.Image) │ +│ - get_touch_event() -> TouchEvent │ +│ - set_brightness(int) │ +│ - initialize() / cleanup() │ +└───────────────────────┬─────────────────────────────────┘ + │ + │ Implemented by + ↓ +┌─────────────────────────────────────────────────────────┐ +│ Platform-Specific HAL Implementation │ +│ │ +│ Examples: │ +│ • PygameDisplayHAL (Desktop/Testing) │ +│ • EinkDisplayHAL (E-ink devices) │ +│ • KoboDisplayHAL (Kobo readers) │ +│ • RemarkableDisplayHAL (reMarkable tablets) │ +│ • [Your Custom HAL] │ +└───────────────────────┬─────────────────────────────────┘ + │ + │ Controls + ↓ +┌─────────────────────────────────────────────────────────┐ +│ Hardware Drivers │ +│ • Framebuffer / Display controller │ +│ • Touch sensor / Digitizer │ +│ • Backlight / Frontlight │ +│ • Power management │ +└─────────────────────────────────────────────────────────┘ +``` + +### 2.2 Event Flow + +``` +Hardware Touch Sensor + ↓ + HAL Driver + ↓ + Touch Detection + ↓ + Gesture Classification ← HAL responsibility + ↓ + TouchEvent + ↓ +DReaderApplication.handle_touch() + ↓ + GestureRouter + ↓ + Gesture Handlers + ↓ + GestureResponse + ↓ + Display Update + ↓ +HAL.show_image() + ↓ + Hardware Display +``` + +--- + +## 3. HAL Interface Specification + +### 3.1 Base Interface: `DisplayHAL` + +**File**: `dreader/hal.py` + +```python +from abc import ABC, abstractmethod +from typing import Optional +from PIL import Image +from dreader.gesture import TouchEvent + +class DisplayHAL(ABC): + """Abstract interface for display and input hardware""" +``` + +### 3.2 Required Methods + +#### 3.2.1 `show_image(image: PIL.Image.Image)` ⭐ CRITICAL + +**Purpose**: Display a PIL Image on the physical screen + +**Contract**: +```python +async def show_image(self, image: Image.Image): + """ + Display a PIL Image on the screen. + + Args: + image: PIL Image to display (typically RGB mode) + + Requirements: + - MUST handle image format conversion if needed + - MUST handle resolution mismatch (scale/crop) + - MUST update physical display hardware + - SHOULD complete within 100ms for responsiveness + - SHOULD support e-ink optimization if applicable + + Image Specifications: + - Format: Usually RGB (may be 'L' for grayscale) + - Size: Typically matches page_size config (e.g., 800x1200) + - Color depth: 8-bit per channel RGB + """ + pass +``` + +**Implementation Notes**: +- **E-ink devices**: Convert to grayscale, apply dithering, use appropriate refresh mode +- **Color displays**: May need RGB↔BGR conversion depending on driver +- **Framebuffer**: May need to write directly to `/dev/fb0` or use platform API +- **Performance**: Cache converted images if format conversion is expensive + +**Example (Pseudocode)**: +```python +async def show_image(self, image: Image.Image): + # Convert to device format + if self.is_eink: + image = image.convert('L') # Grayscale + image = apply_dithering(image) + + # Resize if needed + if image.size != (self.width, self.height): + image = image.resize((self.width, self.height), Image.LANCZOS) + + # Write to framebuffer or display API + self.framebuffer.write(image) + + # Refresh display + self.display_controller.refresh() +``` + +--- + +#### 3.2.2 `get_touch_event() -> Optional[TouchEvent]` ⭐ CRITICAL + +**Purpose**: Wait for and return the next touch event with gesture classification + +**Contract**: +```python +async def get_touch_event(self) -> Optional[TouchEvent]: + """ + Get the next touch event from hardware. + + Returns: + TouchEvent if available, None if no event + + Requirements: + - MUST classify gesture type (tap, swipe, long press, etc.) + - MUST return None immediately if no event (non-blocking) + - SHOULD detect gestures accurately with sensible thresholds + - MUST provide accurate pixel coordinates + - MAY implement timeouts for responsiveness + + Gesture Detection Thresholds (Recommended): + - Tap: < 30px movement, < 300ms duration + - Long press: < 30px movement, >= 500ms duration + - Swipe: >= 30px movement, < 500ms duration + - Drag: >= 30px movement, sustained motion + - Pinch: Two-finger distance change + + Coordinate System: + - Origin (0, 0) = top-left corner + - X increases right + - Y increases down + - Range: (0, 0) to (width-1, height-1) + """ + pass +``` + +**TouchEvent Format**: +```python +@dataclass +class TouchEvent: + gesture: GestureType # Required: gesture classification + x: int # Required: primary touch X + y: int # Required: primary touch Y + x2: Optional[int] = None # Optional: secondary touch X (pinch) + y2: Optional[int] = None # Optional: secondary touch Y (pinch) + timestamp_ms: float = 0 # Optional: timestamp +``` + +**Implementation Pattern**: +```python +async def get_touch_event(self) -> Optional[TouchEvent]: + # Read from touch hardware + touch_data = await self.read_touch_sensor() + + if not touch_data: + return None + + # Track touch state + if touch_data.is_down: + self._touch_start = (touch_data.x, touch_data.y) + self._touch_start_time = time.time() + + elif touch_data.is_up: + # Calculate gesture + dx = touch_data.x - self._touch_start[0] + dy = touch_data.y - self._touch_start[1] + distance = (dx**2 + dy**2) ** 0.5 + duration = time.time() - self._touch_start_time + + # Classify gesture + if distance < 30: + if duration >= 0.5: + gesture = GestureType.LONG_PRESS + else: + gesture = GestureType.TAP + else: + # Swipe direction + if abs(dx) > abs(dy): + gesture = GestureType.SWIPE_LEFT if dx < 0 else GestureType.SWIPE_RIGHT + else: + gesture = GestureType.SWIPE_UP if dy < 0 else GestureType.SWIPE_DOWN + + return TouchEvent(gesture, touch_data.x, touch_data.y) + + return None +``` + +--- + +#### 3.2.3 `set_brightness(level: int)` + +**Purpose**: Control display brightness/frontlight + +**Contract**: +```python +async def set_brightness(self, level: int): + """ + Set display brightness. + + Args: + level: Brightness level (0-10) + 0 = dimmest (may be off) + 10 = brightest + + Requirements: + - MUST accept range 0-10 + - SHOULD map to hardware's native range + - MAY be no-op if brightness control unavailable + - SHOULD persist setting if supported by hardware + """ + pass +``` + +**Implementation Notes**: +- Map 0-10 to hardware range (e.g., 0-255) +- E-ink frontlight: Control LED PWM or similar +- LCD backlight: Control via sysfs or platform API +- Desktop: May be no-op or control window alpha + +--- + +#### 3.2.4 `initialize()` (Optional) + +**Purpose**: One-time hardware initialization + +**Contract**: +```python +async def initialize(self): + """ + Initialize the display hardware. + + Called once before application starts. + + Typical Tasks: + - Open framebuffer device + - Initialize touch sensor + - Configure display controller + - Set default brightness + - Calibrate touch screen + - Clear screen to white/black + """ + pass +``` + +--- + +#### 3.2.5 `cleanup()` (Optional) + +**Purpose**: Hardware cleanup on shutdown + +**Contract**: +```python +async def cleanup(self): + """ + Clean up display hardware resources. + + Called during application shutdown. + + Typical Tasks: + - Close file descriptors + - Restore default display state + - Power down display + - Release hardware locks + """ + pass +``` + +--- + +#### 3.2.6 `show_message(message: str, duration: float)` (Optional) + +**Purpose**: Display simple text messages (loading screens, errors) + +**Contract**: +```python +async def show_message(self, message: str, duration: float = 2.0): + """ + Display a text message. + + Args: + message: Text to display + duration: How long to show (seconds) + + Default implementation provided (creates PIL Image with text). + Override for platform-specific message display. + """ + pass +``` + +--- + +### 3.3 Extended Interface: `EventLoopHAL` + +**Purpose**: For platforms that provide their own event loop (Pygame, Qt, Tkinter) + +**File**: `dreader/hal.py` + +```python +class EventLoopHAL(DisplayHAL): + """Extended HAL that provides its own event loop""" + + @abstractmethod + async def run_event_loop(self, app): + """ + Run the platform's event loop. + + Args: + app: DReaderApplication instance + + Typical Flow: + 1. await self.initialize() + 2. await app.start() + 3. Enter event loop: + a. Get events + b. await app.handle_touch(event) + c. Handle quit/close + 4. await app.shutdown() + 5. await self.cleanup() + """ + pass +``` + +**Use Case**: When the platform requires control of the main loop (e.g., Pygame, Qt) + +--- + +## 4. Touch Event & Gesture System + +### 4.1 GestureType Enumeration + +**File**: `dreader/gesture.py` + +All gestures that HAL must be able to detect: + +```python +class GestureType(Enum): + TAP = "tap" # Single finger tap + LONG_PRESS = "long_press" # Hold for 500ms+ + SWIPE_LEFT = "swipe_left" # Horizontal swipe left + SWIPE_RIGHT = "swipe_right" # Horizontal swipe right + SWIPE_UP = "swipe_up" # Vertical swipe up + SWIPE_DOWN = "swipe_down" # Vertical swipe down + PINCH_IN = "pinch_in" # Two-finger pinch (zoom out) + PINCH_OUT = "pinch_out" # Two-finger spread (zoom in) + DRAG_START = "drag_start" # Start dragging + DRAG_MOVE = "drag_move" # Continue dragging + DRAG_END = "drag_end" # End dragging +``` + +### 4.2 Gesture Priority & Requirements + +#### 4.2.1 Essential Gestures (MUST implement) + +| Gesture | Usage | Detection | +|---------|-------|-----------| +| **TAP** | Link/button activation, word selection | < 30px movement, < 300ms | +| **SWIPE_LEFT** | Next page | Horizontal, dx < -30px | +| **SWIPE_RIGHT** | Previous page | Horizontal, dx > 30px | +| **SWIPE_UP** | Open navigation/TOC | Vertical, dy < -30px | +| **SWIPE_DOWN** | Open settings (from top 20%) | Vertical, dy > 30px | + +#### 4.2.2 Important Gestures (SHOULD implement) + +| Gesture | Usage | Detection | +|---------|-------|-----------| +| **LONG_PRESS** | Word definition, context menu | < 30px movement, >= 500ms | +| **PINCH_OUT** | Increase font size | Two-finger distance increase | +| **PINCH_IN** | Decrease font size | Two-finger distance decrease | + +#### 4.2.3 Advanced Gestures (MAY implement) + +| Gesture | Usage | Detection | +|---------|-------|-----------| +| **DRAG_START/MOVE/END** | Text selection | Sustained motion >= 30px | + +### 4.3 Gesture Detection Algorithm + +#### 4.3.1 State Machine + +``` +IDLE + ↓ touch_down +TOUCHING + ↓ timeout(500ms) → LONG_PRESS + ↓ movement > 30px → MOVING + ↓ touch_up → TAP +MOVING + ↓ touch_up → SWIPE_* + ↓ sustained → DRAG_* +``` + +#### 4.3.2 Recommended Thresholds + +```python +# Distance thresholds +TAP_THRESHOLD = 30 # pixels +SWIPE_MIN_DISTANCE = 30 # pixels +DRAG_THRESHOLD = 30 # pixels + +# Time thresholds +LONG_PRESS_DURATION = 0.5 # seconds (500ms) +TAP_MAX_DURATION = 0.3 # seconds (300ms) +SWIPE_MAX_DURATION = 0.5 # seconds + +# Direction thresholds +SWIPE_ANGLE_THRESHOLD = 45 # degrees (for horizontal vs vertical) +``` + +#### 4.3.3 Pseudocode + +```python +class GestureDetector: + def on_touch_down(self, x, y): + self.start_pos = (x, y) + self.start_time = time.now() + self.state = TOUCHING + + def on_touch_move(self, x, y): + dx = x - self.start_pos[0] + dy = y - self.start_pos[1] + distance = sqrt(dx**2 + dy**2) + + if distance > TAP_THRESHOLD and self.state == TOUCHING: + self.state = MOVING + # Could emit DRAG_START here + + def on_touch_up(self, x, y): + dx = x - self.start_pos[0] + dy = y - self.start_pos[1] + distance = sqrt(dx**2 + dy**2) + duration = time.now() - self.start_time + + if distance < TAP_THRESHOLD: + if duration >= LONG_PRESS_DURATION: + return GestureType.LONG_PRESS + else: + return GestureType.TAP + else: + # Determine swipe direction + if abs(dx) > abs(dy): # Horizontal + if dx > 0: + return GestureType.SWIPE_RIGHT + else: + return GestureType.SWIPE_LEFT + else: # Vertical + if dy > 0: + return GestureType.SWIPE_DOWN + else: + return GestureType.SWIPE_UP +``` + +### 4.4 TouchEvent Construction + +```python +# Example: Creating a TAP event +event = TouchEvent( + gesture=GestureType.TAP, + x=450, + y=320, + timestamp_ms=time.time() * 1000 +) + +# Example: Creating a SWIPE event +event = TouchEvent( + gesture=GestureType.SWIPE_LEFT, + x=600, # End position + y=400, + timestamp_ms=time.time() * 1000 +) + +# Example: Creating a PINCH event (two fingers) +event = TouchEvent( + gesture=GestureType.PINCH_OUT, + x=400, # Finger 1 + y=500, + x2=600, # Finger 2 + y2=700, + timestamp_ms=time.time() * 1000 +) +``` + +--- + +## 5. Implementation Requirements + +### 5.1 Mandatory Features + +✅ **MUST implement**: +- [ ] `show_image()` - Display PIL Images +- [ ] `get_touch_event()` - Return TouchEvents +- [ ] Basic gesture detection (TAP, SWIPE_LEFT/RIGHT/UP/DOWN) +- [ ] Correct coordinate system (0,0 = top-left) +- [ ] Async/await support for all methods + +### 5.2 Recommended Features + +⭐ **SHOULD implement**: +- [ ] `initialize()` and `cleanup()` - Proper lifecycle management +- [ ] `set_brightness()` - Brightness control +- [ ] LONG_PRESS gesture +- [ ] PINCH_IN/OUT gestures (if multitouch available) +- [ ] Efficient image format conversion +- [ ] E-ink optimization (partial refresh, dithering) + +### 5.3 Optional Features + +💡 **MAY implement**: +- [ ] `show_message()` override - Custom loading screens +- [ ] DRAG gestures - Text selection +- [ ] Touch pressure sensitivity +- [ ] Multi-touch tracking +- [ ] Hardware acceleration +- [ ] Custom refresh modes (e-ink A2/DU/GC16) + +### 5.4 Performance Targets + +| Metric | Target | Critical | +|--------|--------|----------| +| Image display latency | < 100ms | < 500ms | +| Touch event latency | < 50ms | < 200ms | +| Frame rate (color) | 30 FPS | 10 FPS | +| E-ink refresh (fast) | < 200ms | < 500ms | +| E-ink refresh (full) | < 1000ms | < 2000ms | +| Gesture detection accuracy | > 95% | > 80% | + +--- + +## 6. Testing & Validation + +### 6.1 Unit Tests + +Test each HAL method in isolation: + +```python +import pytest +from your_hal import YourDisplayHAL + +@pytest.mark.asyncio +async def test_show_image(): + hal = YourDisplayHAL() + await hal.initialize() + + # Create test image + img = Image.new('RGB', (800, 1200), color=(255, 0, 0)) + + # Should not raise + await hal.show_image(img) + + await hal.cleanup() + +@pytest.mark.asyncio +async def test_gesture_detection(): + hal = YourDisplayHAL() + await hal.initialize() + + # Simulate tap + event = await hal.get_touch_event() + assert event.gesture == GestureType.TAP + assert 0 <= event.x < hal.width + assert 0 <= event.y < hal.height +``` + +### 6.2 Integration Tests + +Test with actual DReaderApplication: + +```python +from dreader.main import DReaderApplication, AppConfig + +async def test_integration(): + hal = YourDisplayHAL() + config = AppConfig( + display_hal=hal, + library_path="./test_books", + page_size=(800, 1200) + ) + app = DReaderApplication(config) + + await app.start() + + # Simulate touch + event = TouchEvent(GestureType.TAP, 400, 600) + await app.handle_touch(event) + + await app.shutdown() +``` + +### 6.3 Manual Test Checklist + +- [ ] Display shows full-screen images without distortion +- [ ] Tap on link navigates to chapter +- [ ] Swipe left advances page +- [ ] Swipe right goes back page +- [ ] Swipe up opens TOC overlay +- [ ] Swipe down (from top) opens settings +- [ ] Long press on word shows definition +- [ ] Pinch gestures change font size (if supported) +- [ ] Brightness control works (if supported) +- [ ] No memory leaks after 100+ page turns +- [ ] No display artifacts or tearing + +### 6.4 Validation Tools + +**Debug Logging**: +```python +import logging +logger = logging.getLogger(__name__) + +async def show_image(self, image): + logger.info(f"Displaying image: {image.size}, {image.mode}") + # ... implementation + +async def get_touch_event(self): + event = # ... detect gesture + logger.info(f"Gesture detected: {event.gesture} at ({event.x}, {event.y})") + return event +``` + +**Touch Coordinate Visualization**: +- Draw red dot at touch coordinates +- Display gesture type as text overlay +- Show swipe trajectory lines + +--- + +## 7. Reference Implementation + +### 7.1 PygameDisplayHAL + +**File**: `dreader/hal_pygame.py` + +The Pygame HAL is a **complete reference implementation** for desktop testing. + +**Key features**: +- Mouse → touch event conversion +- Gesture detection algorithm +- Keyboard shortcuts +- Debug logging + +**Study this implementation for**: +- Gesture detection state machine +- Coordinate handling +- Event loop structure +- Error handling patterns + +### 7.2 Minimal HAL Template + +```python +from dreader.hal import DisplayHAL +from dreader.gesture import TouchEvent, GestureType +from PIL import Image +from typing import Optional +import asyncio + +class MinimalHAL(DisplayHAL): + """Minimal HAL implementation template""" + + def __init__(self, width: int = 800, height: int = 1200): + self.width = width + self.height = height + + # Gesture tracking + self._touch_start = None + self._touch_start_time = 0 + + async def initialize(self): + """Initialize hardware""" + # TODO: Open framebuffer, initialize touch sensor + pass + + async def cleanup(self): + """Cleanup hardware""" + # TODO: Close file descriptors, cleanup + pass + + async def show_image(self, image: Image.Image): + """Display image on screen""" + # Convert to device format + if image.mode != 'RGB': + image = image.convert('RGB') + + # Resize if needed + if image.size != (self.width, self.height): + image = image.resize( + (self.width, self.height), + Image.Resampling.LANCZOS + ) + + # TODO: Write to framebuffer or display API + # framebuffer.write(image) + + async def get_touch_event(self) -> Optional[TouchEvent]: + """Get next touch event""" + # TODO: Read from touch hardware + # touch_data = await read_touch_sensor() + + # If no touch, return None + # if not touch_data: + # return None + + # Classify gesture based on touch_down/touch_up + # See gesture detection algorithm in section 4.3 + + pass + + async def set_brightness(self, level: int): + """Set brightness (0-10)""" + # Map to hardware range + hardware_value = int(level * 255 / 10) + + # TODO: Write to brightness control + # with open('/sys/class/backlight/.../brightness', 'w') as f: + # f.write(str(hardware_value)) +``` + +--- + +## 8. Platform-Specific Considerations + +### 8.1 E-Ink Devices (Kobo, Kindle, reMarkable) + +#### Display Characteristics: +- Grayscale only (8-bit per pixel) +- Slow refresh (200ms - 1000ms) +- Multiple refresh modes (A2, DU, GC16, GL16) +- Requires dithering for images + +#### Optimization Strategies: + +**1. Partial Refresh**: +```python +async def show_image(self, image: Image.Image, partial: bool = True): + if partial: + # Use fast A2 mode for text-only updates + self.controller.set_mode('A2') + else: + # Full GC16 refresh every N pages + self.controller.set_mode('GC16') + + self.framebuffer.write(image) + self.controller.refresh() +``` + +**2. Dithering**: +```python +from PIL import ImageDraw + +def apply_dithering(image: Image.Image) -> Image.Image: + # Convert to grayscale + image = image.convert('L') + + # Apply Floyd-Steinberg dithering + image = image.convert('1', dither=Image.FLOYDSTEINBERG) + image = image.convert('L') + + return image +``` + +**3. Refresh Strategy**: +- Text pages: A2 mode (fast, ~200ms) +- Image-heavy pages: GC16 mode (slow, ~1000ms) +- Full refresh every 5-10 pages to clear ghosting + +#### Touch Input: +- Often resistive touch (single-point only) +- Lower resolution than display +- May require calibration + +**Example: Kobo HAL skeleton**: +```python +class KoboDisplayHAL(DisplayHAL): + def __init__(self): + self.fb = open('/dev/fb0', 'wb') + self.touch = open('/dev/input/event0', 'rb') + self.width = 1072 + self.height = 1448 + self.refresh_counter = 0 + + async def show_image(self, image: Image.Image): + # Convert to grayscale + image = image.convert('L') + + # Resize + image = image.resize((self.width, self.height)) + + # Write to framebuffer + self.fb.write(image.tobytes()) + + # Decide refresh mode + self.refresh_counter += 1 + if self.refresh_counter % 10 == 0: + # Full refresh every 10 pages + self._trigger_refresh('GC16') + else: + # Fast refresh + self._trigger_refresh('A2') +``` + +--- + +### 8.2 Linux Framebuffer + +#### Display: +```python +async def show_image(self, image: Image.Image): + # Open framebuffer + with open('/dev/fb0', 'wb') as fb: + # Convert to RGB565 or RGB888 depending on device + if self.color_depth == 16: + # RGB565 + data = image.convert('RGB').tobytes('raw', 'RGB') + # Convert to RGB565 packed format + data = self._convert_rgb888_to_rgb565(data) + else: + # RGB888 + data = image.convert('RGB').tobytes() + + fb.write(data) +``` + +#### Touch Input (evdev): +```python +import evdev + +async def get_touch_event(self) -> Optional[TouchEvent]: + device = evdev.InputDevice('/dev/input/event0') + + async for event in device.async_read_loop(): + if event.type == evdev.ecodes.EV_ABS: + if event.code == evdev.ecodes.ABS_X: + self.current_x = event.value + elif event.code == evdev.ecodes.ABS_Y: + self.current_y = event.value + + elif event.type == evdev.ecodes.EV_KEY: + if event.code == evdev.ecodes.BTN_TOUCH: + if event.value == 1: # Touch down + self._on_touch_down(self.current_x, self.current_y) + else: # Touch up + gesture = self._on_touch_up(self.current_x, self.current_y) + if gesture: + return TouchEvent(gesture, self.current_x, self.current_y) +``` + +--- + +### 8.3 Embedded Systems (Raspberry Pi, etc.) + +#### Recommendations: +- Use hardware-accelerated PIL if available (Pillow-SIMD) +- Minimize memory allocations (reuse image buffers) +- Consider using mmap for framebuffer access +- Implement watchdog for crash recovery + +```python +import mmap + +class EmbeddedHAL(DisplayHAL): + def __init__(self): + self.fb_fd = os.open('/dev/fb0', os.O_RDWR) + self.fb_size = self.width * self.height * 4 # RGB888 + self.fb_mmap = mmap.mmap( + self.fb_fd, + self.fb_size, + mmap.MAP_SHARED, + mmap.PROT_READ | mmap.PROT_WRITE + ) + + async def show_image(self, image: Image.Image): + # Write directly to mmap'd framebuffer + data = image.convert('RGB').tobytes() + self.fb_mmap.seek(0) + self.fb_mmap.write(data) +``` + +--- + +### 8.4 Desktop Testing (Windows/macOS/Linux) + +Use **PygameDisplayHAL** (already provided) or implement similar with: +- **Tkinter**: For simple cross-platform windows +- **Qt (PyQt5/PySide6)**: For advanced GUI features +- **SDL2**: For game-like responsiveness + +--- + +## 9. Example Implementations + +### 9.1 Complete Example: Pygame HAL + +See `dreader/hal_pygame.py` for full implementation. + +**Key sections to study**: +- Lines 158-240: Gesture detection in `get_touch_event()` +- Lines 111-140: Image display in `show_image()` +- Lines 302-354: Event loop in `run_event_loop()` + +--- + +### 9.2 Pseudocode: E-Ink HAL + +```python +from dreader.hal import DisplayHAL +from dreader.gesture import TouchEvent, GestureType +from PIL import Image +import time + +class EInkHAL(DisplayHAL): + """E-Ink device HAL with partial refresh support""" + + def __init__(self, width=1072, height=1448): + self.width = width + self.height = height + self.refresh_count = 0 + + # Hardware interfaces + self.framebuffer = None + self.touch_device = None + self.frontlight = None + + # Gesture tracking + self._touch_down_pos = None + self._touch_down_time = 0 + + async def initialize(self): + """Initialize e-ink display and touch sensor""" + # Open framebuffer + self.framebuffer = open('/dev/fb0', 'r+b') + + # Open touch device + self.touch_device = evdev.InputDevice('/dev/input/event0') + + # Initialize frontlight + self.frontlight = open('/sys/class/backlight/mxc_mst716/brightness', 'w') + + # Clear screen + blank = Image.new('L', (self.width, self.height), 255) + await self.show_image(blank) + + async def cleanup(self): + """Cleanup hardware resources""" + if self.framebuffer: + self.framebuffer.close() + if self.frontlight: + self.frontlight.close() + + async def show_image(self, image: Image.Image): + """Display image with e-ink optimization""" + # Convert to grayscale + if image.mode != 'L': + image = image.convert('L') + + # Resize if needed + if image.size != (self.width, self.height): + image = image.resize((self.width, self.height), Image.LANCZOS) + + # Write to framebuffer + self.framebuffer.seek(0) + self.framebuffer.write(image.tobytes()) + + # Decide refresh mode + self.refresh_count += 1 + if self.refresh_count % 10 == 0: + # Full refresh every 10 pages (remove ghosting) + self._trigger_refresh('FULL') + await asyncio.sleep(1.0) # Wait for full refresh + else: + # Partial refresh (fast) + self._trigger_refresh('PARTIAL') + await asyncio.sleep(0.2) # Wait for partial refresh + + def _trigger_refresh(self, mode: str): + """Trigger display refresh""" + if mode == 'FULL': + # Device-specific: trigger full GC16 refresh + # Example: ioctl(MXCFB_SEND_UPDATE, waveform=GC16) + pass + else: + # Device-specific: trigger partial A2 refresh + # Example: ioctl(MXCFB_SEND_UPDATE, waveform=A2) + pass + + async def get_touch_event(self) -> Optional[TouchEvent]: + """Read touch events from evdev""" + current_x = 0 + current_y = 0 + + # Read events (with timeout) + try: + events = await asyncio.wait_for( + self._read_touch_events(), + timeout=0.1 + ) + except asyncio.TimeoutError: + return None + + for event in events: + if event.type == evdev.ecodes.EV_ABS: + if event.code == evdev.ecodes.ABS_X: + current_x = event.value + elif event.code == evdev.ecodes.ABS_Y: + current_y = event.value + + elif event.type == evdev.ecodes.EV_KEY: + if event.code == evdev.ecodes.BTN_TOUCH: + if event.value == 1: # Touch down + self._touch_down_pos = (current_x, current_y) + self._touch_down_time = time.time() + + else: # Touch up + if not self._touch_down_pos: + continue + + # Calculate gesture + dx = current_x - self._touch_down_pos[0] + dy = current_y - self._touch_down_pos[1] + distance = (dx**2 + dy**2) ** 0.5 + duration = time.time() - self._touch_down_time + + # Classify + if distance < 30: + if duration >= 0.5: + gesture = GestureType.LONG_PRESS + else: + gesture = GestureType.TAP + else: + if abs(dx) > abs(dy): + gesture = GestureType.SWIPE_LEFT if dx < 0 else GestureType.SWIPE_RIGHT + else: + gesture = GestureType.SWIPE_UP if dy < 0 else GestureType.SWIPE_DOWN + + self._touch_down_pos = None + return TouchEvent(gesture, current_x, current_y) + + return None + + async def _read_touch_events(self): + """Helper to read touch events with async""" + events = [] + async for event in self.touch_device.async_read_loop(): + events.append(event) + if event.type == evdev.ecodes.EV_SYN: + break # End of event batch + return events + + async def set_brightness(self, level: int): + """Set frontlight brightness""" + # Map 0-10 to hardware range (e.g., 0-100) + value = level * 10 + self.frontlight.seek(0) + self.frontlight.write(str(value)) + self.frontlight.flush() +``` + +--- + +### 9.3 Usage Example + +```python +import asyncio +from your_hal import YourDisplayHAL +from dreader.main import DReaderApplication, AppConfig + +async def main(): + # Create HAL + hal = YourDisplayHAL(width=800, height=1200) + + # Configure application + config = AppConfig( + display_hal=hal, + library_path="/home/user/Books", + page_size=(800, 1200), + log_level=logging.INFO + ) + + # Create application + app = DReaderApplication(config) + + # If using EventLoopHAL: + await hal.run_event_loop(app) + + # OR if using basic DisplayHAL: + # await app.start() + # while app.is_running(): + # event = await hal.get_touch_event() + # if event: + # await app.handle_touch(event) + # await asyncio.sleep(0.01) + # await app.shutdown() + +if __name__ == "__main__": + asyncio.run(main()) +``` + +--- + +## 10. Troubleshooting + +### Common Issues + +**Issue**: Images not displaying +- Check framebuffer path (`/dev/fb0`) +- Verify color format (RGB vs BGR) +- Check image size matches display +- Verify write permissions + +**Issue**: Touch not detected +- Check evdev device path +- Verify touch coordinates are mapped correctly +- Check coordinate system (origin, scaling) +- Test with `evtest` utility + +**Issue**: Gestures not recognized +- Check thresholds (TAP_THRESHOLD, etc.) +- Add debug logging to gesture detection +- Verify touch up/down events are paired +- Check timestamp accuracy + +**Issue**: Slow performance +- Profile image conversion (use cProfile) +- Cache converted images +- Use partial refresh on e-ink +- Consider hardware acceleration + +--- + +## 11. Resources + +### Documentation +- DReader Architecture: `INTEGRATION_DOCUMENTATION_INDEX.md` +- pyWebLayout Integration: `PYWEBLAYOUT_INTEGRATION_GUIDE.md` +- Async Rendering: `ASYNC_DIRTY_FLAG_RENDERING.md` + +### Reference Code +- Pygame HAL: `dreader/hal_pygame.py` +- HAL Interface: `dreader/hal.py` +- Gesture Types: `dreader/gesture.py` +- Main Application: `dreader/main.py` + +### External References +- Linux Framebuffer: https://www.kernel.org/doc/Documentation/fb/api.txt +- evdev Input: https://python-evdev.readthedocs.io/ +- PIL/Pillow: https://pillow.readthedocs.io/ +- E-Ink Controllers: Device-specific documentation (Kobo, Kindle, reMarkable) + +--- + +## 12. Checklist for HAL Implementers + +Use this checklist to track your implementation: + +### Setup +- [ ] Read this specification completely +- [ ] Study PygameDisplayHAL reference implementation +- [ ] Set up development environment +- [ ] Test DReader with Pygame HAL first + +### Core Implementation +- [ ] Create HAL class inheriting from DisplayHAL +- [ ] Implement `__init__()` with device configuration +- [ ] Implement `initialize()` - hardware setup +- [ ] Implement `cleanup()` - resource cleanup +- [ ] Implement `show_image()` - display rendering +- [ ] Implement `get_touch_event()` - input handling +- [ ] Implement gesture detection algorithm +- [ ] Implement `set_brightness()` (if supported) + +### Testing +- [ ] Unit test each method +- [ ] Test with minimal application +- [ ] Test with full DReader application +- [ ] Test all essential gestures +- [ ] Performance profiling +- [ ] Memory leak testing +- [ ] Long-term stability testing + +### Optimization +- [ ] Profile and optimize hot paths +- [ ] Implement caching where appropriate +- [ ] Add platform-specific optimizations +- [ ] Tune gesture thresholds +- [ ] Optimize display refresh strategy + +### Documentation +- [ ] Document platform-specific requirements +- [ ] Document known limitations +- [ ] Create usage examples +- [ ] Write troubleshooting guide + +--- + +## Version History + +| Version | Date | Changes | +|---------|------|---------| +| 1.0 | 2025-11-09 | Initial specification | + +--- + +**End of HAL Implementation Specification** + +For questions or issues, please refer to the DReader documentation or create an issue in the project repository. diff --git a/README.md b/README.md index 4b923c7..ce401cc 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,9 @@ | ![Documentation Coverage](https://gitea.tourolle.paris/dtourolle/dreader-application/raw/branch/badges/cov_info/coverage-docs.svg) | **Documentation Coverage** - Percentage of code with docstrings | | ![License](https://img.shields.io/badge/license-MIT-blue.svg) | **License** - Project licensing information | -> 📋 **Note**: Badges show results from the commit referenced in the URLs. Red "error" badges indicate build failures for that specific step. - ## Description -DReader Application is a complete, production-ready ebook reader built on [pyWebLayout](https://gitea.tourolle.paris/dtourolle/pyWebLayout). It demonstrates how to build a full-featured ebook reader with text highlighting, bookmarks, gesture support, and position persistence. +DReader Application is a complete, production-ready ebook reader built on [pyWebLayout](https://gitea.tourolle.paris/dtourolle/pyWebLayout). It demonstrates how to build a full-featured ebook reader with library browsing, text highlighting, bookmarks, gesture support, overlays, and position persistence. This project serves as both a reference implementation and a ready-to-use ereader library for building desktop, web-based, or embedded reading applications. @@ -20,11 +18,12 @@ This project serves as both a reference implementation and a ready-to-use ereade ### Core Reading Features - 📖 **EPUB Support** - Load and render EPUB files with full text extraction +- 📚 **Library Management** - Browse and select books from your collection - 📄 **Page Rendering** - Render pages as PIL Images optimized for any display - ⬅️➡️ **Navigation** - Smooth forward and backward page navigation - 🔖 **Bookmarks** - Save and restore reading positions with persistence - 📑 **Chapter Navigation** - Jump to chapters by title or index via TOC -- 📋 **TOC Overlay** - Interactive table of contents overlay with gesture support +- 📋 **Unified Overlays** - Navigation (TOC + Bookmarks) and Settings overlays - 📊 **Progress Tracking** - Real-time reading progress percentage ### Text Interaction @@ -40,6 +39,7 @@ This project serves as both a reference implementation and a ready-to-use ereade - 💾 **Position Persistence** - Stable positions across style changes - ⚡ **Smart Reflow** - Automatic text reflow on font/spacing changes - 🎨 **Custom Styling** - Full control over colors, fonts, and layout +- 💾 **Settings Persistence** - Save and restore preferences across sessions ## Installation @@ -156,8 +156,8 @@ reader.get_chapters() # List all chapters reader.get_current_chapter_info() reader.get_reading_progress() # Returns 0.0 to 1.0 -# TOC Overlay -overlay_image = reader.open_toc_overlay() # Returns composited image with TOC +# Navigation Overlay (unified TOC + Bookmarks) +overlay_image = reader.open_navigation_overlay() # Opens with tabs reader.close_overlay() reader.is_overlay_open() ``` @@ -236,34 +236,84 @@ elif response.action == ActionType.CHAPTER_SELECTED: # - TAP: Select words, activate links, navigate TOC # - LONG_PRESS: Show definitions or context menu # - SWIPE_LEFT/RIGHT: Page navigation -# - SWIPE_UP: Open TOC overlay (from bottom 20% of screen) -# - SWIPE_DOWN: Close overlay +# - SWIPE_UP: Open navigation overlay (from bottom 20% of screen) +# - SWIPE_DOWN: Close overlay or open settings (from top 20%) # - PINCH_IN/OUT: Font size adjustment # - DRAG: Text selection ``` -### File Operations +### Settings Persistence ```python -# Save current page to file -reader.render_to_file("current_page.png") +from dreader.state import StateManager +from pathlib import Path -# Context manager (auto-saves position on close) -with EbookReader(page_size=(800, 1000)) as reader: - reader.load_epub("book.epub") - # ... use reader ... -# Position automatically saved on exit +# Initialize state manager +state_file = Path.home() / ".config" / "dreader" / "state.json" +state_manager = StateManager(state_file=state_file) + +# Load saved state +state = state_manager.load_state() + +# Create reader and apply saved settings +reader = EbookReader(page_size=(800, 1000)) +reader.load_epub("mybook.epub") +reader.apply_settings(state.settings.to_dict()) + +# Settings are automatically saved +reader.increase_font_size() +state_manager.update_settings(reader.get_current_settings()) +state_manager.save_state() +``` + +### Library Management + +```python +from dreader.library import LibraryManager + +# Initialize library +library = LibraryManager( + library_path="/path/to/books", + page_size=(800, 1200) +) + +# Scan for EPUB files +library.scan_library() + +# Render library view +library_image = library.render_library() + +# Handle book selection +book_path = library.handle_library_tap(x=400, y=300) +if book_path: + reader.load_epub(book_path) ``` ## Examples -Check out the `examples/` directory for complete working examples: +Check out the [examples/](examples/) directory for complete working examples: +### Basic Examples - **[simple_ereader_example.py](examples/simple_ereader_example.py)** - Basic ereader usage with EPUB loading and navigation - **[ereader_demo.py](examples/ereader_demo.py)** - Comprehensive demo showcasing all features -- **[word_selection_highlighting.py](examples/word_selection_highlighting.py)** - Text selection and highlighting - **[simple_word_highlight.py](examples/simple_word_highlight.py)** - Minimal highlighting example + +### Text Highlighting +- **[word_selection_highlighting.py](examples/word_selection_highlighting.py)** - Text selection and highlighting + +### Overlays +- **[demo_toc_overlay.py](examples/demo_toc_overlay.py)** - Interactive table of contents overlay +- **[navigation_overlay_example.py](examples/navigation_overlay_example.py)** - Unified navigation overlay (TOC + Bookmarks) +- **[demo_settings_overlay.py](examples/demo_settings_overlay.py)** - Settings panel with font/spacing controls + +### Library & State +- **[library_reading_integration.py](examples/library_reading_integration.py)** - Complete library → reading → resume workflow +- **[persistent_settings_example.py](examples/persistent_settings_example.py)** - Save/restore settings across sessions + +### Advanced +- **[demo_pagination.py](examples/demo_pagination.py)** - Pagination system demonstration - **[generate_ereader_gifs.py](examples/generate_ereader_gifs.py)** - Generate animated GIF demonstrations +- **[generate_library_demo_gif.py](examples/generate_library_demo_gif.py)** - Generate library demo animations ## Architecture @@ -279,6 +329,27 @@ dreader.application.EbookReader (High-Level API) └── pyWebLayout.io.readers.epub_reader # EPUB parsing ``` +### Component Structure + +``` +dreader/ +├── application.py # Main EbookReader class (coordinator) +├── managers/ # Specialized management modules +│ ├── document.py # Document loading (EPUB/HTML) +│ ├── settings.py # Font and spacing controls +│ └── highlight_coordinator.py # Text highlighting +├── handlers/ +│ └── gestures.py # Touch event routing +├── overlays/ # UI overlay system +│ ├── base.py # Base overlay functionality +│ ├── navigation.py # TOC and bookmarks overlay +│ └── settings.py # Settings overlay +├── library.py # Library browsing and book selection +├── state.py # Application state persistence +├── html_generator.py # HTML generation for overlays +└── gesture.py # Gesture definitions and responses +``` + ### Relationship to pyWebLayout **pyWebLayout** is a layout engine library providing low-level primitives: @@ -297,6 +368,30 @@ Think of it like this: - **pyWebLayout** = React (library) - **DReader Application** = Next.js (framework) +## State Management + +### File Structure +``` +~/.config/dreader/ +├── state.json # Application state +├── covers/ # Cached book covers +├── bookmarks/ # Per-book bookmarks +├── highlights/ # Per-book highlights +└── xray/ # X-Ray data (future) +``` + +### State Persistence +- **Auto-save**: Every 60 seconds +- **Immediate save**: On mode change, settings change, shutdown +- **Boot behavior**: Resume last book at last position or show library +- **Error handling**: Fall back to library if book missing or state corrupt + +### Position Stability +- Positions stored by abstract document structure (chapter/block/word indices) +- Stable across font size changes, spacing changes, page size changes +- Per-book storage using document IDs +- Special `__auto_resume__` bookmark for last reading position + ## Use Cases - 📱 **Desktop Ereader Applications** - Build native ereader apps with Python @@ -340,6 +435,9 @@ python simple_ereader_example.py /path/to/book.epub # Run comprehensive demo python ereader_demo.py /path/to/book.epub +# Run library integration demo +python library_reading_integration.py /path/to/library/ + # Generate animated GIFs python generate_ereader_gifs.py /path/to/book.epub ``` @@ -351,6 +449,10 @@ The project includes comprehensive tests covering: - **Application API** - All EbookReader methods and workflows - **System Integration** - Layout manager, bookmarks, and state management - **Highlighting** - Word and selection highlighting with persistence +- **Overlays** - Navigation and settings overlay interactions +- **Gestures** - Touch event handling and routing +- **Boot Recovery** - State persistence and position restoration +- **Library** - Book scanning, selection, and metadata - **Edge Cases** - Error handling, boundary conditions, and recovery ```bash @@ -367,6 +469,53 @@ pytest -v pytest --cov=dreader --cov-report=term-missing ``` +## Hardware Integration + +DReader requires a Hardware Abstraction Layer (HAL) for display and input: + +```python +from abc import ABC, abstractmethod +from PIL import Image + +class DisplayHAL(ABC): + """Abstract display interface for platform integration""" + + @abstractmethod + def show_image(self, image: Image.Image): + """Display a PIL Image on the screen""" + + @abstractmethod + def get_touch_events(self) -> Iterator[TouchEvent]: + """Get iterator of touch events from hardware""" + + @abstractmethod + def set_brightness(self, level: int): + """Set display brightness (0-10)""" +``` + +**Example HAL Implementations:** +- **E-ink Displays**: IT8951, Remarkable device SDK +- **Desktop**: pygame, tkinter, Qt +- **Web**: Flask + HTML canvas +- **Embedded**: Device-specific framebuffer + +See [HAL_IMPLEMENTATION_SPEC.md](HAL_IMPLEMENTATION_SPEC.md) for detailed integration guidelines. + +## Documentation + +- [README.md](README.md) - This file, main project documentation +- [REQUIREMENTS.md](REQUIREMENTS.md) - Application requirements specification +- [ARCHITECTURE.md](ARCHITECTURE.md) - System architecture and design details +- [HAL_IMPLEMENTATION_SPEC.md](HAL_IMPLEMENTATION_SPEC.md) - Hardware integration guide + +## Performance + +- **Boot Time**: ~2-3 seconds to resume reading +- **Page Turn**: ~50-100ms (depends on page complexity) +- **Overlay Open**: ~200-250ms (includes HTML generation and rendering) +- **Memory Usage**: ~20-30MB base + 10-50MB per book +- **Cache**: Automatic cover image and metadata caching for fast library loading + ## Contributing Contributions welcome! This project demonstrates what's possible with pyWebLayout. If you build something cool or find ways to improve the reader, please share! diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 76575e9..fa338cc 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -1,965 +1,196 @@ # E-Reader Application Requirements ## Overview -This document defines the requirements for a full-featured e-reader application built on the dreader/pyWebLayout framework. The application will support multiple modes, state persistence, and a complete user interaction model. +This document defines the core requirements for a full-featured e-reader application built on dreader/pyWebLayout. The application supports library browsing, reading with overlays, state persistence, and gesture-based interaction. -## Implementation Status Summary +## Application Modes -### Legend -- ✅ **Complete**: Fully implemented and tested -- 📖 **Example Ready**: Working examples exist, needs production controller -- 🔜 **Planned**: Designed but not yet implemented -- ⏸️ **Future**: Deferred to later phase - -### Core Features Status -| Feature | Status | Location | -|---------|--------|----------| -| EbookReader (page rendering, navigation) | ✅ Complete | [application.py](dreader/application.py) | -| Gesture handling (tap, swipe, pinch) | ✅ Complete | [gesture.py](dreader/gesture.py) | -| Navigation Overlay (unified TOC + Bookmarks) | ✅ Complete | [application.py](dreader/application.py), [examples/navigation_overlay_example.py](examples/navigation_overlay_example.py) | -| Settings Overlay | ✅ Complete | [application.py](dreader/application.py#L1318-L1349) | -| Highlighting system | ✅ Complete | [application.py](dreader/application.py#L1086-L1288) | -| Library management | ✅ Complete | [library.py](dreader/library.py) | -| State persistence | ✅ Complete | [state.py](dreader/state.py) | -| Persistent rendering settings | ✅ Complete | [examples/persistent_settings_example.py](examples/persistent_settings_example.py) | -| Book scanning & metadata | ✅ Complete | [book_utils.py](dreader/book_utils.py) | -| Mode transitions (LIBRARY ↔ READING) | 📖 Example Ready | [examples/library_reading_integration.py](examples/library_reading_integration.py) | -| Full application integration | 📖 Example Ready | All components working, needs main controller | -| Word Lookup Overlay | 🔜 Spec complete | Phase 2 | -| X-Ray feature | 🔜 Spec complete | Phase 2 | - -### Phase Completion -- **Phase 1 (MVP)**: **~95% complete** - All core features working with examples, needs production main controller -- **Phase 2**: ~60% complete - Unified navigation overlay done, settings persistence complete, word lookup planned -- **Phase 3**: 0% complete - Advanced features deferred - -### Working Examples -The following complete, runnable examples demonstrate full integration: - -| Example | Demonstrates | Status | -|---------|-------------|--------| -| [simple_ereader_example.py](examples/simple_ereader_example.py) | Basic reading, page navigation, bookmarks | ✅ Working | -| [library_reading_integration.py](examples/library_reading_integration.py) | **Full LIBRARY ↔ READING workflow** | ✅ Working | -| [persistent_settings_example.py](examples/persistent_settings_example.py) | Settings persistence across sessions | ✅ Working | -| [navigation_overlay_example.py](examples/navigation_overlay_example.py) | Unified navigation with tabs | ✅ Working | -| [demo_settings_overlay.py](examples/demo_settings_overlay.py) | Settings overlay interactions | ✅ Working | -| [word_selection_highlighting.py](examples/word_selection_highlighting.py) | Text highlighting | ✅ Working | - -**Key Integration Example**: [library_reading_integration.py](examples/library_reading_integration.py) demonstrates the complete user flow: -1. Display library of EPUB files -2. Select book by clicking/tapping -3. Open and read selected book -4. Access settings overlay -5. Return to library from settings -6. Select another book -7. Auto-resume from saved position - ---- - -## 1. Application Modes - -### 1.1 LIBRARY Mode ✅ (Implemented in library.py) -**Purpose**: Browse and select books from the user's library +### LIBRARY Mode +Browse and select books from the user's library. **Features**: -- Display grid/table of available books with cover images -- Show book metadata (title, author) -- Book selection via touch/click -- Visual feedback on selection (highlight, hover state) -- Support for scrolling/pagination if library is large -- Search/filter books (future enhancement) -- Sort options (by title, author, recent, etc.) (future enhancement) +- Grid display of books with covers and metadata (title, author) +- Book selection via tap/click +- Visual feedback on selection **Interactions**: -- **Tap/Click on book**: Transition to READING mode with selected book -- **Long-press on book**: Show book context menu (delete, info, etc.) (future) -- **Swipe**: Scroll library view +- **Tap book**: Open in READING mode +- **Swipe**: Scroll library (future) -**Display Requirements**: -- Cover images: 150-300px wide thumbnails -- Title/Author text below or beside cover -- Responsive layout for different screen sizes -- Loading indicator while scanning library - ---- - -### 1.2 READING Mode ✅ (Implemented in application.py) -**Purpose**: Read the current book with page navigation +### READING Mode +Read the current book with page navigation. **Features**: -- Display current page rendered by pyWebLayout +- Page rendering via pyWebLayout - Page navigation (next/previous) -- Access to overlays via buttons/gestures -- Visual progress indicator (page numbers, percentage) -- Header showing book title/author -- Footer with navigation controls +- Access to overlays +- Progress indicator **Interactions**: -- **Tap left/right edge**: Previous/Next page -- **Swipe left/right**: Previous/Next page -- **Tap center**: Toggle controls visibility (future) -- **Tap header buttons**: Open overlays (TOC, Settings, etc.) -- **Pinch in/out**: Decrease/Increase font size -- **Long-press on word**: Show definition/highlight menu (future) +- **Tap edges**: Previous/Next page +- **Swipe left/right**: Page navigation +- **Swipe up (from bottom)**: Open navigation overlay +- **Swipe down (from top)**: Open settings overlay +- **Pinch in/out**: Adjust font size +- **Long-press on word**: Highlight/lookup (future) -**Sub-states within READING mode**: -1. **Base reading state**: Just the page content -2. **TOC_OVERLAY**: Table of contents over page -3. **SETTINGS_OVERLAY**: Settings panel over page -4. **BOOKMARKS_OVERLAY**: Bookmarks list over page +## Overlay System ---- +### Navigation Overlay +Unified overlay with tabbed interface for navigation. -### 1.3 Overlay States +**Tabs**: +- **Contents**: Chapter list for TOC navigation +- **Bookmarks**: Saved positions with jump/delete/add -#### 1.3.1 TOC_OVERLAY ✅ (Implemented) -**Purpose**: Navigate to different chapters +**Interactions**: +- **Tap chapter/bookmark**: Jump to location, close overlay +- **Tap tab**: Switch between Contents and Bookmarks +- **Swipe down**: Close overlay + +### Settings Overlay +Adjust reading preferences with real-time preview. + +**Controls**: +- Font size (A-, A+) +- Line spacing (+/-) +- Block spacing (+/-) +- Word spacing (+/-) +- Back to Library button + +**Interactions**: +- **Tap buttons**: Adjust settings immediately +- **Swipe down**: Close overlay + +### Word Lookup Overlay (Planned - Phase 2) +Provide word definitions and contextual information. **Features**: -- Scrollable list of chapters -- Hierarchical chapter display (if available) -- Current chapter indication -- Click to jump to chapter +- **Dictionary**: Word definition, pronunciation +- **X-Ray**: LLM-generated spoiler-free character/place/concept information up to current reading position +- **Highlight**: Add colored highlight +- **Copy**: Copy to clipboard -**Interactions**: -- **Tap chapter**: Jump to chapter, close overlay, return to READING -- **Tap close/back**: Return to READING mode -- **Tap outside overlay**: Close overlay (optional) +**X-Ray Behavior**: +- Pre-generated per book via offline LLM analysis +- Only shows information revealed up to current page (spoiler-free) +- Character relationships, place descriptions, concept explanations +- Entity occurrence tracking -#### 1.3.2 SETTINGS_OVERLAY ✅ (Implemented) -**Purpose**: Adjust reading preferences - -**Features**: -- Font size controls (A-, A+) -- Line spacing controls -- Brightness controls (if hardware supports) -- Theme selection (day/night/sepia) (future) -- WiFi configuration (future) - -**Interactions**: -- **Tap buttons**: Adjust settings in real-time -- **Tap close**: Return to READING mode -- Changes persist across sessions - -#### 1.3.3 BOOKMARKS_OVERLAY ✅ (Implemented) -**Purpose**: Manage and navigate bookmarks - -**Features**: -- List of saved bookmarks with names -- Show bookmark position info (chapter, page) -- Delete bookmark option -- Add new bookmark option - -**Interactions**: -- **Tap bookmark**: Jump to bookmark, close overlay, return to READING -- **Tap delete**: Remove bookmark from list -- **Tap close**: Return to READING mode - -#### 1.3.4 WORD_LOOKUP_OVERLAY 🔜 (Planned for Phase 2) -**Purpose**: Provide word definitions and contextual analysis - -**Features**: -- **Dictionary Lookup**: Show word definition, pronunciation, etymology -- **X-Ray Feature**: Display LLM-generated contextual information about characters, places, and concepts - - Show **spoiler-free** summary of what has been revealed about this entity up to current reading position - - Character information: relationships, motivations, key events involving them (up to current page only) - - Place information: descriptions, significance, events that occurred there (up to current page only) - - Concept information: explanations, thematic importance (up to current page only) - - Pre-generated per book via LLM analysis (offline process), stored in cache - - Never reveals information from future chapters -- **Highlight Options**: Add permanent highlight to selected word with color choice -- **Copy to Clipboard**: Copy word or selection - -**Interactions**: -- **Tap "Dictionary"**: Show dictionary definition panel -- **Tap "X-Ray"**: Show spoiler-free contextual summary (if available for this entity) -- **Tap "Highlight"**: Add colored highlight, show color picker -- **Tap "Copy"**: Copy text to clipboard -- **Tap close/outside**: Return to READING mode - -**Display Layout**: -``` -┌─────────────────────────────────────┐ -│ Selected: "Sherlock Holmes" │ -├─────────────────────────────────────┤ -│ [Dictionary] [X-Ray] [Highlight] │ -├─────────────────────────────────────┤ -│ (Content area based on active tab) │ -│ │ -│ Dictionary Tab: │ -│ Definition, pronunciation, etc. │ -│ │ -│ X-Ray Tab: │ -│ SHERLOCK HOLMES (Character) │ -│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ -│ A consulting detective living in │ -│ London at 221B Baker Street. Known │ -│ for exceptional deductive reasoning│ -│ and observational skills. │ -│ │ -│ Key Relationships (so far): │ -│ • Dr. Watson - Companion, narrator │ -│ • Mrs. Hudson - Landlady │ -│ │ -│ Mentioned: 23 times (Chapters 1-3) │ -│ [Info current as of your position] │ -└─────────────────────────────────────┘ -``` - -**Data Requirements**: -- X-Ray data file per book (JSON format) - - Generated via LLM chapter-by-chapter analysis (offline process) - - Structure: `{entity_name: {chapter_N: {summary, relationships, ...}}}` - - Lookup uses current reading position to filter out future content -- Dictionary API integration (local or online) -- Entity recognition (match selected word to X-Ray entities) - ---- - -## 2. State Management & Persistence ✅ (Implemented in state.py) - -### 2.1 State Definition -The application must track and persist the following state: - -```python -class EreaderMode(Enum): - LIBRARY = "library" - READING = "reading" - -class OverlayState(Enum): - NONE = "none" - TOC = "toc" - SETTINGS = "settings" - BOOKMARKS = "bookmarks" - WORD_LOOKUP = "word_lookup" -``` - -### 2.2 Persistent State Data Structure - -**Key Insight**: EbookReader already manages bookmarks and positions via `bookmarks_dir`. Our state only needs to track high-level application state, not page positions. +## State Management +### Persistent State Structure ```json { - "version": "1.0", - "mode": "reading", - "overlay": "none", + "mode": "reading|library", + "overlay": "none|navigation|settings|word_lookup", "current_book": { "path": "/path/to/book.epub", "title": "Book Title", - "author": "Author Name", - "last_read_timestamp": "2025-11-07T10:30:00Z" + "author": "Author Name" }, "library": { "books_path": "/path/to/library", - "last_selected_index": 3, - "scan_cache": [ - { - "path": "/path/to/book.epub", - "title": "Book Title", - "author": "Author", - "cover_cached": true, - "last_modified": "2025-11-01T12:00:00Z" - } - ] + "scan_cache": [...] }, "settings": { "font_scale": 1.0, "line_spacing": 5, "inter_block_spacing": 15, - "brightness": 8, - "theme": "day" + "brightness": 8 } } ``` -**What's NOT in state (handled by EbookReader)**: -- Current page position → `EreaderLayoutManager.current_position` -- Bookmarks → `bookmarks_dir/{document_id}_{bookmark_name}.json` -- Reading progress → Calculated on-demand by `reader.get_reading_progress()` +**State Location**: `~/.config/dreader/state.json` -**Auto-Resume Strategy**: -- Use special bookmark name `"__auto_resume__"` for last position -- Bookmark files are per-book: `{document_id}___auto_resume__.json` -- On shutdown: `reader.save_position("__auto_resume__")` -- On startup: `reader.load_position("__auto_resume__")` -- No mixing between books (each has its own document_id) +**Save Triggers**: +- Every 60 seconds (auto-save) +- On mode change +- On settings change +- On application shutdown -### 2.3 State Persistence Requirements -- **Location**: - - Linux/Mac: `~/.config/dreader/state.json` - - Windows: `%APPDATA%/dreader/state.json` -- **Save Triggers**: - - On mode change - - On page turn (debounced to avoid excessive writes) - - On settings change - - On bookmark add/delete - - On application close -- **Load Triggers**: - - On application startup - - After crash/power loss (automatic recovery) -- **Safety**: - - Atomic writes (write to temp file, then rename) - - Validation on load (schema check, corrupt data handling) - - Backup previous state file - - Handle missing/corrupt state gracefully +**Boot Behavior**: +- **Cold start**: Show library +- **Resume**: Reopen last book at saved position with restored settings +- **Error handling**: Fall back to library if book missing or state corrupt -### 2.4 Boot/Resume Behavior -1. **Cold Start** (no state file): - - Start in LIBRARY mode - - Default settings applied +### Position Persistence +- Per-book positions stored via EbookReader bookmark system +- Special bookmark `__auto_resume__` for last reading position +- Position stable across font size and spacing changes -2. **Resume from State**: - - If last mode was LIBRARY: Return to library view - - If last mode was READING: Reopen last book at last page - - Restore all settings (font size, spacing, etc.) - - Clear overlay state (always start without overlay) +## Library Management -3. **Error Handling**: - - If last book no longer exists: Return to LIBRARY mode - - If state file corrupt: Start fresh, backup corrupt file - - Log all state errors for debugging +**Features**: +- Scan directory for EPUB files +- Extract metadata (title, author) and cover images +- Cache covers to disk for performance +- Incremental updates (scan only new/modified files) ---- - -## 3. Interaction Model ✅ (Implemented via gesture.py + application.handle_touch) - -### 3.1 Touch/Click Callbacks -The application must support callbacks/handlers for: - -**LIBRARY Mode**: -- `on_book_selected(book_path)` - Open book in READING mode -- `on_library_scroll(direction)` - Scroll library view - -**READING Mode**: -- `on_page_turn(direction)` - Next/Previous page -- `on_tap_region(x, y, region)` - Handle tap on specific region - - Regions: left-edge, right-edge, center, header, footer -- `on_button_press(button_id)` - Handle button clicks - - Buttons: toc, settings, bookmarks, library -- `on_zoom_gesture(direction)` - Pinch in/out for font size -- `on_word_tap(x, y)` - Single tap on word for quick highlight -- `on_word_long_press(x, y)` - Long press on word for lookup overlay - -**Overlay Mode**: -- `on_chapter_selected(chapter_index)` - Jump to chapter -- `on_setting_changed(setting_name, value)` - Update settings -- `on_bookmark_selected(bookmark_name)` - Jump to bookmark -- `on_word_lookup_action(action, word)` - Handle word lookup actions - - Actions: dictionary, xray, highlight, copy -- `on_xray_occurrence_selected(position)` - Jump to word occurrence -- `on_overlay_close()` - Return to READING mode - -### 3.2 Interactive Elements in Rendered Content -For the library view rendered via pyWebLayout: -- Each book cover/title should be a clickable element -- Use pyWebLayout's link/interactive features to make covers tappable -- Query pixel coordinates to determine which book was clicked -- Map clicks to book indices - -**Implementation Options**: -1. **Bounding Box Approach**: Store bounding boxes for each book element, detect clicks -2. **HTML Links**: Embed book paths as links in HTML, use link query API -3. **Table Cell IDs**: Use table cell metadata to identify clicked books - ---- - -## 4. Library Management ✅ (Implemented in book_utils.py + library.py) - -### 4.1 Book Scanning -- Scan specified directory for EPUB files -- Extract metadata (title, author) using pyWebLayout's EPUB reader -- Extract cover images (or use first page as fallback) -- Cache metadata to avoid re-scanning on every boot -- Support for incremental updates (only scan new/modified files) - -### 4.2 Cover Image Handling -**Options**: -1. **Base64 Embedded**: Encode covers as base64 in HTML (current approach) -2. **File-based**: Save covers to cache directory, reference by path -3. **Hybrid**: Cache on disk, fall back to base64 for generation - -**Recommendation**: File-based caching -- Faster page generation -- Less memory usage -- Reusable across sessions - -### 4.3 Library Cache Structure +**Cache Structure**: ``` ~/.config/dreader/ ├── state.json # Application state ├── covers/ # Cached cover images -│ ├── book1_cover.png -│ ├── book2_cover.png -│ └── ... -├── bookmarks/ # Bookmark data per book -│ ├── book1.json -│ └── book2.json -├── highlights/ # Highlight data per book -│ ├── book1.json -│ └── book2.json -└── xray/ # X-Ray data per book (LLM-generated) - ├── book1_xray.json - └── book2_xray.json +├── bookmarks/ # Per-book bookmarks +├── highlights/ # Per-book highlights +└── xray/ # X-Ray data (future) ``` -**X-Ray Data Format Example**: -```json -{ - "version": "1.0", - "book_id": "sherlock_holmes_adventures", - "generated_date": "2025-11-08", - "entities": { - "Sherlock Holmes": { - "type": "character", - "chapters": { - "1": { - "summary": "Introduced as a consulting detective...", - "relationships": ["Dr. Watson"], - "traits": ["observant", "logical"], - "key_events": ["Met Watson", "Moved to Baker Street"] - }, - "2": { - "summary": "Demonstrates deductive method...", - "relationships": ["Dr. Watson", "Inspector Lestrade"], - "traits": ["observant", "logical", "unconventional"], - "key_events": ["Solved first case"] - } - } - }, - "221B Baker Street": { - "type": "place", - "chapters": { - "1": { - "summary": "Holmes and Watson's shared lodgings", - "significance": "Home base for investigations" - } - } - } - } -} -``` +## Gesture Handling ---- +### Reading Mode Gestures +- `TAP`: Word selection, link following, page turn (edges) +- `SWIPE_LEFT/RIGHT`: Page navigation +- `SWIPE_UP` (from bottom 20%): Open navigation overlay +- `SWIPE_DOWN` (from top 20%): Open settings overlay +- `PINCH_IN/OUT`: Font size adjustment +- `LONG_PRESS`: Word lookup (future) -## 5. Technical Architecture ✅ (Core modules implemented) +### Overlay Mode Gestures +- `TAP`: Interact with overlay elements +- `SWIPE_DOWN`: Close overlay -### 5.1 Module Structure -``` -dreader/ -├── __init__.py # ✅ Exports -├── application.py # ✅ EbookReader class with gesture handling -├── state.py # ✅ State management with asyncio auto-save -├── library.py # ✅ Library mode logic with interactive selection -├── book_utils.py # ✅ Book scanning utilities -├── html_generator.py # ✅ HTML generation for UI -├── gesture.py # ✅ Touch/gesture event handling -└── overlay.py # ✅ Overlay rendering and compositing -``` -**Note**: No separate callbacks.py - gesture handling integrated into application.py via handle_touch() method +### Library Mode Gestures +- `TAP`: Select book -### 5.2 State Manager API (`dreader/state.py`) -```python -class StateManager: - def __init__(self, state_file_path: str) - def load_state() -> AppState - def save_state(state: AppState) - def get_current_mode() -> EreaderMode - def set_mode(mode: EreaderMode) - def get_current_book() -> Optional[BookState] - def set_current_book(book: BookState) - def get_settings() -> Settings - def update_setting(key: str, value: Any) -``` +## Technical Requirements -### 5.3 Library Manager API (`dreader/library.py`) -```python -class LibraryManager: - def __init__(self, library_path: str, cache_dir: str) - def scan_library() -> List[Book] - def get_cached_metadata() -> List[Book] - def refresh_metadata(book_path: str) - def get_book_at_index(index: int) -> Optional[Book] - def render_library_view() -> Image.Image - def handle_library_click(x: int, y: int) -> Optional[str] # Returns book path -``` +### Performance Targets +- Boot time: < 3 seconds +- Page turn: < 200ms +- Library load: < 1 second (up to 100 books) +- State save: < 50ms (non-blocking) -### 5.4 Callback Manager API (`dreader/callbacks.py`) -```python -class CallbackManager: - def __init__(self, reader: EbookReader, state_manager: StateManager, library_manager: LibraryManager) - def handle_interaction(event: InteractionEvent) -> Response - def on_book_selected(book_path: str) - def on_page_turn(direction: str) - def on_overlay_open(overlay_type: OverlayState) - def on_overlay_close() - def on_settings_change(setting: str, value: Any) -``` - ---- - -## 6. HAL (Hardware Abstraction Layer) Integration - -### 6.1 HAL Responsibilities -The HAL will handle: -- Rendering images to the display -- Capturing touch/click input -- Sending interaction events to the application -- Hardware-specific features (brightness control, WiFi, etc.) - -### 6.2 Application ↔ HAL Interface -**From Application to HAL**: -- `render_image(image: PIL.Image, layer: str)` - Display image on specified layer - - Layers: "base" (page/library), "overlay" (settings/TOC/etc.) -- `set_brightness(level: int)` - Adjust screen brightness -- `show_loading(message: str)` - Display loading indicator - -**From HAL to Application**: -- `on_touch(x: int, y: int, gesture: GestureType)` - Touch event -- `on_button_press(button_id: str)` - Hardware button press (if any) -- `on_power_event(event: PowerEvent)` - Sleep/wake/shutdown - -### 6.3 Multi-Layer Rendering -To support overlays efficiently: -1. **Base Layer**: Main content (library view or reading page) -2. **Overlay Layer**: Semi-transparent overlays (TOC, settings, etc.) -3. **HAL Compositing**: HAL combines layers for display - -**Alternative**: Application composites layers and sends single image to HAL - ---- - -## 7. Future Enhancements - -### 7.1 Phase 2 Features -- Full-text search within books -- ✅ Highlighting and annotations (basic implementation exists) -- Word lookup overlay with dictionary and X-Ray features -- Night/sepia reading themes -- WiFi configuration UI -- OTA (over-the-air) updates -- Cloud sync for bookmarks/progress -- PDF support - -### 7.2 Phase 3 Features -- Multiple library folders -- Collections/categories -- Reading statistics -- Social features (sharing quotes, etc.) -- Text-to-speech -- Translation support - ---- - -## 8. Testing Requirements - -### 8.1 Unit Tests -- State serialization/deserialization -- State file corruption handling -- Library scanning with various EPUB formats -- Callback routing logic -- Click detection and bounding box calculations - -### 8.2 Integration Tests -- Full mode transitions (LIBRARY → READING → OVERLAY → READING → LIBRARY) -- State persistence across "reboots" (save → load → verify) -- Multi-book workflow (open book A, switch to book B, resume book A) -- Settings changes reflected in rendering - -### 8.3 Manual/UI Tests -- Touch responsiveness on target hardware -- Overlay appearance and dismissal -- Page turn performance -- Library scrolling smoothness -- Boot time from power-on to usable state - ---- - -## 9. Performance Requirements - -- **Boot Time**: < 3 seconds to display library or resume reading -- **Page Turn**: < 200ms from gesture to new page display -- **Library Load**: < 1 second for libraries up to 100 books -- **State Save**: < 50ms (non-blocking if possible) -- **Memory**: < 100MB RAM for typical book (depends on images) - ---- - -## 10. Implementation Priority - -### Phase 1 (MVP): -1. ✅ Basic EbookReader class (application.py - complete) -2. ✅ HTML generation for reading view (html_generator.py - complete) -3. ✅ Book scanning and metadata extraction (book_utils.py - complete) -4. ✅ State management module with persistence (state.py - complete) -5. ✅ Library manager with clickable book selection (library.py - complete) -6. ✅ Mode transitions (LIBRARY ↔ READING) - **FULLY DEMONSTRATED** in [examples/library_reading_integration.py](examples/library_reading_integration.py) -7. ✅ Basic overlays (TOC, Settings) - overlay.py + application.py methods complete -8. ✅ Gesture/touch handling (gesture.py - complete with TouchEvent, GestureType, GestureResponse) -9. ✅ Full application integration - **WORKING EXAMPLES EXIST**, needs production main.py controller - -### Phase 2: -1. ✅ Bookmarks overlay with management (application.py:1351-1377 - complete) -2. ✅ Settings persistence and real-time updates (FULLY COMPLETE) - - ✅ Settings overlay with real-time preview (application.py:1318-1349) - - ✅ Settings saved to state.json (state.py:79-103) - - ✅ Settings restored on app startup via apply_settings() (application.py:725-765) - - ✅ Includes: font_scale, line_spacing, inter_block_spacing, word_spacing - - ✅ Example: examples/persistent_settings_example.py -3. ✅ Boot recovery and resume functionality (FULLY TESTED) - - ✅ Position restoration via __auto_resume__ bookmark (tests/test_boot_recovery.py:49-98) - - ✅ Settings restoration across sessions (tests/test_boot_recovery.py:100-153) - - ✅ Bookmark persistence (tests/test_boot_recovery.py:155-200) - - ✅ Full state workflow (tests/test_boot_recovery.py:202-277) - - ✅ Multiple books with separate state (tests/test_boot_recovery.py:279-343) - - ✅ Corrupt state file recovery (tests/test_boot_recovery.py:345-362) - - ✅ Missing book handling (tests/test_boot_recovery.py:364-393) - - ✅ Cold start with no state (tests/test_boot_recovery.py:395-414) - - ✅ Async auto-save functionality (tests/test_boot_recovery.py:516-586) -4. 🚧 Error handling and user feedback -5. 🚧 Performance optimization -6. 🔜 Word lookup overlay (Dictionary + X-Ray feature) - spec complete, not implemented -7. ✅ Unified navigation overlay (TOC + Bookmarks tabs) - COMPLETE - - ✅ Tabbed interface combining Contents and Bookmarks (html_generator.py:507-637) - - ✅ Tab switching without closing overlay (overlay.py:459-481, application.py:1172-1186) - - ✅ Gesture handling for all interactions (application.py:828-904) - - ✅ New OverlayState.NAVIGATION state (state.py:33) - - ✅ Tests and example provided (tests/test_navigation_overlay.py, examples/navigation_overlay_example.py) - - ✅ Documentation: NAVIGATION_OVERLAY_IMPLEMENTATION.md - -### Phase 3: -1. Advanced library features (search, sort, filter) -2. Advanced highlighting (color picker, annotation notes) -3. Enhanced X-Ray (character relationships, concept maps) -4. Themes and customization -5. Offline dictionary database -6. Word history and vocabulary tracking - ---- - -## Resolved Design Decisions - -### 1. Click Detection in Library View ✓ -**Decision**: Use pyWebLayout's built-in `is_interactive` and `link_target` properties -- Each book element is marked as interactive with book path as link_target -- Query pixel on tap to get clicked book -- Same pattern as in-book link handling (application.py:748-769) - -### 2. Multi-Layer Rendering ✓ -**Decision**: Application-side compositing -- Application renders base layer (library or reading page) -- For overlays: Composite smaller overlay image onto base layer -- Remove overlay by re-rendering the existing base page -- On settings change: Re-render background, then re-composite overlay if active -- Single final image sent to HAL for display - -### 3. State Save Frequency ✓ -**Decision**: Timer-based with asyncio -- Auto-save every 60 seconds (configurable) -- Immediate save on: - - Book closed - - Device shutdown - - Mode change - - Settings change -- Use asyncio timer loop for automatic saves (non-blocking) - -### 4. Cover Image Strategy -**Decision**: File-based cache (preferred) -- Cache covers to `~/.config/dreader/covers/` -- Generate on first scan, reuse on subsequent boots -- Fallback to base64 if cache unavailable - -### 5. HAL Integration -**Decision**: HAL provides gesture recognition -- HAL sends TouchEvent with GestureType (TAP, SWIPE, etc.) -- Application handles business logic via gesture handlers -- Application returns GestureResponse with action type - ---- - -## 11. Main Application Controller (Final Integration Piece) - -### 11.1 Current Status -All core components are **fully functional and tested** with working integration examples. What's needed is a production-ready main application controller to orchestrate these components. - -### 11.2 Reference Implementation -The file [examples/library_reading_integration.py](examples/library_reading_integration.py) demonstrates the complete integration pattern and serves as a reference for building the main controller. - -### 11.3 Required Main Controller Components - -#### A. Application Class Structure -```python -class DReaderApplication: - """ - Main application controller coordinating library and reading modes. - - Responsibilities: - - Mode management (LIBRARY ↔ READING transitions) - - Component lifecycle (LibraryManager, EbookReader) - - State persistence integration - - Event routing to appropriate handlers - - Display updates - """ - - def __init__(self, config: AppConfig): - # State management - self.state_manager = StateManager() - self.state = self.state_manager.load_state() - - # Components (lazy-initialized) - self.library: Optional[LibraryManager] = None - self.reader: Optional[EbookReader] = None - - # Display abstraction - self.display_hal = config.display_hal - self.current_image: Optional[Image.Image] = None - - def start(self): - """Initialize and show initial screen based on saved state""" - - def handle_touch(self, event: TouchEvent): - """Route touch events to library or reader based on mode""" - - def shutdown(self): - """Clean shutdown with state preservation""" -``` - -#### B. Mode Transition Logic -```python -def _enter_library_mode(self): - """Switch to library browsing""" - # 1. Save and close reader if active - if self.reader: - self.reader.save_position("__auto_resume__") - self.reader.close() - self.reader = None - - # 2. Initialize library - if not self.library: - self.library = LibraryManager(...) - - # 3. Render library view - self.current_image = self.library.render_library() - self.state_manager.set_mode(EreaderMode.LIBRARY) - self._update_display() - -def _enter_reading_mode(self, book_path: str): - """Switch to reading mode""" - # 1. Initialize reader if needed - if not self.reader: - self.reader = EbookReader(...) - - # 2. Load book - self.reader.load_epub(book_path) - - # 3. Apply saved settings - self.reader.apply_settings(self.state.settings.to_dict()) - - # 4. Restore position - self.reader.load_position("__auto_resume__") - - # 5. Update state - self.state_manager.set_current_book(BookState(...)) - self.state_manager.set_mode(EreaderMode.READING) - - # 6. Render page - self.current_image = self.reader.get_current_page() - self._update_display() -``` - -#### C. Event Handling Integration -```python -def handle_touch(self, event: TouchEvent): - """Process touch based on current mode""" - if self.state.mode == EreaderMode.LIBRARY: - self._handle_library_touch(event) - elif self.state.mode == EreaderMode.READING: - self._handle_reading_touch(event) - - self._update_display() - -def _handle_library_touch(self, event: TouchEvent): - """Library mode touch handling""" - if event.gesture == GestureType.TAP: - book_path = self.library.handle_library_tap(event.x, event.y) - if book_path: - self._enter_reading_mode(book_path) - -def _handle_reading_touch(self, event: TouchEvent): - """Reading mode touch handling""" - response = self.reader.handle_touch(event) - - # Handle special actions - if response.action == ActionType.BACK_TO_LIBRARY: - self._enter_library_mode() - elif response.action == ActionType.PAGE_TURN: - self.current_image = self.reader.get_current_page() - elif response.action == ActionType.OVERLAY_OPENED: - self.current_image = self.reader.get_current_page() - elif response.action == ActionType.SETTING_CHANGED: - # Settings changed, update state - settings = self.reader.get_current_settings() - self.state_manager.update_settings(settings) -``` - -#### D. State Persistence Integration -```python -def start(self): - """Boot sequence with state restoration""" - # 1. Start auto-save - self.state_manager.start_auto_save() - - # 2. Restore previous mode - if self.state.mode == EreaderMode.READING and self.state.current_book: - # Resume reading last book - self._enter_reading_mode(self.state.current_book.path) - else: - # Show library - self._enter_library_mode() - - # 3. Display initial screen - self._update_display() - -def shutdown(self): - """Graceful shutdown""" - # 1. Save current position if reading - if self.reader and self.reader.is_loaded(): - self.reader.save_position("__auto_resume__") - self.reader.close() - - # 2. Stop auto-save and save final state - asyncio.run(self.state_manager.stop_auto_save(save_final=True)) -``` - -### 11.4 Display HAL Interface - -The main controller needs a display abstraction layer (HAL). This is **platform-specific** and not part of the core dreader library. - -**Required HAL Interface:** +### Platform Integration +Application requires a display HAL (Hardware Abstraction Layer): ```python class DisplayHAL(ABC): - """Abstract display interface for platform integration""" - - @abstractmethod - def show_image(self, image: Image.Image): - """Display a PIL Image on the screen""" - - @abstractmethod - def get_touch_events(self) -> Iterator[TouchEvent]: - """Get iterator of touch events from hardware""" - - @abstractmethod - def set_brightness(self, level: int): - """Set display brightness (0-10)""" + def show_image(image: Image.Image) + def get_touch_events() -> Iterator[TouchEvent] + def set_brightness(level: int) ``` -**Example Implementations:** -- **E-Ink Display**: Use device-specific SDK (e.g., IT8951, Remarkable) -- **Desktop Testing**: Use PIL + tkinter or pygame -- **Web Interface**: Use Flask + HTML canvas -- **Qt Application**: Use QPixmap + QTouchEvent +## Implementation Phases -### 11.5 Implementation Checklist +### Phase 1 (MVP) - Complete ✅ +- Core reading (page navigation, bookmarks) +- Library browsing and book selection +- Navigation overlay (TOC + Bookmarks) +- Settings overlay with persistence +- State management and auto-resume +- Gesture handling +- Highlighting system -To complete the main application controller: +### Phase 2 - In Progress +- Word lookup overlay with dictionary +- X-Ray feature (spoiler-free contextual info) +- Enhanced library features (search, sort) -- [ ] Create `dreader/main.py` with `DReaderApplication` class -- [ ] Implement mode transition methods (`_enter_library_mode`, `_enter_reading_mode`) -- [ ] Implement event routing (`handle_touch`) -- [ ] Implement boot/resume logic (`start`) -- [ ] Implement graceful shutdown (`shutdown`) -- [ ] Create platform-specific DisplayHAL implementation -- [ ] Create configuration system (AppConfig class) -- [ ] Add command-line argument parsing -- [ ] Create systemd service file (for embedded systems) -- [ ] Add logging throughout controller -- [ ] Write integration tests for main controller -- [ ] Create user documentation - -### 11.6 Platform-Specific Entry Points - -Once the main controller exists, create platform-specific entry points: - -**Desktop (Pygame):** -```python -# desktop_app.py -from dreader.main import DReaderApplication -from hal.pygame_display import PygameDisplayHAL - -def main(): - config = AppConfig( - display_hal=PygameDisplayHAL(width=800, height=1200), - library_path="~/Books", - page_size=(800, 1200) - ) - - app = DReaderApplication(config) - app.start() - - # Event loop - running = True - while running: - for event in app.display_hal.get_touch_events(): - app.handle_touch(event) - - app.shutdown() -``` - -**E-Ink Device:** -```python -# eink_app.py -from dreader.main import DReaderApplication -from hal.eink_display import EinkDisplayHAL - -def main(): - config = AppConfig( - display_hal=EinkDisplayHAL(device="/dev/epd"), - library_path="/mnt/books", - page_size=(1200, 1600) - ) - - app = DReaderApplication(config) - app.start() - - # Hardware event loop - app.display_hal.run_event_loop(app.handle_touch) - - app.shutdown() -``` - -**Web Interface:** -```python -# web_app.py -from flask import Flask, render_template, jsonify, request -from dreader.main import DReaderApplication -from hal.web_display import WebDisplayHAL - -app = DReaderApplication(...) - -@app.route('/touch', methods=['POST']) -def handle_touch(): - x, y = request.json['x'], request.json['y'] - gesture = request.json['gesture'] - event = TouchEvent(gesture, x, y) - app.handle_touch(event) - return jsonify(success=True) - -@app.route('/current_image') -def get_image(): - return send_image(app.current_image) -``` - ---- - -## Acceptance Criteria - -The implementation is complete when: -1. User can browse library and select a book with a single tap -2. Selected book opens at last read position (or first page if new) -3. User can turn pages forward/backward via tap or swipe -4. User can open TOC and jump to any chapter -5. User can adjust font size and see changes immediately -6. User can add/remove bookmarks and navigate to them -7. Application state persists across reboots -8. Application resumes exactly where user left off after power cycle -9. All features work on target hardware with acceptable performance -10. No data loss on unexpected shutdown/crash +### Phase 3 - Future +- Night/sepia themes +- Full-text search within books +- Cloud sync for bookmarks +- PDF support +- Reading statistics diff --git a/dreader/hal_pygame.py b/dreader/hal_pygame.py index 3ede1c0..a7f3b03 100644 --- a/dreader/hal_pygame.py +++ b/dreader/hal_pygame.py @@ -200,6 +200,8 @@ class PygameDisplayHAL(EventLoopHAL): # Detect gesture type gesture = None + # For swipe gestures, use the starting position (mouse_down_pos) + # For tap/long-press, use the ending position (mouse_up_pos) x, y = mouse_up_pos if distance < self.drag_threshold: @@ -211,7 +213,8 @@ class PygameDisplayHAL(EventLoopHAL): gesture = GestureType.TAP logger.info(f"[GESTURE] ✓ Detected: TAP") else: - # Swipe + # Swipe - use starting position for location-based checks + x, y = self.mouse_down_pos if abs(dx) > abs(dy): # Horizontal swipe if dx > 0: @@ -233,6 +236,8 @@ class PygameDisplayHAL(EventLoopHAL): self.mouse_down_pos = None if gesture: + # For swipe gestures, (x,y) is the start position + # For tap/long-press, (x,y) is the tap position logger.info(f"[EVENT] Returning TouchEvent: {gesture.value} at ({x}, {y})") return TouchEvent(gesture, x, y) else: diff --git a/examples/LIBRARY_DEMO.md b/examples/LIBRARY_DEMO.md deleted file mode 100644 index f9e2df4..0000000 --- a/examples/LIBRARY_DEMO.md +++ /dev/null @@ -1,164 +0,0 @@ -# Library Reading Demo - -This directory contains scripts to demonstrate the complete LIBRARY ↔ READING workflow for the dreader e-reader application. - -## Demo GIF - -**File**: [`doc/images/library_reading_demo.gif`](../doc/images/library_reading_demo.gif) (591 KB, 800x1200 pixels) - -### What the Demo Shows - -The animated GIF demonstrates the complete user workflow: - -1. **Library View** (2s) - - Shows a grid of available books - - Title: "📚 My Library - Select a book" - -2. **Book Selection** (1.5s) - - Visual tap indicator on the first book - - Shows where user taps to select - -3. **Reading Pages** (5 frames, ~5s total) - - Opens "Alice's Adventures in Wonderland" - - Shows 5 consecutive pages - - Page turns are animated - - Progress shown in header - -4. **Settings Overlay** (2s) - - Shows settings panel with font controls - - Highlights "Back to Library" button - - Visual tap indicator showing where to click - -5. **Return to Library** (2s) - - Book closes, position saved automatically - - Library view shown again - - Annotation: "Back to Library (position saved)" - -6. **Reopen Book** (1.5s) - - User taps same book again - - Visual indicator shows reselection - -7. **Auto-Resume** (3s) - - Book opens at saved position (24.6% progress) - - Shows the exact page where user left off - - Annotation: "✅ Auto-resumed at 24.6%" - -**Total Duration**: ~17 seconds (looping) - -## Scripts - -### `generate_library_demo_gif.py` - -Generate the demo GIF showing the complete workflow. - -**Usage**: -```bash -python generate_library_demo_gif.py path/to/library [output.gif] -``` - -**Examples**: -```bash -# Generate to default location (doc/images/library_reading_demo.gif) -python generate_library_demo_gif.py tests/data/library-epub/ - -# Generate to custom location -python generate_library_demo_gif.py tests/data/library-epub/ doc/images/custom_demo.gif -``` - -**Features**: -- Automatic book scanning and cover extraction -- Visual tap indicators showing user interactions -- Annotations explaining each step -- Configurable frame durations -- Auto-resume demonstration - -### `library_reading_integration.py` - -Comprehensive integration test for the library ↔ reading workflow. - -**Usage**: -```bash -python library_reading_integration.py path/to/library -``` - -**What it Tests**: -1. Library scanning and rendering -2. Book selection via tap -3. Book loading and reading -4. Page navigation (swipe gestures) -5. Settings overlay -6. Settings adjustments (font size) -7. Back to library button -8. Auto-resume functionality -9. Multiple book selection - -**Output**: Generates PNG images for each step (8 images total) - -## Implementation Status - -### ✅ Complete Features - -- **Library Management** ([library.py](../dreader/library.py)) - - Book scanning and metadata extraction - - Cover image caching - - Interactive book selection - - Clickable book rows - -- **Reading Mode** ([application.py](../dreader/application.py)) - - EPUB rendering - - Page navigation (swipe, tap) - - Progress tracking - - Position saving/loading - -- **State Persistence** ([state.py](../dreader/state.py)) - - Auto-save on page turn - - Resume at last position - - Settings persistence - - Per-book bookmarks - -- **Overlays** - - TOC (Table of Contents) - ✅ Working - - Settings - ✅ Working - - Bookmarks - ✅ Working - -### 🚧 Known Issues - -- **HTML Link Interactivity**: The "Back to Library" button in settings overlay doesn't respond to taps - - Root cause documented in [HTML_LINKS_INVESTIGATION.md](../HTML_LINKS_INVESTIGATION.md) - - Workaround: Will be implemented using programmatic UI generation - - Does not affect the demo GIF (which shows the intended workflow) - -## Requirements - -- Python 3.8+ -- PIL/Pillow (for image generation) -- dreader application -- pyWebLayout library -- Test EPUB files in the library directory - -## File Sizes - -- Demo GIF: ~622 KB (optimized for quality) -- Integration test PNGs: ~50-170 KB each -- Total demo assets: <2 MB - -## Demo Generation Time - -- Library scanning: <1 second -- EPUB loading: <1 second -- Page rendering: ~0.5 seconds per page -- Total: ~10-15 seconds to generate complete GIF - -## Use Cases - -1. **Documentation**: Visual demonstration of application features -2. **Testing**: Verify complete workflow end-to-end -3. **Presentations**: Show stakeholders the user experience -4. **Debugging**: Identify issues in the workflow -5. **Training**: Help users understand the application flow - -## See Also - -- [REQUIREMENTS.md](../REQUIREMENTS.md) - Full application requirements -- [HTML_GENERATION.md](../HTML_GENERATION.md) - HTML rendering documentation -- [HTML_LINKS_INVESTIGATION.md](../HTML_LINKS_INVESTIGATION.md) - Link interactivity debugging diff --git a/examples/README_EREADER.md b/examples/README_EREADER.md deleted file mode 100644 index b8ed104..0000000 --- a/examples/README_EREADER.md +++ /dev/null @@ -1,421 +0,0 @@ -# EbookReader - Simple EPUB Reader Application - -The `EbookReader` class provides a complete, user-friendly interface for building ebook reader applications with pyWebLayout. It wraps all the complex ereader infrastructure into a simple API. - -## Features - -- 📖 **EPUB Loading** - Load EPUB files with automatic content extraction -- ⬅️➡️ **Page Navigation** - Forward and backward page navigation -- 🔖 **Position Management** - Save/load reading positions (stable across font changes) -- 📑 **Chapter Navigation** - Jump to chapters by title or index -- 🔤 **Font Size Control** - Increase/decrease font size with live re-rendering -- 📏 **Spacing Control** - Adjust line, block, and word spacing -- 💾 **Persistent Settings** - Save and restore rendering preferences across sessions -- 📊 **Progress Tracking** - Get reading progress and position information -- 🎨 **Text Highlighting** - Highlight words and passages with colors -- 📋 **Overlays** - TOC, Settings, and Bookmarks overlays -- 🖱️ **Gesture Support** - Handle tap, swipe, pinch gestures -- 💾 **Context Manager Support** - Automatic cleanup with `with` statement - -## Quick Start - -```python -from pyWebLayout.layout.ereader_application import EbookReader - -# Create reader -reader = EbookReader(page_size=(800, 1000)) - -# Load an EPUB -reader.load_epub("mybook.epub") - -# Get current page as PIL Image -page_image = reader.get_current_page() -page_image.save("current_page.png") - -# Navigate -reader.next_page() -reader.previous_page() - -# Close reader -reader.close() -``` - -## API Reference - -### Initialization - -```python -reader = EbookReader( - page_size=(800, 1000), # Page dimensions (width, height) in pixels - margin=40, # Page margin in pixels - background_color=(255, 255, 255), # RGB background color - line_spacing=5, # Line spacing in pixels - inter_block_spacing=15, # Space between blocks in pixels - bookmarks_dir="ereader_bookmarks", # Directory for bookmarks - buffer_size=5 # Number of pages to cache -) -``` - -### Loading EPUB - -```python -# Load EPUB file -success = reader.load_epub("path/to/book.epub") - -# Check if book is loaded -if reader.is_loaded(): - print("Book loaded successfully") - -# Get book information -book_info = reader.get_book_info() -# Returns: { -# 'title': 'Book Title', -# 'author': 'Author Name', -# 'document_id': 'book', -# 'total_blocks': 5000, -# 'total_chapters': 20, -# 'page_size': (800, 1000), -# 'font_scale': 1.0 -# } -``` - -### Page Navigation - -```python -# Get current page as PIL Image -page = reader.get_current_page() - -# Navigate to next page -page = reader.next_page() # Returns None at end of book - -# Navigate to previous page -page = reader.previous_page() # Returns None at beginning - -# Save current page to file -reader.render_to_file("page.png") -``` - -### Position Management - -Positions are saved based on abstract document structure (chapter/block/word indices), making them stable across font size and styling changes. - -```python -# Save current position -reader.save_position("my_bookmark") - -# Load saved position -page = reader.load_position("my_bookmark") - -# List all saved positions -positions = reader.list_saved_positions() -# Returns: ['my_bookmark', 'chapter_2', ...] - -# Delete a position -reader.delete_position("my_bookmark") - -# Get detailed position info -info = reader.get_position_info() -# Returns: { -# 'position': {'chapter_index': 0, 'block_index': 42, 'word_index': 15, ...}, -# 'chapter': {'title': 'Chapter 1', 'level': 'H1', ...}, -# 'progress': 0.15, # 15% through the book -# 'font_scale': 1.0, -# 'book_title': 'Book Title', -# 'book_author': 'Author Name' -# } - -# Get reading progress (0.0 to 1.0) -progress = reader.get_reading_progress() -print(f"You're {progress*100:.1f}% through the book") -``` - -### Chapter Navigation - -```python -# Get all chapters -chapters = reader.get_chapters() -# Returns: [('Chapter 1', 0), ('Chapter 2', 1), ...] - -# Get chapters with positions -chapter_positions = reader.get_chapter_positions() -# Returns: [('Chapter 1', RenderingPosition(...)), ...] - -# Jump to chapter by index -page = reader.jump_to_chapter(1) # Jump to second chapter - -# Jump to chapter by title -page = reader.jump_to_chapter("Chapter 1") - -# Get current chapter info -chapter_info = reader.get_current_chapter_info() -# Returns: {'title': 'Chapter 1', 'level': HeadingLevel.H1, 'block_index': 0} -``` - -### Font Size Control - -```python -# Get current font size scale -scale = reader.get_font_size() # Default: 1.0 - -# Set specific font size scale -page = reader.set_font_size(1.5) # 150% of normal size - -# Increase font size by 10% -page = reader.increase_font_size() - -# Decrease font size by 10% -page = reader.decrease_font_size() -``` - -### Spacing Control - -```python -# Set line spacing (spacing between lines within a paragraph) -page = reader.set_line_spacing(10) # 10 pixels - -# Set inter-block spacing (spacing between paragraphs, headings, etc.) -page = reader.set_inter_block_spacing(20) # 20 pixels -``` - -### Context Manager - -The reader supports Python's context manager protocol for automatic cleanup: - -```python -with EbookReader(page_size=(800, 1000)) as reader: - reader.load_epub("book.epub") - page = reader.get_current_page() - # ... do stuff -# Automatically saves position and cleans up resources -``` - -## Complete Example - -```python -from pyWebLayout.layout.ereader_application import EbookReader - -# Create reader with custom settings -with EbookReader( - page_size=(800, 1000), - margin=50, - line_spacing=8, - inter_block_spacing=20 -) as reader: - # Load EPUB - if not reader.load_epub("my_novel.epub"): - print("Failed to load EPUB") - exit(1) - - # Get book info - info = reader.get_book_info() - print(f"Reading: {info['title']} by {info['author']}") - print(f"Total chapters: {info['total_chapters']}") - - # Navigate through first few pages - for i in range(5): - page = reader.get_current_page() - page.save(f"page_{i+1:03d}.png") - reader.next_page() - - # Save current position - reader.save_position("page_5") - - # Jump to a chapter - chapters = reader.get_chapters() - if len(chapters) > 2: - print(f"Jumping to: {chapters[2][0]}") - reader.jump_to_chapter(2) - reader.render_to_file("chapter_3_start.png") - - # Return to saved position - reader.load_position("page_5") - - # Adjust font size - reader.increase_font_size() - reader.render_to_file("page_5_larger_font.png") - - # Get progress - progress = reader.get_reading_progress() - print(f"Reading progress: {progress*100:.1f}%") -``` - -## Persistent Settings - -Settings like font size and spacing are automatically saved and restored across sessions: - -```python -from dreader import EbookReader -from dreader.state import StateManager -from pathlib import Path - -# Initialize state manager -state_file = Path.home() / ".config" / "dreader" / "state.json" -state_manager = StateManager(state_file=state_file) - -# Load saved state -state = state_manager.load_state() -print(f"Saved font scale: {state.settings.font_scale}") - -# Create reader with saved settings -reader = EbookReader( - line_spacing=state.settings.line_spacing, - inter_block_spacing=state.settings.inter_block_spacing -) - -# Load book and apply all saved settings -reader.load_epub("mybook.epub") -reader.apply_settings(state.settings.to_dict()) - -# User changes settings... -reader.increase_font_size() -reader.set_line_spacing(10) - -# Save new settings for next session -current_settings = reader.get_current_settings() -state_manager.update_settings(current_settings) -state_manager.save_state() - -# Next time the app starts, these settings will be restored! -``` - -See [persistent_settings_example.py](persistent_settings_example.py) for a complete demonstration. - -## Demo Scripts - -Run these demos to see features in action: - -```bash -# Comprehensive feature demo -python examples/ereader_demo.py path/to/book.epub - -# Persistent settings demo -python examples/persistent_settings_example.py - -# TOC overlay demo (generates animated GIF) -python examples/demo_toc_overlay.py - -# Settings overlay demo (generates animated GIF) -python examples/demo_settings_overlay.py - -# Word highlighting examples -python examples/word_selection_highlighting.py -``` - -This will demonstrate: -- Basic page navigation -- Position save/load -- Chapter navigation -- Font size adjustments -- Spacing adjustments -- Book information retrieval - -The demo generates multiple PNG files showing different pages and settings. - -## Position Storage Format - -Positions are stored as JSON files in the `bookmarks_dir` (default: `ereader_bookmarks/`): - -```json -{ - "chapter_index": 0, - "block_index": 42, - "word_index": 15, - "table_row": 0, - "table_col": 0, - "list_item_index": 0, - "remaining_pretext": null, - "page_y_offset": 0 -} -``` - -This format is tied to the abstract document structure, making positions stable across: -- Font size changes -- Line spacing changes -- Inter-block spacing changes -- Page size changes - -## Integration Example: Simple GUI - -Here's a minimal example of integrating with Tkinter: - -```python -import tkinter as tk -from tkinter import filedialog -from PIL import ImageTk -from pyWebLayout.layout.ereader_application import EbookReader - -class SimpleEreaderGUI: - def __init__(self, root): - self.root = root - self.reader = EbookReader(page_size=(600, 800)) - - # Create UI - self.image_label = tk.Label(root) - self.image_label.pack() - - btn_frame = tk.Frame(root) - btn_frame.pack() - - tk.Button(btn_frame, text="Open EPUB", command=self.open_epub).pack(side=tk.LEFT) - tk.Button(btn_frame, text="Previous", command=self.prev_page).pack(side=tk.LEFT) - tk.Button(btn_frame, text="Next", command=self.next_page).pack(side=tk.LEFT) - tk.Button(btn_frame, text="Font+", command=self.increase_font).pack(side=tk.LEFT) - tk.Button(btn_frame, text="Font-", command=self.decrease_font).pack(side=tk.LEFT) - - def open_epub(self): - filepath = filedialog.askopenfilename(filetypes=[("EPUB files", "*.epub")]) - if filepath: - self.reader.load_epub(filepath) - self.display_page() - - def display_page(self): - page = self.reader.get_current_page() - if page: - photo = ImageTk.PhotoImage(page) - self.image_label.config(image=photo) - self.image_label.image = photo - - def next_page(self): - if self.reader.next_page(): - self.display_page() - - def prev_page(self): - if self.reader.previous_page(): - self.display_page() - - def increase_font(self): - self.reader.increase_font_size() - self.display_page() - - def decrease_font(self): - self.reader.decrease_font_size() - self.display_page() - -root = tk.Tk() -root.title("Simple Ereader") -app = SimpleEreaderGUI(root) -root.mainloop() -``` - -## Performance Notes - -- The reader uses intelligent page caching for fast navigation -- First page load may take ~1 second, subsequent pages are typically < 0.1 seconds -- Background rendering attempts to pre-cache upcoming pages (you may see pickle warnings, which can be ignored) -- Font size changes invalidate the cache and require re-rendering from the current position -- Position save/load is nearly instantaneous - -## Limitations - -- Currently supports EPUB files only (no PDF, MOBI, etc.) -- Images in EPUBs may not render in some cases -- Tables are skipped in rendering -- Complex HTML layouts may not render perfectly -- No text selection or search functionality (these would need to be added separately) - -## See Also - -- `examples/ereader_demo.py` - Comprehensive feature demonstration -- `pyWebLayout/layout/ereader_manager.py` - Underlying manager class -- `pyWebLayout/layout/ereader_layout.py` - Core layout engine -- `examples/README_EPUB_RENDERERS.md` - Lower-level EPUB rendering