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:
@@ -0,0 +1,109 @@
|
||||
// Handle PDFs with variable page sizes
|
||||
// Procedural implementation (no OOP)
|
||||
|
||||
interface PageInfo {
|
||||
pageNumber: number;
|
||||
width: number;
|
||||
height: number;
|
||||
rotation: number;
|
||||
}
|
||||
|
||||
interface PDFPageSizesState {
|
||||
pageSizes: Map<number, PageInfo>;
|
||||
defaultSize: { width: number; height: number };
|
||||
}
|
||||
|
||||
function createPDFPageSizes(): PDFPageSizesState {
|
||||
return {
|
||||
pageSizes: new Map(),
|
||||
defaultSize: { width: 595, height: 842 },
|
||||
};
|
||||
}
|
||||
|
||||
async function loadPDFPageSizes(
|
||||
state: PDFPageSizesState,
|
||||
doc: any,
|
||||
): Promise<PDFPageSizesState> {
|
||||
const pageSizes = new Map<number, PageInfo>();
|
||||
|
||||
for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {
|
||||
const page = await doc.getPage(pageNum);
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
|
||||
const pageInfo: PageInfo = {
|
||||
pageNumber: pageNum,
|
||||
width: viewport.width,
|
||||
height: viewport.height,
|
||||
rotation: viewport.rotation,
|
||||
};
|
||||
|
||||
pageSizes.set(pageNum, pageInfo);
|
||||
}
|
||||
|
||||
return { ...state, pageSizes };
|
||||
}
|
||||
|
||||
function getPDFPageSize(
|
||||
state: PDFPageSizesState,
|
||||
pageNumber: number,
|
||||
): PageInfo | null {
|
||||
return state.pageSizes.get(pageNumber) || null;
|
||||
}
|
||||
|
||||
function isPDFPageLandscape(
|
||||
state: PDFPageSizesState,
|
||||
pageNumber: number,
|
||||
): boolean {
|
||||
const size = getPDFPageSize(state, pageNumber);
|
||||
if (!size) return false;
|
||||
|
||||
const effectiveWidth =
|
||||
size.rotation === 90 || size.rotation === 270 ? size.height : size.width;
|
||||
const effectiveHeight =
|
||||
size.rotation === 90 || size.rotation === 270 ? size.width : size.height;
|
||||
|
||||
return effectiveWidth > effectiveHeight;
|
||||
}
|
||||
|
||||
function getPDFCommonSize(state: PDFPageSizesState): {
|
||||
width: number;
|
||||
height: number;
|
||||
} {
|
||||
if (state.pageSizes.size === 0) {
|
||||
return state.defaultSize;
|
||||
}
|
||||
|
||||
const sizeGroups: Map<
|
||||
string,
|
||||
{ width: number; height: number; count: number }
|
||||
> = new Map();
|
||||
|
||||
state.pageSizes.forEach((size) => {
|
||||
const key = getPageSizeKey(size.width, size.height);
|
||||
const existing = sizeGroups.get(key);
|
||||
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
} else {
|
||||
sizeGroups.set(key, { width: size.width, height: size.height, count: 1 });
|
||||
}
|
||||
});
|
||||
|
||||
let mostCommon = state.defaultSize;
|
||||
let maxCount = 0;
|
||||
|
||||
sizeGroups.forEach((size) => {
|
||||
if (size.count > maxCount) {
|
||||
maxCount = size.count;
|
||||
mostCommon = { width: size.width, height: size.height };
|
||||
}
|
||||
});
|
||||
|
||||
return mostCommon;
|
||||
}
|
||||
|
||||
function getPageSizeKey(width: number, height: number): string {
|
||||
const w = Math.round(width / 10) * 10;
|
||||
const h = Math.round(height / 10) * 10;
|
||||
return `${w}x${h}`;
|
||||
}
|
||||
Reference in New Issue
Block a user