dreader-application/ARCHITECTURE.md
Duncan Tourolle 01e79dfa4b
All checks were successful
Python CI / test (3.12) (push) Successful in 22m19s
Python CI / test (3.13) (push) Successful in 8m23s
Test appplication for offdevice testing
2025-11-09 17:47:34 +01:00

553 lines
15 KiB
Markdown

# DReader Application Architecture
## Overview
DReader is a full-featured ebook reader application built on top of [pyWebLayout](https://gitea.tourolle.paris/dtourolle/pyWebLayout). It provides a complete reading experience with navigation, bookmarks, highlights, and customizable display settings.
## System Architecture
### High-Level 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** provides low-level rendering primitives:
- Text layout and rendering algorithms
- Document structure and pagination
- Query systems for interactive content
- Core rendering infrastructure
**DReader** is an application framework that:
- Combines pyWebLayout components into a complete reader
- Provides high-level APIs for common ereader tasks
- Manages application state (bookmarks, highlights, positions)
- Handles business logic for gestures and interactions
Think of it as:
- **pyWebLayout** = React (library)
- **DReader** = Next.js (framework)
## Core Components
### 1. EbookReader (Main Coordinator)
**Location**: [application.py](dreader/application.py)
The central orchestrator that coordinates all subsystems:
```python
class EbookReader:
"""Main ebook reader application"""
# Core dependencies
manager: EreaderLayoutManager # pyWebLayout layout engine
doc_manager: DocumentManager # Document loading
settings_manager: SettingsManager # Display settings
highlight_coordinator: HighlightCoordinator # Text highlighting
gesture_router: GestureRouter # Gesture handling
overlay_manager: OverlayManager # Overlay rendering
```
**Key Responsibilities**:
- Document lifecycle (load, close)
- Page navigation (next, previous, chapters)
- Bookmark management
- Position persistence
- Settings coordination
- Gesture event routing
### 2. Document Manager
**Location**: [managers/document.py](dreader/managers/document.py)
Handles document loading and metadata extraction.
**Responsibilities**:
- Load EPUB files via pyWebLayout
- Extract book metadata (title, author, etc.)
- Provide document info to other components
### 3. Settings Manager
**Location**: [managers/settings.py](dreader/managers/settings.py)
Manages all display settings with persistence.
**Settings**:
- Font scale (adjustable font size)
- Line spacing
- Inter-block spacing (paragraph spacing)
- Word spacing
**Features**:
- Real-time preview in settings overlay
- Persistent across sessions
- Position preservation when settings change
### 4. Gesture Router
**Location**: [handlers/gestures.py](dreader/handlers/gestures.py)
Routes touch events to appropriate handlers based on application state.
**Gesture Types**:
- `TAP` - Word selection, link following, overlay interaction
- `SWIPE_LEFT` - Next page
- `SWIPE_RIGHT` - Previous page
- `SWIPE_UP` - Open navigation overlay (from bottom 20%)
- `SWIPE_DOWN` - Open settings overlay (from top) or close overlay
- `PINCH_IN/OUT` - Font size adjustment
- `DRAG` - Text selection (start, move, end)
**Routing Logic**:
```
Touch Event → GestureRouter
├─ Is overlay open?
│ ├─ Yes → Route to overlay handler
│ └─ No → Route to reading mode handler
└─ Return GestureResponse
```
### 5. Overlay System
**Location**: [overlays/](dreader/overlays/)
The overlay system provides modal UI panels over the reading content.
#### Overlay Manager
**Location**: [overlays/base.py](dreader/overlays/base.py)
Core overlay rendering and compositing infrastructure.
**Responsibilities**:
- Render overlay HTML to images
- Composite overlays over base page
- Darken background for modal effect
- Handle coordinate translation for interaction
- Cache for performance
#### Navigation Overlay
**Location**: [overlays/navigation.py](dreader/overlays/navigation.py)
Unified overlay with tabbed interface for:
- **Contents Tab**: Chapter navigation (TOC)
- **Bookmarks Tab**: Saved position management
**Features**:
- Tab switching without closing overlay
- Chapter selection with jump
- Bookmark selection with jump
- Add/delete bookmarks
#### Settings Overlay
**Location**: [overlays/settings.py](dreader/overlays/settings.py)
Interactive settings panel with real-time preview.
**Controls**:
- Font size: A- / A+ buttons
- Line spacing: +/- buttons
- Block spacing: +/- buttons
- Word spacing: +/- buttons
**Interaction**: Changes apply immediately, overlay refreshes to show updated values.
### 6. Library Manager
**Location**: [library.py](dreader/library.py)
Manages the library browsing experience.
**Features**:
- Scan directory for EPUB files
- Extract and cache metadata
- Render library grid view
- Handle book selection via tap
- Cache cover images for performance
**Display**: Renders books in a grid with cover thumbnails and metadata.
### 7. State Manager
**Location**: [state.py](dreader/state.py)
Persistent application state across sessions.
**State Structure**:
```python
class AppState:
mode: EreaderMode # LIBRARY or READING
overlay: OverlayState # Current overlay type
current_book: BookState # Currently open book
library: LibraryState # Library scan cache
settings: SettingsState # Display settings
```
**Persistence**:
- Location: `~/.config/dreader/state.json`
- Auto-save every 60 seconds
- Immediate save on mode change, settings change, shutdown
- Atomic writes for safety
**Boot Behavior**:
- Resume last book at last position
- Restore all settings
- Fall back to library if book missing
## Data Flow Diagrams
### Opening an Overlay
```
User Action
EbookReader.open_navigation_overlay()
├─ Get current page (base layer)
├─ Get chapters and bookmarks
OverlayManager.open_navigation_overlay()
├─ Generate HTML
├─ Render to image (using temp reader)
├─ Composite over base page
│ ├─ Darken background
│ ├─ Add border
│ └─ Paste panel at center
└─ Cache base page, overlay, offset
Return composited image
```
### Overlay Interaction
```
User Touch (x, y)
GestureRouter.handle_touch()
├─ Overlay open? YES
EbookReader._handle_overlay_tap(x, y)
OverlayManager.query_overlay_pixel(x, y)
├─ Translate screen coords to overlay coords
├─ Query pyWebLayout for link at position
└─ Return link_target (e.g., "chapter:5")
Parse link_target and execute action:
├─ "chapter:N" → jump_to_chapter(N), close overlay
├─ "bookmark:name" → load_position(name), close overlay
├─ "setting:action" → apply setting, refresh overlay
└─ "tab:name" → switch tab, keep overlay open
Return GestureResponse
```
### State Persistence
```
Application Running
StateManager auto-save timer (every 60s)
├─ Gather current state
├─ Serialize to JSON
└─ Atomic write to disk
OR
User performs action (page turn, setting change)
├─ StateManager.save_state()
└─ Immediate write
Application Shutdown
├─ Save position: reader.save_position("__auto_resume__")
├─ Stop auto-save
└─ Final state.json write
```
### Boot Sequence
```
Application Start
StateManager.load_state()
├─ Read state.json
├─ Validate and parse
└─ Create AppState object
Check previous mode:
├─ READING mode?
│ ├─ Load last book
│ ├─ Apply saved settings
│ └─ Restore position ("__auto_resume__")
└─ LIBRARY mode?
└─ Show library grid
```
## File Organization
### Application State Files
```
~/.config/dreader/
├── state.json # Application state
├── covers/ # Cached book covers
│ └── {book_id}.png
├── bookmarks/ # Per-book bookmarks
│ └── {document_id}_{bookmark_name}.json
└── highlights/ # Per-book highlights
└── {document_id}_highlights.json
```
### Bookmark Format
Each book's position is stored separately using document ID:
```json
{
"document_id": "book123",
"bookmark_name": "__auto_resume__",
"position": {
"offset": 1234,
"chapter": 5
},
"timestamp": "2025-11-09T10:30:00Z"
}
```
## Gesture Handling
### Gesture Priority and Routing
```
Touch Event
Is overlay open?
├─ YES → Overlay Mode
│ ├─ TAP → Handle overlay interaction
│ ├─ SWIPE_DOWN → Close overlay
│ └─ Other → Ignore (modal behavior)
└─ NO → Reading Mode
├─ TAP
│ ├─ On link → Follow link
│ ├─ On word → Select word
│ ├─ Left edge → Previous page
│ └─ Right edge → Next page
├─ SWIPE
│ ├─ LEFT → Next page
│ ├─ RIGHT → Previous page
│ ├─ UP (from bottom 20%) → Open navigation
│ └─ DOWN (from top 20%) → Open settings
├─ PINCH
│ ├─ IN → Decrease font size
│ └─ OUT → Increase font size
└─ DRAG
├─ START → Begin text selection
├─ MOVE → Extend selection
└─ END → Complete selection
```
### Response Types
```python
class ActionType(Enum):
NONE = "none"
PAGE_TURN = "page_turn"
WORD_SELECTED = "word_selected"
LINK_FOLLOWED = "link_followed"
CHAPTER_SELECTED = "chapter_selected"
BOOKMARK_SELECTED = "bookmark_selected"
SETTING_CHANGED = "setting_changed"
OVERLAY_OPENED = "overlay_opened"
OVERLAY_CLOSED = "overlay_closed"
TAB_SWITCHED = "tab_switched"
```
## Performance Characteristics
### Rendering Performance
- **Page Turn**: ~50-100ms (depends on page complexity)
- **Overlay Open**: ~200-250ms (includes HTML generation and rendering)
- **Tab Switch**: ~125ms (uses cached base page)
- **Setting Change**: ~150ms (re-render with new settings)
- **Tap Interaction**: ~5-10ms (coordinate query)
### Memory Usage
- **Base Application**: ~20-30MB
- **Per Book**: ~10-50MB (depends on images)
- **Overlay Cache**: ~5-10MB
### Optimization Strategies
1. **Caching**:
- Base page cached during overlay display
- Overlay panel cached for tab switching
- Cover images cached to disk
- Metadata cached between sessions
2. **Lazy Loading**:
- Library covers loaded on-demand
- Book content loaded only when opened
- Overlays rendered only when needed
3. **Efficient Updates**:
- Tab switching reuses base page
- Setting changes use incremental rendering
- Position saves are debounced
## Extension Points
### Adding New Overlays
To add a new overlay type:
1. Define new `OverlayState` enum value in [state.py](dreader/state.py#L27-L33)
2. Create HTML generator in [html_generator.py](dreader/html_generator.py)
3. Add overlay class in `overlays/` directory
4. Implement open/close methods in [overlay manager](dreader/overlays/base.py)
5. Add gesture handling in [application.py](dreader/application.py)
### Custom Gesture Handlers
To add custom gestures:
1. Define gesture type in [gesture.py](dreader/gesture.py)
2. Add handler in [gestures.py](dreader/handlers/gestures.py)
3. Define action type for response
4. Update gesture router logic
### HAL Integration
To integrate with hardware:
Create a display abstraction layer implementing:
```python
class DisplayHAL(ABC):
@abstractmethod
def show_image(self, image: Image.Image):
"""Display image on hardware"""
@abstractmethod
def get_touch_events(self) -> Iterator[TouchEvent]:
"""Get touch input from hardware"""
@abstractmethod
def set_brightness(self, level: int):
"""Control display brightness"""
```
Examples:
- **E-ink**: IT8951, Remarkable device SDK
- **Desktop**: pygame, tkinter
- **Web**: Flask + HTML canvas
- **Qt**: QPixmap + QTouchEvent
## Testing Strategy
### Unit Tests
- State serialization and persistence
- Gesture routing logic
- Coordinate translation
- HTML generation
### Integration Tests
- Mode transitions (LIBRARY ↔ READING)
- Overlay lifecycle (open → interact → close)
- Boot recovery and resume
- Settings persistence
### Example-Based Testing
Working examples demonstrate full integration:
- [simple_ereader_example.py](examples/simple_ereader_example.py)
- [library_reading_integration.py](examples/library_reading_integration.py)
- [navigation_overlay_example.py](examples/navigation_overlay_example.py)
- [demo_settings_overlay.py](examples/demo_settings_overlay.py)
## Design Patterns
### Component-Based Architecture
- **Managers**: Single-responsibility modules for specific tasks
- **Handlers**: Event routing and processing
- **Overlays**: Self-contained UI components
### Delegation Over Inheritance
- EbookReader delegates to specialized managers
- No deep inheritance hierarchies
- Composition for flexibility
### State Machine Pattern
- Clear state transitions (modes, overlays)
- State persistence for resume
- Predictable behavior
### Event-Driven Architecture
- Touch events drive all interactions
- Response objects communicate results
- Decoupled components
## Future Architecture Considerations
### Sub-Application Pattern
Current overlay handling uses a monolithic approach. Future refactoring could extract overlays into sub-applications:
```python
class OverlaySubApplication(ABC):
def open(self, context: OverlayContext) -> Image.Image: ...
def handle_tap(self, x: int, y: int) -> GestureResponse: ...
def close(self) -> Image.Image: ...
```
Benefits:
- Self-contained overlay logic
- Easier testing
- Plugin support
- Composable overlays
### Plugin System
Enable third-party extensions:
- Custom overlay types
- Additional gestures
- Export formats
- Cloud sync providers
## References
- [pyWebLayout Documentation](https://gitea.tourolle.paris/dtourolle/pyWebLayout)
- [REQUIREMENTS.md](REQUIREMENTS.md) - Detailed feature specifications
- [README.md](README.md) - User-facing documentation
- [examples/](examples/) - Working code examples