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

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 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/

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

  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
  2. Create HTML generator in html_generator.py
  3. Add overlay class in overlays/ directory
  4. Implement open/close methods in overlay manager
  5. Add gesture handling in application.py

Custom Gesture Handlers

To add custom gestures:

  1. Define gesture type in gesture.py
  2. Add handler in 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:

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:

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