15 KiB
DReader Application Architecture
Overview
DReader is a full-featured ebook reader application built on top of 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
The central orchestrator that coordinates all subsystems:
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
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
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
Routes touch events to appropriate handlers based on application state.
Gesture Types:
TAP- Word selection, link following, overlay interactionSWIPE_LEFT- Next pageSWIPE_RIGHT- Previous pageSWIPE_UP- Open navigation overlay (from bottom 20%)SWIPE_DOWN- Open settings overlay (from top) or close overlayPINCH_IN/OUT- Font size adjustmentDRAG- 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/
The overlay system provides modal UI panels over the reading content.
Overlay Manager
Location: 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
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
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
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
Persistent application state across sessions.
State Structure:
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:
{
"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
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
-
Caching:
- Base page cached during overlay display
- Overlay panel cached for tab switching
- Cover images cached to disk
- Metadata cached between sessions
-
Lazy Loading:
- Library covers loaded on-demand
- Book content loaded only when opened
- Overlays rendered only when needed
-
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:
- Define new
OverlayStateenum value in state.py - Create HTML generator in html_generator.py
- Add overlay class in
overlays/directory - Implement open/close methods in overlay manager
- Add gesture handling in application.py
Custom Gesture Handlers
To add custom gestures:
- Define gesture type in gesture.py
- Add handler in gestures.py
- Define action type for response
- Update gesture router logic
HAL Integration
To integrate with hardware:
Create a display abstraction layer implementing:
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
- library_reading_integration.py
- navigation_overlay_example.py
- 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:
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
- REQUIREMENTS.md - Detailed feature specifications
- README.md - User-facing documentation
- examples/ - Working code examples