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,62 @@
// Import types and existing parsers
import type { ReflowableBook, SpineItem, TOCItem } from "./types";
import { parseEPUB } from "../../parsers/epub-parsers";
import { parseFB2 } from "../../parsers/fb2-parser";
import { parseTXT } from "../../parsers/txt-parser";
import { parseHTML } from "../../parsers/html-parser";
// Parse any reflowable format
export async function parseReflowable(
file: File,
format: "epub" | "fb2" | "txt" | "html",
): Promise<ReflowableBook> {
switch (format) {
case "epub":
return await parseEPUB(file);
case "fb2":
return await parseFB2(file);
case "txt":
return await parseTXT(file);
case "html":
return await parseHTML(file);
default:
throw new Error(`Unsupported reflowable format: ${format}`);
}
}
// Validate parsed book data
export function validateBook(book: ReflowableBook): boolean {
return book.spine.length > 0 && book.metadata.title !== "";
}
// Get book title
export function getBookTitle(book: ReflowableBook): string {
return book.metadata.title || "Untitled";
}
// Get book author
export function getBookAuthor(book: ReflowableBook): string {
return book.metadata.author || "Unknown";
}
// Get total spine count
export function getSpineCount(book: ReflowableBook): number {
return book.spine.length;
}
// Get TOC as flat list
export function getFlatTOC(book: ReflowableBook): TOCItem[] {
const flat: TOCItem[] = [];
function traverse(items: TOCItem[]) {
for (const item of items) {
flat.push(item);
if (item.children.length > 0) {
traverse(item.children);
}
}
}
traverse(book.toc);
return flat;
}