553 lines
15 KiB
Markdown
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
|