chore: tidy repository layout and consolidate hardware docs
Root had grown to 30 entries, most of it generated output and one-off scripts. It now holds 12. Docs: - Merge HARDWARE_SETUP, HARDWARE_PINOUT, GPIO_BUTTONS and ACCELEROMETER_PAGE_FLIP into a single docs/HARDWARE.md - Move ARCHITECTURE, REQUIREMENTS and HAL_IMPLEMENTATION_SPEC to docs/, leaving only README.md in the root - Update cross-references in README, setup_rpi.py and install_hardware_drivers.sh, and re-base ARCHITECTURE's source links Merging surfaced three errors, reconciled against hardware_config.json: - The power button was documented as GPIO 21 to GND with a pull-up. It is active high (pull_up: false); wiring it as documented reads as permanently pressed. - GPIO_BUTTONS used GPIO 23 for next-page; it is GPIO 27. - The FT5316 INT pin was routed to GPIO 27, which collides with the next-page button. Now documented as a conflict. Scripts: - Move debug_overlay_links.py and debug_previous_page.py to scripts/debug/ - Rename test_pagination_visual.py to scripts/debug/visualize_pagination.py; it renders output and asserts nothing, so the test_ prefix was misleading - Fix the __file__-relative paths these three relied on - Track update_pyweblayout.sh under scripts/ Tests: - Reword the backward-navigation tests, which described a pyWebLayout bug that is now fixed, as regression tests Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,552 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user