refactor(reader): create modular format-specific architecture

Implement complete modularization of reader code by separating format-specific
functionality into dedicated modules. This replaces the monolithic structure
with a clean, maintainable architecture that separates concerns by format type.

## New Architecture

### Format-Specific Modules
- **formats/reflowable/**: EPUB, FB2, TXT, HTML (page-based pagination)
  - types.ts: Shared type definitions for reflowable formats
  - page-calculator.ts: Word-count based pagination with HTML slicing
  - navigation.ts: Page-based navigation logic
  - progress-tracker.ts: CFI-based progress tracking
  - content-renderer.ts: DOM rendering for page content
  - parser.ts: Unified parser interface for all reflowable formats
  - ebook/**: Migrated ebook-specific features

- **formats/pdf/**: PDF format support
  - Core PDF functionality (navigation, text selection, annotations)
  - Advanced features (bookmarks, search, outlines, dual-page)
  - Page cache and rendering optimizations

- **formats/comic/**: Comic format support
  - Background color, chapter markers, page caching
  - Page ordering, gap adjustments

- **formats/manga/**: Manga format support
  - RTL navigation, vertical scrolling, reading direction

## Key Improvements

1. **Separation of Concerns**: Each format has its own dedicated module
2. **No Circular Dependencies**: Clean import structure
3. **Type Safety**: Comprehensive TypeScript types throughout
4. **Functional Programming**: Pure functions, no OOP complexity
5. **Scalability**: Easy to add new formats without touching core code

## Migration Path

- Old format-specific code in reader/, ebook/, pdf/, comic/, manga/
- New code in formats/[format]/ structure
- Maintains backward compatibility during transition
- Core reader logic remains format-agnostic

This change enables the implementation of page-based pagination for reflowable
formats while keeping PDF, comic, and manga functionality unchanged.
This commit is contained in:
2026-04-09 14:53:12 -04:00
parent 07ec8afa5f
commit e4c18e51f9
42 changed files with 5678 additions and 0 deletions
@@ -0,0 +1,82 @@
// Import types
import type { ReflowableBook, ReadingPosition } from "./types";
import { findPageByCFI, createPositionFromPage } from "./page-calculator";
// Update current position
export function updateCurrentPosition(
book: ReflowableBook,
position: ReadingPosition,
): ReflowableBook {
return {
...book,
position,
};
}
// Extract CFI from position
export function getCurrentCFI(book: ReflowableBook): string {
return book.position.cfi;
}
// Calculate progress for display
export function calculateProgress(book: ReflowableBook): {
currentPage: number;
totalPages: number;
percentage: number;
} {
const totalPages = book.pagination?.totalPages || 1;
const currentPage = book.position.currentPage;
const percentage =
totalPages > 0 ? Math.round((currentPage / totalPages) * 100) : 0;
return { currentPage, totalPages, percentage };
}
// Get position for saving to database
export function getPositionForSave(book: ReflowableBook): {
cfi: string;
progress: number;
page: number;
} {
return {
cfi: book.position.cfi,
progress: book.position.progress,
page: book.position.currentPage,
};
}
// Restore position from database
export function restorePosition(
book: ReflowableBook,
savedCFI: string,
savedPage?: number,
): ReadingPosition {
if (!book.pagination) {
return book.position;
}
// If we have saved CFI, try to find exact position
if (savedCFI) {
const pageNum = findPageByCFI(book.pagination, savedCFI);
return createPositionFromPage(book, pageNum);
}
// Otherwise use saved page number
if (savedPage && savedPage > 0) {
return createPositionFromPage(book, savedPage);
}
return book.position;
}
// Check if position changed significantly
export function didPositionChange(
oldPos: ReadingPosition,
newPos: ReadingPosition,
): boolean {
return (
oldPos.currentPage !== newPos.currentPage ||
oldPos.cfi !== newPos.cfi ||
Math.abs(oldPos.progress - newPos.progress) > 0.01
);
}