General fixes (#102)
* Improved PDF embedded annotation hit detection logic * Updated EPUB and PDF reader logic to improve position tracking and TTS stability. Key changes: - Refined CFI and locator handling in `EpubReaderScreen` with improved initialization. - Updated `BookPaginator` to more accurately calculate text offsets in chunks and improve CFI matching logic. - Modified `TtsPlaybackManager` to insert media items at the correct index, ensuring proper playback order. - Simplified `EpubReaderTts` page scrolling by removing conditional checks for backward jumps. - Disabled OCR fallback in `PdfViewerScreen` during text extraction for TTS. * Updated extractTextWithCfiFromTop to use getBoundingClientRect for identifying the starting block
This commit is contained in:
parent
ddcd253c7b
commit
fbe8e7aa56
9 changed files with 95 additions and 184 deletions
|
|
@ -1097,62 +1097,44 @@
|
|||
|
||||
window.extractTextWithCfiFromTop = function () {
|
||||
try {
|
||||
// 1. Find the element at the top of the viewport.
|
||||
const viewportX = window.innerWidth / 2;
|
||||
const viewportY = window.VIEWPORT_PADDING_TOP + 20; // A bit down from the very top edge
|
||||
let topElement = document.elementFromPoint(viewportX, viewportY);
|
||||
|
||||
if (!topElement) {
|
||||
// Fallback if nothing is found (e.g., blank space between elements)
|
||||
topElement = document.body.querySelector("p, h1, h2, h3, h4, img, svg, table, li");
|
||||
if (!topElement) return "[]"; // Chapter seems empty
|
||||
}
|
||||
|
||||
// 2. Find its containing block-level element that we use for TTS.
|
||||
const ttsNodeSelector = "p, h1, h2, h3, h4, h5, h6, li, blockquote";
|
||||
let startBlock = topElement.closest(ttsNodeSelector);
|
||||
|
||||
if (!startBlock) {
|
||||
// If the element itself isn't in a TTS block, fall back to the start of the chapter.
|
||||
console.log("Could not find a starting TTS block. Falling back to full chapter.");
|
||||
return window.extractTextWithCfi();
|
||||
}
|
||||
|
||||
// 3. Get all potential TTS nodes.
|
||||
const allContentNodes = Array.from(document.body.querySelectorAll(ttsNodeSelector));
|
||||
|
||||
// 4. Find the index of our starting block.
|
||||
const startIndex = allContentNodes.findIndex((node) => node === startBlock);
|
||||
let startBlock = null;
|
||||
let startIndex = -1;
|
||||
|
||||
if (startIndex === -1) {
|
||||
// Should be rare if startBlock was found, but as a safeguard:
|
||||
console.log("Could not find the start block in the node list. Falling back to full chapter.");
|
||||
for (let i = 0; i < allContentNodes.size || i < allContentNodes.length; i++) {
|
||||
const node = allContentNodes[i];
|
||||
const rect = node.getBoundingClientRect();
|
||||
|
||||
if (rect.bottom > (window.VIEWPORT_PADDING_TOP + 10)) {
|
||||
startBlock = node;
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!startBlock) {
|
||||
return window.extractTextWithCfi();
|
||||
}
|
||||
|
||||
// 5. Slice the array and process it.
|
||||
const nodesToProcess = allContentNodes.slice(startIndex);
|
||||
const results = [];
|
||||
|
||||
nodesToProcess.forEach((node) => {
|
||||
const text = node.innerText ? node.innerText.trim() : "";
|
||||
|
||||
if (text.length > 0 && node.offsetParent !== null) {
|
||||
try {
|
||||
const cfi = getCfiPathForElement(node, 0);
|
||||
|
||||
if (cfi) {
|
||||
results.push({ cfi: cfi, text: text });
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore CFI generation errors for a single node
|
||||
const cfiObj = getCfiPathForElement(node, 0);
|
||||
if (cfiObj && cfiObj.cfi) {
|
||||
results.push({ cfi: cfiObj, text: text });
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.stringify(results);
|
||||
} catch (e) {
|
||||
// On any error, fall back to extracting everything to not break TTS completely.
|
||||
return window.extractTextWithCfi();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -720,9 +720,7 @@ fun ChapterWebView(
|
|||
|
||||
if (!initialCfi.isNullOrBlank()) {
|
||||
val cfiJsCommand = "javascript:window.scrollToCfi('$initialCfi');"
|
||||
Timber.d(
|
||||
"WebView onPageFinished: Executing initial scroll to CFI: $initialCfi"
|
||||
)
|
||||
Timber.tag("POS_DIAG").d("WebView onPageFinished: Triggering scroll to initialCfi: $initialCfi")
|
||||
view?.evaluateJavascript(cfiJsCommand) {
|
||||
onChapterInitiallyScrolled()
|
||||
scrollActionTaken = true
|
||||
|
|
|
|||
|
|
@ -884,11 +884,15 @@ fun EpubReaderHost(
|
|||
var chapterHead by remember(currentChapterIndex) { mutableStateOf("") }
|
||||
var isChapterParsing by remember(currentChapterIndex) { mutableStateOf(true) }
|
||||
|
||||
var cfiToLoad by remember { mutableStateOf<String?>(null) }
|
||||
var cfiToLoad by remember { mutableStateOf(initialCfi) }
|
||||
var fragmentToLoad by remember { mutableStateOf<String?>(null) }
|
||||
var isInitialCfiLoad by remember(initialLocator) { mutableStateOf(initialLocator != null) }
|
||||
var bookmarkPageMap by remember { mutableStateOf<Map<String, Int>>(emptyMap()) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
Timber.tag("POS_DIAG").d("Reader Opening: initialLocator=$initialLocator, initialCfi=$initialCfi")
|
||||
}
|
||||
|
||||
var initialScrollTargetForChapter by rememberSaveable(epubBook.title) {
|
||||
mutableStateOf(if (initialLocator != null) null else ChapterScrollPosition.START)
|
||||
}
|
||||
|
|
@ -1175,8 +1179,8 @@ fun EpubReaderHost(
|
|||
var foundIdx = -1
|
||||
for (i in chunks.indices) {
|
||||
val c = chunks[i]
|
||||
val cPath = c.sourceCfi.substringBefore(":")
|
||||
val bPath = baseCfi.substringBefore(":")
|
||||
val cPath = com.aryan.reader.paginatedreader.CfiUtils.getPath(c.sourceCfi)
|
||||
val bPath = com.aryan.reader.paginatedreader.CfiUtils.getPath(baseCfi)
|
||||
if (cPath == bPath && startOffset >= c.startOffsetInSource && startOffset < c.startOffsetInSource + c.text.length) {
|
||||
foundIdx = i
|
||||
break
|
||||
|
|
@ -2550,10 +2554,9 @@ fun EpubReaderHost(
|
|||
showDictionaryUpsellDialog = true
|
||||
},
|
||||
onCfiGenerated = { cfi ->
|
||||
Timber.tag("PosSaveDiag").d("EpubReaderScreen: onCfiGenerated callback triggered with CFI: '$cfi'")
|
||||
Timber.tag("POS_DIAG").d("JS generated CFI: '$cfi'")
|
||||
|
||||
if (cfi.isBlank() || !cfi.startsWith('/')) {
|
||||
Timber.tag("PosSaveDiag").w("EpubReaderScreen: onCfiGenerated received an invalid CFI, aborting save: '$cfi'")
|
||||
if (isSavingAndExiting) {
|
||||
isSavingAndExiting = false
|
||||
onNavigateBack()
|
||||
|
|
@ -2562,7 +2565,6 @@ fun EpubReaderHost(
|
|||
}
|
||||
|
||||
scope.launch {
|
||||
Timber.tag("PosSaveDiag").d("EpubReaderScreen: Requesting locator conversion for chapter $latestChapterIndex")
|
||||
val locator =
|
||||
locatorConverter.getLocatorFromCfi(
|
||||
epubBook,
|
||||
|
|
@ -2570,8 +2572,6 @@ fun EpubReaderHost(
|
|||
cfi
|
||||
)
|
||||
|
||||
Timber.tag("PosSaveDiag").d("EpubReaderScreen: Locator conversion returned: $locator")
|
||||
|
||||
if (locator != null) {
|
||||
lastKnownLocator = locator
|
||||
|
||||
|
|
@ -2639,8 +2639,7 @@ fun EpubReaderHost(
|
|||
} else {
|
||||
0f
|
||||
}
|
||||
Timber.d("CFI received for saving: $cfi. Progress: $progress%"
|
||||
)
|
||||
Timber.tag("POS_DIAG").i("Saving Position: Chapter=$latestChapterIndex, CFI=$cfi, Progress=$progress%")
|
||||
onSavePosition(locator, cfi, progress)
|
||||
} else {
|
||||
Timber.w("Failed to convert CFI to Locator: $cfi."
|
||||
|
|
|
|||
|
|
@ -215,15 +215,12 @@ fun TtsHighlightHandler(
|
|||
val targetPage = pag.findPageForCfiAndOffset(chapterIdx, cfi, offset)
|
||||
|
||||
if (targetPage != null && targetPage != pagerState.currentPage) {
|
||||
// Prevent backward jumps during reading (unless significant) to avoid jitter
|
||||
if (targetPage >= pagerState.currentPage) {
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(targetPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Internal Helper Functions ---
|
||||
|
||||
|
|
|
|||
|
|
@ -352,15 +352,23 @@ class BookPaginator(
|
|||
if (blockText.isNotBlank()) {
|
||||
val textChunksInBlock = splitTextIntoChunks(blockText)
|
||||
|
||||
var currentOffsetInBlock = 0
|
||||
var currentSearchIndex = 0
|
||||
textChunksInBlock.forEach { chunkText ->
|
||||
val firstWord = chunkText.trim().substringBefore(' ')
|
||||
val relativeOffset = if (firstWord.isNotEmpty()) {
|
||||
val idx = blockText.indexOf(firstWord, currentSearchIndex)
|
||||
if (idx != -1) idx else currentSearchIndex
|
||||
} else {
|
||||
currentSearchIndex
|
||||
}
|
||||
|
||||
val chunk = TtsChunk(
|
||||
text = chunkText,
|
||||
sourceCfi = block.cfi!!,
|
||||
startOffsetInSource = block.startCharOffsetInSource + currentOffsetInBlock
|
||||
startOffsetInSource = block.startCharOffsetInSource + relativeOffset
|
||||
)
|
||||
allTtsChunks.add(chunk)
|
||||
currentOffsetInBlock += chunkText.length
|
||||
currentSearchIndex = relativeOffset + chunkText.length
|
||||
}
|
||||
} else {
|
||||
Timber.d("PAGINATOR: Skipping blank text block. CFI: ${block.cfi}, startOffset: ${block.startCharOffsetInSource}")
|
||||
|
|
@ -1036,8 +1044,10 @@ class BookPaginator(
|
|||
return null
|
||||
}
|
||||
|
||||
val targetPath = CfiUtils.getPath(cfi)
|
||||
val foundRange = index.find { range ->
|
||||
val cfiMatches = cfi.startsWith(range.cfi)
|
||||
val rangePath = CfiUtils.getPath(range.cfi)
|
||||
val cfiMatches = targetPath == rangePath || targetPath.startsWith(rangePath) || rangePath.startsWith(targetPath)
|
||||
val offsetMatches = charOffset >= range.startOffset && charOffset < range.endOffset
|
||||
cfiMatches && offsetMatches
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,39 +51,25 @@ class LocatorConverter(
|
|||
private val context: Context
|
||||
) {
|
||||
private suspend fun processAndCacheChapter(book: EpubBook, chapterIndex: Int): List<SemanticBlock>? = withContext(Dispatchers.IO) {
|
||||
Timber.tag("PosSaveDiag").d("processAndCacheChapter: STARTED for book='${book.title}', chapterIndex=$chapterIndex")
|
||||
Timber.tag("POS_DIAG").d("processAndCacheChapter: Processing for bookId='${book.title}' index=$chapterIndex")
|
||||
try {
|
||||
val chapter = book.chapters.getOrNull(chapterIndex)
|
||||
if (chapter == null) {
|
||||
Timber.tag("PosSaveDiag").e("processAndCacheChapter: FAILED. Chapter is null for index $chapterIndex")
|
||||
return@withContext null
|
||||
}
|
||||
val chapter = book.chapters.getOrNull(chapterIndex) ?: return@withContext null
|
||||
|
||||
Timber.tag("PosSaveDiag").d("processAndCacheChapter: Checking HTML content. RAM content length: ${chapter.htmlContent.length}")
|
||||
|
||||
val htmlToParse = if (chapter.htmlContent.isNotBlank()) {
|
||||
Timber.tag("PosSaveDiag").d("processAndCacheChapter: Using HTML from RAM.")
|
||||
chapter.htmlContent
|
||||
} else {
|
||||
Timber.tag("PosSaveDiag").d("processAndCacheChapter: RAM HTML is blank. Falling back to disk. Path: ${book.extractionBasePath} / ${chapter.htmlFilePath}")
|
||||
val htmlToParse = chapter.htmlContent.ifBlank {
|
||||
try {
|
||||
val file = File(book.extractionBasePath, chapter.htmlFilePath)
|
||||
if (file.exists()) {
|
||||
val content = file.readText()
|
||||
Timber.tag("PosSaveDiag").d("processAndCacheChapter: Read file from disk SUCCESS. Content length: ${content.length}")
|
||||
content
|
||||
} else {
|
||||
Timber.tag("PosSaveDiag").e("processAndCacheChapter: File DOES NOT EXIST at ${file.absolutePath}")
|
||||
""
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.tag("PosSaveDiag").e(e, "processAndCacheChapter: Exception reading chapter file from disk")
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
if (htmlToParse.isBlank()) {
|
||||
Timber.tag("PosSaveDiag").w("processAndCacheChapter: Final HTML to parse is blank. Aborting semantic block generation.")
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +115,6 @@ class LocatorConverter(
|
|||
otherComplex = mergedOtherComplex
|
||||
)
|
||||
|
||||
Timber.tag("PosSaveDiag").d("processAndCacheChapter: Calling htmlToSemanticBlocks...")
|
||||
val semanticBlocks = htmlToSemanticBlocks(
|
||||
html = htmlToParse,
|
||||
cssRules = parsingCssRules,
|
||||
|
|
@ -140,10 +125,8 @@ class LocatorConverter(
|
|||
fontFamilyMap = emptyMap(),
|
||||
constraints = constraints
|
||||
)
|
||||
Timber.tag("PosSaveDiag").d("processAndCacheChapter: htmlToSemanticBlocks returned ${semanticBlocks.size} blocks.")
|
||||
|
||||
val protoBytes = proto.encodeToByteArray(semanticBlocks)
|
||||
Timber.tag("PosSaveDiag").d("processAndCacheChapter: Encoded blocks to protoBytes (size: ${protoBytes.size} bytes).")
|
||||
|
||||
val newCacheEntry = ProcessedChapter(
|
||||
bookId = book.title,
|
||||
|
|
@ -152,10 +135,8 @@ class LocatorConverter(
|
|||
estimatedPageCount = 0
|
||||
)
|
||||
bookCacheDao.insertProcessedChapters(listOf(newCacheEntry))
|
||||
Timber.tag("PosSaveDiag").i("processAndCacheChapter: On-demand processing and DB caching SUCCESS for chapter $chapterIndex.")
|
||||
semanticBlocks
|
||||
} catch (e: Exception) {
|
||||
Timber.tag("PosSaveDiag").e(e, "processAndCacheChapter: FAILED for chapter $chapterIndex")
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
@ -172,12 +153,10 @@ class LocatorConverter(
|
|||
}
|
||||
|
||||
if (allBlocks.isNullOrEmpty()) {
|
||||
Timber.tag("PosSaveDiag").w("getLocatorFromCfi: Cache missing or empty for chapter $chapterIndex. Triggering on-demand processing.")
|
||||
allBlocks = processAndCacheChapter(book, chapterIndex)
|
||||
}
|
||||
|
||||
if (allBlocks.isNullOrEmpty()) {
|
||||
Timber.tag("PosSaveDiag").e("getLocatorFromCfi: FAILED. Could not get or process semantic blocks.")
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ import kotlin.math.PI
|
|||
import kotlin.math.abs
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
|
@ -919,20 +920,27 @@ internal fun PdfPageComposable(
|
|||
if (count > 0) {
|
||||
val allAnnots = (0 until count).mapNotNull { i ->
|
||||
val subtype = NativePdfiumBridge.getAnnotSubtype(pagePtr, i)
|
||||
if (subtype == annotLink) return@mapNotNull null // skip links here
|
||||
if (subtype == annotLink) return@mapNotNull null
|
||||
|
||||
var contents = NativePdfiumBridge.getAnnotString(pagePtr, i, "Contents")
|
||||
if (contents.isNullOrBlank()) {
|
||||
contents = NativePdfiumBridge.getAnnotString(pagePtr, i, "RC")
|
||||
}
|
||||
|
||||
val contents = NativePdfiumBridge.getAnnotString(pagePtr, i, "Contents")
|
||||
val name = NativePdfiumBridge.getAnnotString(pagePtr, i, "NM")
|
||||
val irt = NativePdfiumBridge.getAnnotString(pagePtr, i, "IRT")
|
||||
val author = NativePdfiumBridge.getAnnotString(pagePtr, i, "T")
|
||||
|
||||
val pdfRectArray = NativePdfiumBridge.getAnnotRect(pagePtr, i)
|
||||
val pdfRectF = if (pdfRectArray != null) {
|
||||
android.graphics.RectF(pdfRectArray[0], pdfRectArray[3], pdfRectArray[2], pdfRectArray[1])
|
||||
android.graphics.RectF(
|
||||
min(pdfRectArray[0], pdfRectArray[2]),
|
||||
max(pdfRectArray[1], pdfRectArray[3]),
|
||||
max(pdfRectArray[0], pdfRectArray[2]),
|
||||
min(pdfRectArray[1], pdfRectArray[3])
|
||||
)
|
||||
} else android.graphics.RectF()
|
||||
|
||||
Timber.tag("PdfCommentDebug").v("Extracted Annot[$i]: Name=$name, IRT=$irt, Subtype=$subtype, Text=${contents?.take(10)}...")
|
||||
|
||||
EmbeddedAnnotation(i, subtype, pdfRectF, contents, author, name, irt)
|
||||
}
|
||||
|
||||
|
|
@ -2405,6 +2413,8 @@ internal fun PdfPageComposable(
|
|||
|
||||
detectTapGestures(onTap = { tapOffset ->
|
||||
val tapInContentCoords = screenToContentCoordinates(tapOffset)
|
||||
val tapXInBitmap = tapInContentCoords.x
|
||||
val tapYInBitmap = tapInContentCoords.y
|
||||
|
||||
coroutineScope.launch {
|
||||
val wasHandled = withContext(Dispatchers.IO) {
|
||||
|
|
@ -2439,9 +2449,6 @@ internal fun PdfPageComposable(
|
|||
}
|
||||
}
|
||||
|
||||
val tapXInBitmap = tapInContentCoords.x
|
||||
val tapYInBitmap = tapInContentCoords.y
|
||||
|
||||
val annotHitTolerance = with(density) { 24.dp.toPx() } / inputScale
|
||||
val hitTolerance = with(density) { 16.dp.toPx() } / inputScale
|
||||
|
||||
|
|
@ -2466,14 +2473,24 @@ internal fun PdfPageComposable(
|
|||
} else false
|
||||
}
|
||||
|
||||
val standardHit = standardAnnotScreenRects.findLast { (_, screenRect) ->
|
||||
val standardHit = standardAnnotScreenRects.findLast { (annot, screenRect) ->
|
||||
if (annot.subtype == 2) return@findLast false
|
||||
|
||||
val left = min(screenRect.left, screenRect.right)
|
||||
val right = max(screenRect.left, screenRect.right)
|
||||
val top = min(screenRect.top, screenRect.bottom)
|
||||
val bottom = max(screenRect.top, screenRect.bottom)
|
||||
|
||||
val inflatedHitBox = Rect(
|
||||
(screenRect.left - annotHitTolerance).toInt(),
|
||||
(screenRect.top - annotHitTolerance).toInt(),
|
||||
(screenRect.right + annotHitTolerance).toInt(),
|
||||
(screenRect.bottom + annotHitTolerance).toInt()
|
||||
(left - annotHitTolerance).toInt(),
|
||||
(top - annotHitTolerance).toInt(),
|
||||
(right + annotHitTolerance).toInt(),
|
||||
(bottom + annotHitTolerance).toInt()
|
||||
)
|
||||
inflatedHitBox.contains(tapInContentCoords.x.toInt(), tapInContentCoords.y.toInt())
|
||||
|
||||
val isHit = inflatedHitBox.contains(tapInContentCoords.x.toInt(), tapInContentCoords.y.toInt())
|
||||
|
||||
isHit
|
||||
}
|
||||
|
||||
if (standardHit != null) {
|
||||
|
|
|
|||
|
|
@ -2792,93 +2792,14 @@ fun PdfViewerScreen(
|
|||
withContext(Dispatchers.IO) { tempTextPage?.close() }
|
||||
}
|
||||
|
||||
if (rawPageText.isNullOrBlank()) {
|
||||
Timber.i(
|
||||
"TTS: Pdfium text is blank or extraction failed. Attempting OCR for page $pageToRead."
|
||||
)
|
||||
ocrAttempted = true
|
||||
ocrUsedForCurrentPageTts = true
|
||||
var ocrBitmap: Bitmap? = null
|
||||
try {
|
||||
if (tempPage == null) {
|
||||
withContext(Dispatchers.IO) {
|
||||
tempPage?.close()
|
||||
Timber.d("TTS/OCR: Re-opening page $pageToRead for bitmap rendering.")
|
||||
tempPage = pdfDocument!!.openPage(pageToRead)
|
||||
}
|
||||
}
|
||||
|
||||
val pageForOcr = tempPage ?: throw IllegalStateException(
|
||||
"TTS/OCR: PDF page couldn't be opened for OCR."
|
||||
)
|
||||
|
||||
val ocrBitmapWidth = 1080
|
||||
val ocrBitmapHeight: Int
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val originalWidthPoints = pageForOcr.getPageWidthPoint()
|
||||
val originalHeightPoints = pageForOcr.getPageHeightPoint()
|
||||
|
||||
if (originalWidthPoints <= 0 || originalHeightPoints <= 0) {
|
||||
throw IllegalStateException(
|
||||
"TTS/OCR: Invalid page dimensions (points) from Pdfium for page $pageToRead."
|
||||
)
|
||||
}
|
||||
val aspectRatio =
|
||||
originalWidthPoints.toFloat() / originalHeightPoints.toFloat()
|
||||
ocrBitmapHeight = (ocrBitmapWidth / aspectRatio).toInt()
|
||||
|
||||
if (ocrBitmapHeight <= 0) {
|
||||
throw IllegalStateException(
|
||||
"TTS/OCR: Calculated invalid bitmap dimensions for OCR ($ocrBitmapWidth x $ocrBitmapHeight) for page $pageToRead."
|
||||
)
|
||||
}
|
||||
|
||||
Timber.d(
|
||||
"TTS/OCR: Rendering page $pageToRead to bitmap of size ${ocrBitmapWidth}x$ocrBitmapHeight."
|
||||
)
|
||||
ocrBitmap = createBitmap(ocrBitmapWidth, ocrBitmapHeight)
|
||||
pageForOcr.renderPageBitmap(
|
||||
bitmap = ocrBitmap,
|
||||
startX = 0,
|
||||
startY = 0,
|
||||
drawSizeX = ocrBitmapWidth,
|
||||
drawSizeY = ocrBitmapHeight,
|
||||
renderAnnot = false
|
||||
)
|
||||
}
|
||||
Timber.d("TTS/OCR: Bitmap rendered for page $pageToRead. Attempting OCR.")
|
||||
|
||||
rawPageText = OcrHelper.extractTextFromBitmap(ocrBitmap!!) {
|
||||
isOcrModelDownloading = true
|
||||
}?.text
|
||||
|
||||
if (!rawPageText.isNullOrBlank()) {
|
||||
Timber.i(
|
||||
"TTS: Text extracted via OCR for page $pageToRead (length: ${rawPageText?.length})."
|
||||
)
|
||||
} else {
|
||||
Timber.w(
|
||||
"TTS: OCR process completed for page $pageToRead but returned no text or blank text."
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "TTS: Error during OCR process for page $pageToRead")
|
||||
} finally {
|
||||
ocrBitmap?.recycle()
|
||||
withContext(Dispatchers.IO) {
|
||||
tempPage?.close()
|
||||
Timber.d("TTS/OCR: Closed page $pageToRead after OCR attempt.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ocrUsedForCurrentPageTts = false
|
||||
withContext(Dispatchers.IO) {
|
||||
tempPage?.close()
|
||||
Timber.d(
|
||||
"TTS: Closed page $pageToRead after successful Pdfium text extraction."
|
||||
)
|
||||
}
|
||||
if (rawPageText.isNullOrBlank()) {
|
||||
Timber.i("TTS: Pdfium text is blank or extraction failed. OCR fallback is temporarily disabled.")
|
||||
} else {
|
||||
Timber.d("TTS: Closed page $pageToRead after successful Pdfium text extraction.")
|
||||
}
|
||||
|
||||
if (rawPageText != null && rawPageText!!.isNotBlank()) {
|
||||
|
|
|
|||
|
|
@ -473,7 +473,15 @@ class TtsPlaybackManager(
|
|||
}
|
||||
|
||||
if (!exists) {
|
||||
player.addMediaItem(nextMediaItem)
|
||||
var insertPosition = player.mediaItemCount
|
||||
for (k in 0 until player.mediaItemCount) {
|
||||
val id = player.getMediaItemAt(k).mediaId.toIntOrNull() ?: -1
|
||||
if (id > targetIndex) {
|
||||
insertPosition = k
|
||||
break
|
||||
}
|
||||
}
|
||||
player.addMediaItem(insertPosition, nextMediaItem)
|
||||
}
|
||||
|
||||
if (player.playbackState == Player.STATE_ENDED && player.playWhenReady && targetIndex == player.currentMediaItemIndex + 1) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue