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:
Aryan 2026-03-21 11:49:27 +05:30 committed by GitHub
parent ddcd253c7b
commit fbe8e7aa56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 95 additions and 184 deletions

View file

@ -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) {

View file

@ -2792,93 +2792,14 @@ fun PdfViewerScreen(
withContext(Dispatchers.IO) { tempTextPage?.close() }
}
ocrUsedForCurrentPageTts = false
withContext(Dispatchers.IO) {
tempPage?.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.")
}
}
Timber.i("TTS: Pdfium text is blank or extraction failed. OCR fallback is temporarily disabled.")
} else {
ocrUsedForCurrentPageTts = false
withContext(Dispatchers.IO) {
tempPage?.close()
Timber.d(
"TTS: Closed page $pageToRead after successful Pdfium text extraction."
)
}
Timber.d("TTS: Closed page $pageToRead after successful Pdfium text extraction.")
}
if (rawPageText != null && rawPageText!!.isNotBlank()) {