Pdf text reflow (#36)
* Implemented PDF reflow mode by introducing a mechanism to convert PDF content to Markdown/HTML for viewing in the EPUB reader. Specific changes include: - Added `PdfReflowGenerator` and `PdfToMarkdownGenerator` to handle PDF text extraction and conversion to reflowable formats. - Updated `MainViewModel` with `toggleReflowMode` logic to switch between original PDF and reflowed views. - Modified `RecentFileEntity` and `RecentFileDao` to persist user reflow preferences, including a Room database migration (v12 to v13). - Updated `PdfViewerScreen` and `EpubReaderControls` to include UI options for toggling reflow mode. - Integrated reflow preference check into the book opening workflow to automatically load the preferred view. - Updated `AppNavigation` and `EpubReaderScreen` to support the new view switching state. * Implemented background processing and incremental loading for PDF reflow mode. - Added `reflowProgress` to `MainViewModel` to track and display PDF-to-Markdown conversion progress in the UI. - Refactored `PdfToMarkdownGenerator` to generate a skeleton EPUB structure immediately while processing page content (text and images) asynchronously. - Switched PDF text extraction to use `PDFBox` with optimized memory settings and JPEG compression for images. - Implemented priority page processing in reflow mode, starting with the user's current page. - Added "Clear Reflow Cache" debug option to the Home Screen. - Enhanced `BookPaginator` to support lazy loading of chapter content from disk and improved cache hit detection. * Refactored PDF Reflow Mode to generate standalone Markdown files instead of temporary EPUB books. * perf(reflow): optimize PDF-to-Markdown conversion and fix viewing lag - Re-architected PdfToMarkdownGenerator to use a single-pass stream (O(N) complexity), fixing performance bottlenecks and timeouts on large PDFs. - Implemented "Virtual Chaptering" in SingleFileImporter for Markdown files to split content into page-level HTML files, eliminating UI lag during reading. - Simplified ReflowWorker to delegate progress tracking and looping to the generator. - Enhanced PdfViewerScreen with a prominent top-bar progress indicator and a completion snackbar with an "OPEN" action. * Optimized EPUB parsing performance and fixed PDF viewer UI layout. - Optimized `EpubParser` by implementing parallel chapter parsing using coroutines and a semaphore to limit concurrency. - Reduced memory usage in `EpubParser` and `SingleFileImporter` by no longer storing full HTML content in memory for chapters. - Updated `EpubXMLFileParser` to support an existing `Document` object to avoid redundant Jsoup parsing. - Fixed an issue in `PdfViewerScreen` where the snackbar was appearing under the bottom app bar.
This commit is contained in:
parent
8f52549c19
commit
1e879eb604
21 changed files with 936 additions and 165 deletions
|
|
@ -655,7 +655,10 @@ fun PaginatedReaderScreen(
|
|||
BookCacheDatabase.getDatabase(context.applicationContext).bookCacheDao()
|
||||
val proto = ProtoBuf { serializersModule = semanticBlockModule }
|
||||
|
||||
Timber.d("Recreating BookPaginator. TextAlign: $userTextAlign")
|
||||
val uniqueBookId = if (book.fileName.length > 20) book.fileName else book.title
|
||||
|
||||
Timber.d("Recreating BookPaginator for ID: $uniqueBookId. TextAlign: $userTextAlign")
|
||||
Timber.tag("ReflowPaginationDiag").d("PaginatedReaderScreen: Instantiating BookPaginator. book.chaptersForPagination.size=${book.chaptersForPagination.size}, initialChapter=$effectiveInitialChapter")
|
||||
|
||||
BookPaginator(
|
||||
coroutineScope = coroutineScope,
|
||||
|
|
@ -667,7 +670,7 @@ fun PaginatedReaderScreen(
|
|||
density = density,
|
||||
fontFamilyMap = fontFamilyMap,
|
||||
isDarkTheme = isDarkTheme,
|
||||
bookId = book.title,
|
||||
bookId = uniqueBookId,
|
||||
bookCacheDao = bookCacheDao,
|
||||
proto = proto,
|
||||
initialChapterToPaginate = effectiveInitialChapter,
|
||||
|
|
@ -719,23 +722,29 @@ fun PaginatedReaderScreen(
|
|||
}
|
||||
}
|
||||
|
||||
// FIX 2: Replace property delegates with local state and a LaunchedEffect observer.
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
var totalPageCount by remember { mutableIntStateOf(0) }
|
||||
var generation by remember { mutableIntStateOf(0) }
|
||||
|
||||
LaunchedEffect(paginator) {
|
||||
launch { snapshotFlow { paginator.isLoading }.collect { isLoading = it } }
|
||||
launch { snapshotFlow { paginator.isLoading }.collect {
|
||||
Timber.tag("ReflowPaginationDiag").d("PaginatedReaderScreen: paginator.isLoading=$it")
|
||||
isLoading = it
|
||||
} }
|
||||
launch {
|
||||
snapshotFlow { paginator.totalPageCount }.collect { newTotalPageCount ->
|
||||
Timber.tag("ReflowPaginationDiag").d("PaginatedReaderScreen: paginator.totalPageCount=$newTotalPageCount")
|
||||
totalPageCount = newTotalPageCount
|
||||
}
|
||||
}
|
||||
launch { snapshotFlow { paginator.generation }.collect { generation = it } }
|
||||
launch { snapshotFlow { paginator.generation }.collect {
|
||||
Timber.tag("ReflowPaginationDiag").d("PaginatedReaderScreen: paginator.generation=$it")
|
||||
generation = it
|
||||
} }
|
||||
}
|
||||
|
||||
LaunchedEffect(pagerState, paginator) {
|
||||
snapshotFlow { pagerState.currentPage }.debounce(500) // Wait for scrolling to settle
|
||||
snapshotFlow { pagerState.currentPage }.debounce(500)
|
||||
.collectLatest { page -> paginator.onUserScrolledTo(page) }
|
||||
}
|
||||
|
||||
|
|
@ -1432,6 +1441,7 @@ internal fun PaginatedReaderContent(
|
|||
CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
Timber.tag("ReflowPaginationDiag").d("PaginatedReaderContent: isLoading=false, totalPageCount=${uiState.totalPageCount}")
|
||||
if (uiState.totalPageCount > 0) {
|
||||
uiState.generation
|
||||
|
||||
|
|
@ -1508,7 +1518,9 @@ internal fun PaginatedReaderContent(
|
|||
var currentChapterPath by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(pageIndex, uiState.generation) {
|
||||
Timber.tag("ReflowPaginationDiag").d("PaginatedReaderContent: Fetching page $pageIndex content. generation=${uiState.generation}")
|
||||
pageContent = onGetPage(pageIndex)
|
||||
Timber.tag("ReflowPaginationDiag").d("PaginatedReaderContent: Fetched page $pageIndex content. isNull=${pageContent == null}, blocks=${pageContent?.content?.size}")
|
||||
onGetChapterPath(pageIndex)?.let { currentChapterPath = it }
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue