Pdf reflow upgrade (#63)

* Replaced PDF-to-Markdown reflow with an enhanced PDF-to-HTML generator.

Specific changes include:
- Replaced `PdfToMarkdownGenerator` and `PdfReflowGenerator` with `PdfToHtmlGenerator`.
- Added native bridge methods in `NativePdfiumBridge.kt` and `pdfium_bridge.cpp` to extract character bounding boxes, page objects, and image pixels.
- Implemented vertical merging of text and images in `PdfToHtmlGenerator` to maintain document layout.
- Added logic to detect and filter repeating headers and footers across PDF pages.
- Updated `ReflowWorker` to generate `.html` files instead of `.md` files and updated `FileType` handling.
- Simplified `SingleFileImporter` by removing dependencies on the legacy Markdown generator.

* Updated PDF to HTML generation to include page breaks and split HTML files into individual chapters based on page markers.

* Added junk character filtering and normalization to PDF text extraction

* - Added `allRecentFiles` to `ReaderScreenState` to track all files, including reflowed versions.
- Updated `recentFiles` in `MainViewModel` to filter out reflowed files (`_reflow`) from the main library view.
- Implemented `deleteBookPermanently` in `MainViewModel` to handle book deletion and cache cleanup.
- Added a "Delete Text View" option to the `EpubReader` controls for reflowed files.
- Improved reflow file detection in `PdfViewerScreen` by checking against `allRecentFiles`.

* Implemented a centralized data-saving mechanism in `PdfViewerScreen` using a debounced `saveAllData` function. This refactor consolidates the saving of annotations, text boxes, highlights, bookmarks, and scroll positions, adding lifecycle-aware triggers and a mutex to ensure data integrity during pauses or document navigation.

* Optimized PDF selection and annotation performance by offloading heavy operations to background threads and improving UI responsiveness.

- Moved PDF page/text opening, character range calculations, and text extraction to `Dispatchers.IO`.
- Wrapped UI updates in `updateSelectionVisuals` and selection logic with `withContext(Dispatchers.Main)`.
- Implemented a more efficient `Popup`-based magnifier to replace manual offsets and transformations.
- Updated `PdfSelectionMenuPopup` properties to prevent focus and click-outside dismissal conflicts.
This commit is contained in:
Aryan 2026-03-13 17:02:09 +05:30 committed by GitHub
parent 9a95b4afcd
commit 843a77d0ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1270 additions and 895 deletions

View file

@ -71,7 +71,6 @@ import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
@ -164,8 +163,8 @@ data class EmbeddedAnnotation(
val rect: android.graphics.RectF,
val contents: String?,
val author: String?,
val name: String?, // Unique ID
val inReplyTo: String?, // ID of parent
val name: String?,
val inReplyTo: String?,
val replies: MutableList<EmbeddedAnnotation> = mutableListOf()
)
@ -1536,90 +1535,101 @@ internal fun PdfPageComposable(
providedTextPage: PdfTextPageKt? = null
) {
if (charRange == null || currentBitmapWidth == 0 || currentBitmapHeight == 0) {
selectedWordScreenRects = emptyList()
startHandleContentPosition.value = null
endHandleContentPosition.value = null
return
}
var localPage: PdfPageKt? = null
var localTextPage: PdfTextPageKt? = null
try {
val pageToUse: PdfPageKt
val textPageToUse: PdfTextPageKt
if (providedPage != null && providedTextPage != null) {
pageToUse = providedPage
textPageToUse = providedTextPage
} else {
localPage = doc.openPage(pageIdx)
localTextPage = localPage.openTextPage()
pageToUse = localPage
textPageToUse = localTextPage
}
val (startIndex, endIndex) = charRange
if (startIndex >= endIndex) {
withContext(Dispatchers.Main) {
selectedWordScreenRects = emptyList()
startHandleContentPosition.value = null
endHandleContentPosition.value = null
return
}
return
}
val length = endIndex - startIndex
val wordPdfRectsF =
textPageToUse.textPageGetRectsForRanges(intArrayOf(startIndex, length))?.map {
it.rect
} ?: emptyList()
withContext(Dispatchers.IO) {
var localPage: PdfPageKt? = null
var localTextPage: PdfTextPageKt? = null
if (wordPdfRectsF.isNotEmpty()) {
val mappedScreenRects = wordPdfRectsF.mapNotNull { pdfRectF ->
val screenRect = pageToUse.mapRectToDevice(
startX = 0,
startY = 0,
sizeX = currentBitmapWidth,
sizeY = currentBitmapHeight,
rotate = rotation,
coords = pdfRectF
)
if (screenRect.width() > 0 && screenRect.height() > 0) screenRect
else {
Timber.d(
"updateSelectionVisuals: Filtering out invalid screen rect: $screenRect"
try {
val pageToUse: PdfPageKt
val textPageToUse: PdfTextPageKt
if (providedPage != null && providedTextPage != null) {
pageToUse = providedPage
textPageToUse = providedTextPage
} else {
localPage = doc.openPage(pageIdx)
localTextPage = localPage.openTextPage()
pageToUse = localPage
textPageToUse = localTextPage
}
val (startIndex, endIndex) = charRange
if (startIndex >= endIndex) {
withContext(Dispatchers.Main) {
selectedWordScreenRects = emptyList()
startHandleContentPosition.value = null
endHandleContentPosition.value = null
}
return@withContext
}
val length = endIndex - startIndex
val wordPdfRectsF =
textPageToUse.textPageGetRectsForRanges(intArrayOf(startIndex, length))?.map {
it.rect
} ?: emptyList()
if (wordPdfRectsF.isNotEmpty()) {
val mappedScreenRects = wordPdfRectsF.mapNotNull { pdfRectF ->
val screenRect = pageToUse.mapRectToDevice(
startX = 0,
startY = 0,
sizeX = currentBitmapWidth,
sizeY = currentBitmapHeight,
rotate = rotation,
coords = pdfRectF
)
null
if (screenRect.width() > 0 && screenRect.height() > 0) screenRect
else {
Timber.d(
"updateSelectionVisuals: Filtering out invalid screen rect: $screenRect"
)
null
}
}
withContext(Dispatchers.Main) {
selectedWordScreenRects = mappedScreenRects
if (mappedScreenRects.isNotEmpty()) {
val firstRect = mappedScreenRects.first()
val lastRect = mappedScreenRects.last()
startHandleContentPosition.value =
Offset(firstRect.left.toFloat(), firstRect.bottom.toFloat())
endHandleContentPosition.value =
Offset(lastRect.right.toFloat(), lastRect.bottom.toFloat())
} else {
selectedWordScreenRects = emptyList()
startHandleContentPosition.value = null
endHandleContentPosition.value = null
}
}
} else {
withContext(Dispatchers.Main) {
selectedWordScreenRects = emptyList()
startHandleContentPosition.value = null
endHandleContentPosition.value = null
}
}
selectedWordScreenRects = mappedScreenRects
if (mappedScreenRects.isNotEmpty()) {
val firstRect = mappedScreenRects.first()
val lastRect = mappedScreenRects.last()
startHandleContentPosition.value =
Offset(firstRect.left.toFloat(), firstRect.bottom.toFloat())
endHandleContentPosition.value =
Offset(lastRect.right.toFloat(), lastRect.bottom.toFloat())
} else {
} catch (e: Exception) {
Timber.e(e, "Error updating selection visuals for page $pageIdx, range $charRange: $e")
withContext(Dispatchers.Main) {
selectedWordScreenRects = emptyList()
startHandleContentPosition.value = null
endHandleContentPosition.value = null
}
} else {
selectedWordScreenRects = emptyList()
startHandleContentPosition.value = null
endHandleContentPosition.value = null
}
} catch (e: Exception) {
Timber.e(e, "Error updating selection visuals for page $pageIdx, range $charRange: $e")
selectedWordScreenRects = emptyList()
startHandleContentPosition.value = null
endHandleContentPosition.value = null
} finally {
if (providedPage == null && providedTextPage == null) {
withContext(NonCancellable) {
withContext(Dispatchers.IO) {
} finally {
if (providedPage == null && providedTextPage == null) {
withContext(NonCancellable) {
try {
localTextPage?.close()
} catch (_: Exception) {
@ -2105,14 +2115,14 @@ internal fun PdfPageComposable(
var pageForMenu: PdfPageKt? = null
var textPageForMenu: PdfTextPageKt? = null
try {
pageForMenu = pdfDocumentItem.openPage(
pdfPageIndex
)
textPageForMenu = pageForMenu.openTextPage()
val text = textPageForMenu.textPageGetText(
currentRange.first,
currentRange.second - currentRange.first
)
val text = withContext(Dispatchers.IO) {
pageForMenu = pdfDocumentItem.openPage(pdfPageIndex)
textPageForMenu = pageForMenu.openTextPage()
textPageForMenu.textPageGetText(
currentRange.first,
currentRange.second - currentRange.first
)
}
if (!text.isNullOrBlank()) {
val combinedRect = Rect(selectedWordScreenRects.first())
selectedWordScreenRects.forEach { combinedRect.union(it) }
@ -2220,79 +2230,81 @@ internal fun PdfPageComposable(
try {
if (!isPdfPage) return@launch
tempPage = pdfDocumentItem.openPage(pdfPageIndex)
tempTextPage = tempPage.openTextPage()
val touchInContentCoords = screenToContentCoordinates(
down.position
)
Timber.d(
"Long press: initial touch in content coords: $touchInContentCoords"
)
val touchInContentCoords = screenToContentCoordinates(down.position)
Timber.d("Long press: initial touch in content coords: $touchInContentCoords")
if (touchInContentCoords.x < 0 || touchInContentCoords.x > actualBitmapWidthPx || touchInContentCoords.y < 0 || touchInContentCoords.y > actualBitmapHeightPx) {
Timber.d(
"Long press: Touch point outside bitmap bounds."
)
Timber.d("Long press: Touch point outside bitmap bounds.")
return@launch
}
val pdfCoords = tempPage.mapDeviceCoordsToPage(
startX = 0,
startY = 0,
sizeX = actualBitmapWidthPx,
sizeY = actualBitmapHeightPx,
rotate = currentPageRotation,
deviceX = touchInContentCoords.x.toInt(),
deviceY = touchInContentCoords.y.toInt()
)
val charTolerance = 5.0
val charIndex = tempTextPage.textPageGetCharIndexAtPos(
x = pdfCoords.x.toDouble(),
y = pdfCoords.y.toDouble(),
xTolerance = charTolerance,
yTolerance = charTolerance
)
var pdfiumSelectionSuccessful = false
if (charIndex != -1) {
val pageCharCount = tempTextPage.textPageCountChars()
val wordBoundaries = findWordBoundaries(
tempTextPage, charIndex, pageCharCount
withContext(Dispatchers.IO) {
tempPage = pdfDocumentItem.openPage(pdfPageIndex)
tempTextPage = tempPage.openTextPage()
val pdfCoords = tempPage.mapDeviceCoordsToPage(
startX = 0,
startY = 0,
sizeX = actualBitmapWidthPx,
sizeY = actualBitmapHeightPx,
rotate = currentPageRotation,
deviceX = touchInContentCoords.x.toInt(),
deviceY = touchInContentCoords.y.toInt()
)
val charTolerance = 5.0
val charIndex = tempTextPage.textPageGetCharIndexAtPos(
x = pdfCoords.x.toDouble(),
y = pdfCoords.y.toDouble(),
xTolerance = charTolerance,
yTolerance = charTolerance
)
if (wordBoundaries != null) {
selectionMethodUsed = PdfSelectionMethod.PDFIUM
selectionCharRange.value = wordBoundaries
updateSelectionVisuals(
pdfDocumentItem,
pdfPageIndex,
selectionCharRange.value,
actualBitmapWidthPx,
actualBitmapHeightPx,
currentPageRotation,
providedPage = tempPage,
providedTextPage = tempTextPage
if (charIndex != -1) {
val pageCharCount = tempTextPage.textPageCountChars()
val wordBoundaries = findWordBoundaries(
tempTextPage, charIndex, pageCharCount
)
if (selectionCharRange.value != null && selectedWordScreenRects.isNotEmpty()) {
val currentRange = selectionCharRange.value!!
val text = tempTextPage.textPageGetText(
currentRange.first,
currentRange.second - currentRange.first
)
if (!text.isNullOrBlank()) {
val combinedRect = Rect(selectedWordScreenRects.first())
selectedWordScreenRects.forEach { combinedRect.union(it) }
customMenuState = CustomPdfMenuState(
selectedText = text,
anchorRect = combinedRect,
charRange = currentRange
)
pdfiumSelectionSuccessful = true
Timber.d(
"Long press: PDFIUM selection successful. Menu: ${customMenuState?.anchorRect}"
)
if (wordBoundaries != null) {
withContext(Dispatchers.Main) {
selectionMethodUsed = PdfSelectionMethod.PDFIUM
selectionCharRange.value = wordBoundaries
}
updateSelectionVisuals(
pdfDocumentItem,
pdfPageIndex,
wordBoundaries,
actualBitmapWidthPx,
actualBitmapHeightPx,
currentPageRotation,
providedPage = tempPage,
providedTextPage = tempTextPage
)
withContext(Dispatchers.Main) {
if (selectionCharRange.value != null && selectedWordScreenRects.isNotEmpty()) {
val currentRange = selectionCharRange.value!!
val text = withContext(Dispatchers.IO) {
tempTextPage.textPageGetText(
currentRange.first,
currentRange.second - currentRange.first
)
}
if (!text.isNullOrBlank()) {
val combinedRect = Rect(selectedWordScreenRects.first())
selectedWordScreenRects.forEach { combinedRect.union(it) }
customMenuState = CustomPdfMenuState(
selectedText = text,
anchorRect = combinedRect,
charRange = currentRange
)
pdfiumSelectionSuccessful = true
Timber.d(
"Long press: PDFIUM selection successful. Menu: ${customMenuState?.anchorRect}"
)
}
}
}
}
}
@ -3682,9 +3694,12 @@ internal fun PdfPageComposable(
var page: PdfPageKt? = null
var textPage: PdfTextPageKt? = null
try {
page = pdfDocumentItem.openPage(pdfPageIndex)
textPage = page.openTextPage()
val charCount = textPage.textPageCountChars()
val charCount = withContext(Dispatchers.IO) {
page = pdfDocumentItem.openPage(pdfPageIndex)
textPage = page.openTextPage()
textPage.textPageCountChars()
}
if (charCount > 0) {
selectionCharRange.value = Pair(0, charCount)
updateSelectionVisuals(
@ -3698,8 +3713,9 @@ internal fun PdfPageComposable(
providedTextPage = textPage
)
if (selectedWordScreenRects.isNotEmpty()) {
val fullText =
textPage.textPageGetText(0, charCount)
val fullText = withContext(Dispatchers.IO) {
textPage!!.textPageGetText(0, charCount)
}
if (!fullText.isNullOrBlank()) {
val combinedRect = Rect(selectedWordScreenRects.first())
selectedWordScreenRects.forEach { combinedRect.union(it) }
@ -3714,9 +3730,11 @@ internal fun PdfPageComposable(
} catch (e: Exception) {
Timber.e(e, "Failed to select all")
} finally {
withContext(Dispatchers.IO) {
textPage?.close()
page?.close()
withContext(NonCancellable) {
withContext(Dispatchers.IO) {
textPage?.close()
page?.close()
}
}
}
} else {
@ -4124,7 +4142,7 @@ internal sealed interface AnnotationRenderData {
internal object PdfAnnotationRenderHelper {
fun createRenderData(annot: PdfAnnotation, widthPx: Int, heightPx: Int): AnnotationRenderData? {
val startTime = System.nanoTime()
if (annot.points.isEmpty()) return null
if (annot.points.size == 1) {
@ -4277,6 +4295,10 @@ internal object PdfAnnotationRenderHelper {
)
}
}
val duration = (System.nanoTime() - startTime) / 1_000_000f
if (duration > 1f) {
Timber.tag("PdfPerf").v("Path Gen: Type=${annot.inkType}, Pts=${annot.points.size}, Time=${duration}ms")
}
return result
}
}
@ -4291,8 +4313,21 @@ private fun PdfAnnotationLayer(
centeringOffsetY: Float,
pageIndex: Int
) {
SideEffect { Timber.tag("PdfDrawPerf").v("ANNOT LAYER: Recomposing (Page $pageIndex)") }
SideEffect { Timber.tag("PdfPerf").v("ANNOT_LAYER: Recomposing Page $pageIndex") }
val staticAnnotations = annotationsProvider()
val staticRenderData = remember(staticAnnotations, actualBitmapWidthPx, actualBitmapHeightPx) {
val startTime = System.nanoTime()
val data = staticAnnotations.mapNotNull { annot ->
PdfAnnotationRenderHelper.createRenderData(
annot, actualBitmapWidthPx, actualBitmapHeightPx
)
}
val duration = (System.nanoTime() - startTime) / 1_000_000f
Timber.tag("PdfPerf").d("ANNOT_LAYER: Processed ${staticAnnotations.size} static annots in ${duration}ms")
data
}
val currentAnnotation = remember(drawingState, pageIndex) {
derivedStateOf {
val annot = drawingState?.currentAnnotation
@ -4312,30 +4347,21 @@ private fun PdfAnnotationLayer(
)
}
val staticRenderData = remember(staticAnnotations, actualBitmapWidthPx, actualBitmapHeightPx) {
staticAnnotations.mapNotNull { annot ->
PdfAnnotationRenderHelper.createRenderData(
annot, actualBitmapWidthPx, actualBitmapHeightPx
)
}
}
val activeRenderData = remember(
currentAnnotation,
currentAnnotation?.points?.size,
actualBitmapWidthPx,
actualBitmapHeightPx
) {
if (currentAnnotation != null) {
Timber.tag("PdfDrawPerf").v(
"ANNOT LAYER: Generating active path for ${currentAnnotation.points.size} points"
)
val startTime = System.nanoTime()
val res = currentAnnotation?.let { annot ->
PdfAnnotationRenderHelper.createRenderData(annot, actualBitmapWidthPx, actualBitmapHeightPx)
}
currentAnnotation?.let { annot ->
PdfAnnotationRenderHelper.createRenderData(
annot, actualBitmapWidthPx, actualBitmapHeightPx
)
val duration = (System.nanoTime() - startTime) / 1_000_000f
if (duration > 0.5f) {
Timber.tag("PdfPerf").v("ANNOT_LAYER: Active path gen took ${duration}ms")
}
res
}
Canvas(modifier = Modifier.fillMaxSize()) {
@ -4386,9 +4412,9 @@ private fun PdfAnnotationLayer(
activeRenderData?.let { drawData(it) }
}
val drawDuration = (System.nanoTime() - drawStart) / 1_000_000f
Timber.tag("PdfDrawPerf").v(
"ANNOT DRAW: Canvas draw took ${drawDuration}ms. Points: ${currentAnnotation?.points?.size ?: 0}"
)
if (drawDuration > 2f) {
Timber.tag("PdfPerf").v("ANNOT_DRAW: Canvas draw took ${drawDuration}ms (Page $pageIndex)")
}
}
}
@ -4528,11 +4554,15 @@ private fun PdfPageRenderer(
onHighlightUpdate: (String, PdfHighlightColor) -> Unit,
onHighlightDelete: (String) -> Unit,
) {
SideEffect {
Timber.tag("PdfPerf").v("PAGE_RENDERER: Recomposing Page ${selectionData.pageIndex}. DraggingHandle=${activeDraggingHandle != null}")
}
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
Timber.tag("PdfPerf").v("GraphicsLayer Update: Scale=$scale, Offset=$offset")
scaleX = scale
scaleY = scale
translationX = offset.x
@ -4783,7 +4813,6 @@ private fun PdfPageRenderer(
}
if (showMagnifier && activeDraggingHandle != null && staticData.bitmap.item != null) {
val handleContentPos = when (activeDraggingHandle) {
Handle.START -> startHandlePos
Handle.END -> endHandlePos
@ -4795,39 +4824,44 @@ private fun PdfPageRenderer(
val magnifierWidth = 120.dp
val magnifierHeight = 60.dp
val magnifierOffsetAboveHandle = 24.dp
val effectiveScale = staticData.effectiveScale
with(density) {
val magnifierWidthPx = magnifierWidth.toPx()
val magnifierHeightPx = magnifierHeight.toPx()
val magnifierOffsetAboveHandlePx = magnifierOffsetAboveHandle.toPx()
val effectiveScale = staticData.effectiveScale
val effectiveZoomFactor = if (isVerticalScroll && effectiveScale > 1f) {
effectiveScale * 1.25f
} else {
magnifierZoomFactor
}
val modifier: Modifier
val effectiveZoomFactor: Float
val popupPositionProvider = remember(pos, layoutCoordinates, density) {
object : androidx.compose.ui.window.PopupPositionProvider {
override fun calculatePosition(
anchorBounds: androidx.compose.ui.unit.IntRect,
windowSize: androidx.compose.ui.unit.IntSize,
layoutDirection: androidx.compose.ui.unit.LayoutDirection,
popupContentSize: androidx.compose.ui.unit.IntSize
): androidx.compose.ui.unit.IntOffset {
val coords = layoutCoordinates ?: return androidx.compose.ui.unit.IntOffset.Zero
if (isVerticalScroll && effectiveScale > 1f) {
val yOffsetPixels =
pos.y - (magnifierHeightPx + magnifierOffsetAboveHandlePx) / effectiveScale
val xOffsetPixels = pos.x - (magnifierWidthPx / 2) / effectiveScale
val windowPos = coords.localToWindow(pos)
val offsetPx = with(density) { magnifierOffsetAboveHandle.toPx() }
modifier =
Modifier
.offset(x = xOffsetPixels.toDp(), y = yOffsetPixels.toDp())
.graphicsLayer(
scaleX = 1f / effectiveScale,
scaleY = 1f / effectiveScale,
transformOrigin = TransformOrigin(0f, 0f)
)
val x = (windowPos.x - popupContentSize.width / 2).toInt()
val y = (windowPos.y - popupContentSize.height - offsetPx).toInt()
effectiveZoomFactor = effectiveScale * 1.25f
} else {
val xOffsetVal = pos.x - magnifierWidthPx / 2
val yOffsetVal = pos.y - magnifierHeightPx - magnifierOffsetAboveHandlePx
modifier = Modifier.offset(x = xOffsetVal.toDp(), y = yOffsetVal.toDp())
effectiveZoomFactor = magnifierZoomFactor
return androidx.compose.ui.unit.IntOffset(x, y)
}
}
}
androidx.compose.ui.window.Popup(
popupPositionProvider = popupPositionProvider,
properties = androidx.compose.ui.window.PopupProperties(
focusable = false,
dismissOnClickOutside = false,
dismissOnBackPress = false,
usePlatformDefaultWidth = false
)
) {
MagnifierComposable(
sourceBitmap = staticData.bitmap.item.asImageBitmap(),
tiles = if (effectiveScale > 1f) staticData.tiles.item else emptyList(),
@ -4839,7 +4873,7 @@ private fun PdfPageRenderer(
selectionRectsInBitmapCoords = selectionData.mergedSelectionRects.item,
highlightColor = Color(0x6633B5E5),
colorFilter = staticData.colorFilter.item,
modifier = modifier
modifier = Modifier
)
}
}
@ -4848,9 +4882,10 @@ private fun PdfPageRenderer(
if (menuState != null) {
BackHandler(enabled = true, onBack = onMenuDismiss)
}
menuState?.let { state ->
if (state.anchorRect.width() > 0 || state.anchorRect.height() > 0) {
val popupPositionProvider = remember(state.anchorRect, density, offset, scale, layoutCoordinates) {
if (menuState != null && !isScrolling && draggingBoxId == null && activeDraggingHandle == null) {
if (menuState.anchorRect.width() > 0 || menuState.anchorRect.height() > 0) {
val popupPositionProvider = remember(menuState.anchorRect, density, offset, scale, layoutCoordinates) {
object : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
@ -4861,8 +4896,12 @@ private fun PdfPageRenderer(
val coords = layoutCoordinates ?: return IntOffset.Zero
// Map the bitmap-space anchor (the icon) to window-space
val topLeftLocal = contentToScreenCoordinates(Offset(state.anchorRect.left.toFloat(), state.anchorRect.top.toFloat()))
val bottomRightLocal = contentToScreenCoordinates(Offset(state.anchorRect.right.toFloat(), state.anchorRect.bottom.toFloat()))
val topLeftLocal = contentToScreenCoordinates(Offset(
menuState.anchorRect.left.toFloat(),
menuState.anchorRect.top.toFloat()))
val bottomRightLocal = contentToScreenCoordinates(Offset(
menuState.anchorRect.right.toFloat(),
menuState.anchorRect.bottom.toFloat()))
val topLeftWindow = coords.localToWindow(topLeftLocal)
val bottomRightWindow = coords.localToWindow(bottomRightLocal)
@ -4890,30 +4929,28 @@ private fun PdfPageRenderer(
}
PdfSelectionMenuPopup(
menuState = state,
menuState = menuState,
popupPositionProvider = popupPositionProvider,
onDismiss = onMenuDismiss,
onCopy = onCopy,
onAiDefine = onAiDefine,
onSelectAll = onSelectAll,
onColorSelected = { color ->
Timber.tag("PdfHighlightDebug").d("PdfSelectionMenuPopup onColorSelected: $color, isExisting=${state.isExistingHighlight}")
if (state.isExistingHighlight && state.highlightId != null) {
onHighlightUpdate(state.highlightId, color)
Timber.tag("PdfHighlightDebug").d("PdfSelectionMenuPopup onColorSelected: $color, isExisting=${menuState.isExistingHighlight}")
if (menuState.isExistingHighlight && menuState.highlightId != null) {
onHighlightUpdate(menuState.highlightId, color)
} else {
Timber.tag("PdfHighlightDebug").d("Calling onHighlightAdd for page ${selectionData.pageIndex}")
onHighlightAdd(
selectionData.pageIndex,
state.charRange,
state.selectedText,
selectionData.pageIndex, menuState.charRange, menuState.selectedText,
color
)
}
onMenuDismiss()
},
onDelete = {
if (state.isExistingHighlight && state.highlightId != null) {
onHighlightDelete(state.highlightId)
if (menuState.isExistingHighlight && menuState.highlightId != null) {
onHighlightDelete(menuState.highlightId)
}
onMenuDismiss()
}