v1.0.47 (#279)
* Add performance and stylus debugging logs * Refactor and decouple UI models from `MainViewModel` * Refactor library state management and projection logic * Implement desktop shell using Compose Multiplatform * Implement desktop shell using Compose Multiplatform * Implement desktop shell using Compose Multiplatform * Introduce ReaderEngine and enhance EPUB reader features in windows app * Move core paginated reader logic to a Kotlin Multiplatform `shared` module and introduce experimental desktop support. * Implement PDF rendering and text extraction for desktop using Pdfium * Add `NonReaderScreens.kt` and UI dependencies * Refactor and centralize library state management and models to improve cross-platform consistency * Implement JSON persistence for desktop library and enhance library management features including shelf CRUD, tagging, and metadata editing * Implement PDF annotation system and enhanced zoom controls for the desktop viewer * Implement WebView-based EPUB rendering for desktop using CEF and embedded resources * Optimize UI state projection, navigation state handling, and main screen pager performance * Implement Bring Your Own Key (BYOK) support for AI features in OSS version * Support Gemini-based Cloud TTS with BYOK support for OSS builds * Refactor table cell image sizing in `PaginatedReader` and improve `MobiParser` native library loading and error handling. * crash fixes * Enhance navigation stability with lifecycle-aware safety checks and update `navigation-compose` to 2.9.6 * Implement dynamic bottom padding for the page info bar to account for device rounded corners * Implement bidirectional jump history navigation and replace the jump-back pill with a dedicated `PdfJumpHistoryBar` * Optimize PDF tiling performance and refine pan-and-fling gesture handling * Implement customizable toolbars with drag-and-drop reordering and placement for PDF and EPUB readers * Updated UI for customize toolbar * Refine drag-and-drop reordering and section assignment for PDF and EPUB reader controls * restructure PDF viewer UI component hierarchy to fix verifier crash * Implement separate text dimming factors for light and dark themes * Synchronize Pdfium access and improve resource lifecycle safety across Kotlin and native layers * Enhance image alignment in paginated and EPUB readers through anchor detection and style-based positioning * Centralize file type resolution logic and implement HTML sanitization during import * Introduce vertical margin customization and configurable progress bar positioning * texture support in epub reader * Enhance TTS session management, progress tracking, and diagnostic logging * Optimize library state projection and folder synchronization performance by refactoring collection lookups and refining metadata extraction logic. * Refine TTS page mapping for PDF and overhaul TTS control UI * Implement natural session completion logic in `TtsPlaybackManager` for cloud tts * Replace Snackbar with `CustomTopBanner` for notifications in `PdfViewerScreen` * Refine TTS playback continuity across PDF pages and improve state management for session transitions * Implement global texture transparency and enhance textured theme support across PDF and EPUB readers. * Update reader themes and improve texture rendering in page animations, EPUB UI, and immersive mode * Add Support Project screen * Optimize library performance via projection caching, batch database updates, and scoped folder synchronization. * Enhance folder synchronization with fallback query mechanisms and refactor annotation sidecar importing logic * Bump version to 1.0.47 (51)
This commit is contained in:
parent
f42de6b462
commit
d7a9cae9e1
126 changed files with 15287 additions and 3154 deletions
|
|
@ -69,9 +69,13 @@ import androidx.compose.ui.graphics.BlendMode
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.ImageShader
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.ShaderBrush
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.TileMode
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
|
|
@ -119,6 +123,7 @@ import androidx.core.graphics.scale
|
|||
import androidx.core.graphics.set
|
||||
import com.aryan.reader.R
|
||||
import com.aryan.reader.SearchResult
|
||||
import com.aryan.reader.loadReaderTextureBitmap
|
||||
import com.aryan.reader.ml.SpeechBubble
|
||||
import com.aryan.reader.pdf.data.PdfAnnotation
|
||||
import com.aryan.reader.pdf.data.PdfTextBox
|
||||
|
|
@ -127,6 +132,7 @@ import com.aryan.reader.pdf.ocr.OcrElement
|
|||
import com.aryan.reader.pdf.ocr.OcrResult
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
|
@ -180,6 +186,13 @@ data class PdfPoint(val x: Float, val y: Float, val timestamp: Long = 0L)
|
|||
|
||||
data class PdfTile(val bitmap: Bitmap, val renderRect: Rect, val tileId: Int, val renderScale: Float = 1f)
|
||||
|
||||
private const val PDF_TILE_SIZE_DP = 256
|
||||
private const val PDF_MAX_TILE_BITMAP_SIZE_PX = 3072
|
||||
private const val PDF_TILE_SCALE_TOLERANCE = 0.06f
|
||||
private const val PDF_TILE_IDLE_RENDER_DELAY_MS = 90L
|
||||
private const val PDF_PAGINATION_PAN_FLING_MIN_VELOCITY = 600f
|
||||
private const val PDF_PAGINATION_PAN_FLING_MULTIPLIER = 0.72f
|
||||
|
||||
enum class LinkSource {
|
||||
ANNOTATION, TEXT_CONTENT
|
||||
}
|
||||
|
|
@ -426,7 +439,10 @@ data class PageStaticData(
|
|||
val colorFilter: StableHolder<ColorFilter?>,
|
||||
val isDarkMode: Boolean,
|
||||
val excludeImages: Boolean,
|
||||
val imageRects: StableHolder<List<android.graphics.Rect>>
|
||||
val imageRects: StableHolder<List<android.graphics.Rect>>,
|
||||
val textureBitmap: StableHolder<ImageBitmap?>,
|
||||
val textureAlpha: Float,
|
||||
val textureBlendMode: BlendMode
|
||||
)
|
||||
|
||||
@Stable
|
||||
|
|
@ -497,6 +513,7 @@ internal fun PdfPageComposable(
|
|||
onTtsHighlightCenterCalculated: ((Float) -> Unit)? = null,
|
||||
onSearchHighlightCenterCalculated: ((Float) -> Unit)? = null,
|
||||
activeTheme: com.aryan.reader.ReaderTheme = com.aryan.reader.ReaderTheme("no_theme", "No Theme", Color.Unspecified, Color.Unspecified, false),
|
||||
activeTextureAlpha: Float = 0.55f,
|
||||
excludeImages: Boolean = false,
|
||||
onDoubleTap: ((Offset) -> Unit)? = null,
|
||||
isEditMode: Boolean = false,
|
||||
|
|
@ -552,7 +569,7 @@ internal fun PdfPageComposable(
|
|||
var isLoadingPage by remember { mutableStateOf(true) }
|
||||
var pageErrorMessage by remember { mutableStateOf<String?>(null) }
|
||||
val density = LocalDensity.current
|
||||
LocalContext.current
|
||||
val context = LocalContext.current
|
||||
val viewConfiguration = LocalViewConfiguration.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var isStylusEraserOverride by remember { mutableStateOf(false) }
|
||||
|
|
@ -564,6 +581,7 @@ internal fun PdfPageComposable(
|
|||
var isTransforming by remember { mutableStateOf(false) }
|
||||
var scale by remember { mutableFloatStateOf(1f) }
|
||||
var offset by remember { mutableStateOf(Offset.Zero) }
|
||||
var paginationPanFlingJob by remember { mutableStateOf<Job?>(null) }
|
||||
|
||||
LaunchedEffect(scale, offset) {
|
||||
onZoomAndPanChanged?.invoke(scale, offset)
|
||||
|
|
@ -590,8 +608,10 @@ internal fun PdfPageComposable(
|
|||
val pdfPageIndex = (virtualPage as? VirtualPage.PdfPage)?.pdfIndex ?: pageIndex
|
||||
|
||||
var tiles by remember { mutableStateOf<List<PdfTile>>(emptyList()) }
|
||||
val tileSizeDp = 256.dp
|
||||
val tileSizeDp = PDF_TILE_SIZE_DP.dp
|
||||
val tileSizePx = with(LocalDensity.current) { tileSizeDp.toPx().toInt() }
|
||||
val latestEffectiveScale by rememberUpdatedState(effectiveScale)
|
||||
val latestEffectiveOffset by rememberUpdatedState(effectiveOffset)
|
||||
|
||||
SideEffect {
|
||||
Timber.tag("PdfDrawPerf")
|
||||
|
|
@ -633,6 +653,8 @@ internal fun PdfPageComposable(
|
|||
var actualBitmapHeightPx by remember { mutableIntStateOf(0) }
|
||||
var currentPageRotation by remember { mutableIntStateOf(0) }
|
||||
|
||||
val needsTilingNow = (effectiveScale > 1f || actualBitmapWidthPx > 3000 || actualBitmapHeightPx > 3000) && (isVerticalScroll || isActivePage)
|
||||
|
||||
val canvasWidthPx = remember { mutableFloatStateOf(0f) }
|
||||
val canvasHeightPx = remember { mutableFloatStateOf(0f) }
|
||||
|
||||
|
|
@ -687,6 +709,15 @@ internal fun PdfPageComposable(
|
|||
activeTheme.backgroundColor
|
||||
}
|
||||
}
|
||||
val textureBitmap = remember(activeTheme.textureId) {
|
||||
loadReaderTextureBitmap(context, activeTheme.textureId)
|
||||
}
|
||||
val effectiveTextureAlpha = remember(activeTheme.textureId, activeTextureAlpha) {
|
||||
if (activeTheme.textureId == null) 0f else activeTextureAlpha.coerceIn(0f, 1f)
|
||||
}
|
||||
val textureBlendMode = remember(activeTheme.textureId, activeTheme.isDark, activeTheme.id) {
|
||||
if (activeTheme.isDark || activeTheme.id == "reverse") BlendMode.Screen else BlendMode.Multiply
|
||||
}
|
||||
|
||||
val centeringOffsetX by remember(canvasWidthPx.floatValue, actualBitmapWidthPx) {
|
||||
derivedStateOf { (canvasWidthPx.floatValue - actualBitmapWidthPx) / 2f }
|
||||
|
|
@ -1146,13 +1177,13 @@ internal fun PdfPageComposable(
|
|||
try {
|
||||
val pagePtr = pageWrapper.getNativePointer()
|
||||
if (pagePtr != 0L) {
|
||||
val objCount = NativePdfiumBridge.getPageObjectCount(pagePtr)
|
||||
val objCount = PdfiumEngineProvider.bridge.getPageObjectCount(pagePtr)
|
||||
val imgRects = mutableListOf<android.graphics.Rect>()
|
||||
val outRect = FloatArray(4)
|
||||
|
||||
for (i in 0 until objCount) {
|
||||
if (NativePdfiumBridge.getPageObjectType(pagePtr, i) == 3) { // 3 = FPDF_PAGEOBJ_IMAGE
|
||||
if (NativePdfiumBridge.getPageObjectBoundingBox(pagePtr, i, outRect)) {
|
||||
if (PdfiumEngineProvider.bridge.getPageObjectType(pagePtr, i) == 3) { // 3 = FPDF_PAGEOBJ_IMAGE
|
||||
if (PdfiumEngineProvider.bridge.getPageObjectBoundingBox(pagePtr, i, outRect)) {
|
||||
val pdfRectF = android.graphics.RectF(
|
||||
min(outRect[0], outRect[2]),
|
||||
max(outRect[1], outRect[3]),
|
||||
|
|
@ -1180,26 +1211,26 @@ internal fun PdfPageComposable(
|
|||
val pagePtr = pageWrapper.getNativePointer()
|
||||
|
||||
if (pagePtr != 0L) {
|
||||
val count = NativePdfiumBridge.getAnnotCount(pagePtr)
|
||||
val count = PdfiumEngineProvider.bridge.getAnnotCount(pagePtr)
|
||||
Timber.tag("PdfCommentDebug").d("Page $pageIndex: Total Annotations found = $count")
|
||||
if (count > 0) {
|
||||
val count = NativePdfiumBridge.getAnnotCount(pagePtr)
|
||||
val count = PdfiumEngineProvider.bridge.getAnnotCount(pagePtr)
|
||||
Timber.tag("PdfCommentDebug").d("Page $pageIndex: Total Annotations found = $count")
|
||||
if (count > 0) {
|
||||
val allAnnots = (0 until count).mapNotNull { i ->
|
||||
val subtype = NativePdfiumBridge.getAnnotSubtype(pagePtr, i)
|
||||
val subtype = PdfiumEngineProvider.bridge.getAnnotSubtype(pagePtr, i)
|
||||
if (subtype == annotLink) return@mapNotNull null
|
||||
|
||||
var contents = NativePdfiumBridge.getAnnotString(pagePtr, i, "Contents")
|
||||
var contents = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "Contents")
|
||||
if (contents.isNullOrBlank()) {
|
||||
contents = NativePdfiumBridge.getAnnotString(pagePtr, i, "RC")
|
||||
contents = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "RC")
|
||||
}
|
||||
|
||||
val name = NativePdfiumBridge.getAnnotString(pagePtr, i, "NM")
|
||||
val irt = NativePdfiumBridge.getAnnotString(pagePtr, i, "IRT")
|
||||
val author = NativePdfiumBridge.getAnnotString(pagePtr, i, "T")
|
||||
val name = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "NM")
|
||||
val irt = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "IRT")
|
||||
val author = PdfiumEngineProvider.bridge.getAnnotString(pagePtr, i, "T")
|
||||
|
||||
val pdfRectArray = NativePdfiumBridge.getAnnotRect(pagePtr, i)
|
||||
val pdfRectArray = PdfiumEngineProvider.bridge.getAnnotRect(pagePtr, i)
|
||||
val pdfRectF = if (pdfRectArray != null) {
|
||||
android.graphics.RectF(
|
||||
min(pdfRectArray[0], pdfRectArray[2]),
|
||||
|
|
@ -1311,8 +1342,7 @@ internal fun PdfPageComposable(
|
|||
}
|
||||
|
||||
LaunchedEffect(
|
||||
effectiveScale,
|
||||
effectiveOffset,
|
||||
needsTilingNow,
|
||||
actualBitmapWidthPx,
|
||||
actualBitmapHeightPx,
|
||||
canvasWidthPx.floatValue,
|
||||
|
|
@ -1323,8 +1353,7 @@ internal fun PdfPageComposable(
|
|||
virtualPage,
|
||||
isActivePage
|
||||
) {
|
||||
val needsTiling = (effectiveScale > 1f || actualBitmapWidthPx > 3000 || actualBitmapHeightPx > 3000) && (isVerticalScroll || isActivePage)
|
||||
if (!needsTiling) {
|
||||
if (!needsTilingNow) {
|
||||
if (tiles.isNotEmpty()) {
|
||||
val oldTiles = tiles
|
||||
tiles = emptyList()
|
||||
|
|
@ -1358,24 +1387,37 @@ internal fun PdfPageComposable(
|
|||
|
||||
snapshotFlow {
|
||||
val rect = visibleScreenRect()
|
||||
if (rect == null) null
|
||||
else {
|
||||
val observedScale = latestEffectiveScale
|
||||
if (isVerticalScroll && rect != null) {
|
||||
val qTop = rect.top / (tileSizePx / 2)
|
||||
val qLeft = rect.left / (tileSizePx / 2)
|
||||
val qBottom = rect.bottom / (tileSizePx / 2)
|
||||
val qRight = rect.right / (tileSizePx / 2)
|
||||
listOf(qTop, qLeft, qBottom, qRight)
|
||||
listOf(qTop, qLeft, qBottom, qRight, (observedScale * 10f).roundToInt())
|
||||
} else if (!isVerticalScroll) {
|
||||
val observedOffset = latestEffectiveOffset
|
||||
val pivotX = screenWidth / 2f
|
||||
val pivotY = screenHeight / 2f
|
||||
val pxTl = (((0 - observedOffset.x) - pivotX) / observedScale + pivotX) - centeringOffsetX
|
||||
val pyTl = (((0 - observedOffset.y) - pivotY) / observedScale + pivotY) - centeringOffsetY
|
||||
val pxBr = (((screenWidth - observedOffset.x) - pivotX) / observedScale + pivotX) - centeringOffsetX
|
||||
val pyBr = (((screenHeight - observedOffset.y) - pivotY) / observedScale + pivotY) - centeringOffsetY
|
||||
|
||||
val qTop = pyTl.toInt() / (tileSizePx / 2)
|
||||
val qLeft = pxTl.toInt() / (tileSizePx / 2)
|
||||
val qBottom = pyBr.toInt() / (tileSizePx / 2)
|
||||
val qRight = pxBr.toInt() / (tileSizePx / 2)
|
||||
listOf(qTop, qLeft, qBottom, qRight, (observedScale * 10f).roundToInt())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.conflate().collectLatest { _ ->
|
||||
|
||||
delay(150)
|
||||
|
||||
val tileCalcStart = System.nanoTime()
|
||||
if (!isActive) return@collectLatest
|
||||
|
||||
if (isScrolling && effectiveScale > 1f) {
|
||||
return@collectLatest
|
||||
}
|
||||
val renderScale = latestEffectiveScale
|
||||
val renderOffset = latestEffectiveOffset
|
||||
|
||||
val currentVisibleRect = visibleScreenRect()
|
||||
|
||||
|
|
@ -1404,14 +1446,14 @@ internal fun PdfPageComposable(
|
|||
val pivotX = screenWidth / 2f
|
||||
val pivotY = screenHeight / 2f
|
||||
|
||||
pxTl = (((0 - effectiveOffset.x) - pivotX) / effectiveScale + pivotX) - centeringOffsetX
|
||||
pyTl = (((0 - effectiveOffset.y) - pivotY) / effectiveScale + pivotY) - centeringOffsetY
|
||||
pxBr = (((screenWidth - effectiveOffset.x) - pivotX) / effectiveScale + pivotX) - centeringOffsetX
|
||||
pyBr = (((screenHeight - effectiveOffset.y) - pivotY) / effectiveScale + pivotY) - centeringOffsetY
|
||||
pxTl = (((0 - renderOffset.x) - pivotX) / renderScale + pivotX) - centeringOffsetX
|
||||
pyTl = (((0 - renderOffset.y) - pivotY) / renderScale + pivotY) - centeringOffsetY
|
||||
pxBr = (((screenWidth - renderOffset.x) - pivotX) / renderScale + pivotX) - centeringOffsetX
|
||||
pyBr = (((screenHeight - renderOffset.y) - pivotY) / renderScale + pivotY) - centeringOffsetY
|
||||
}
|
||||
|
||||
val visibleBitmapRect = Rect(pxTl.toInt(), pyTl.toInt(), pxBr.toInt(), pyBr.toInt())
|
||||
val inset = if (effectiveScale > 2f) 0 else -tileSizePx
|
||||
val inset = if (renderScale > 2f) 0 else -tileSizePx
|
||||
visibleBitmapRect.inset(inset, inset)
|
||||
|
||||
val requiredTileIds = mutableSetOf<Int>()
|
||||
|
|
@ -1431,8 +1473,10 @@ internal fun PdfPageComposable(
|
|||
|
||||
val currentTileIds = tiles.map { it.tileId }.toSet()
|
||||
|
||||
val scaleTolerance = 0.05f
|
||||
val validCurrentTileIds = tiles.filter { abs(it.renderScale - effectiveScale) <= scaleTolerance }.map { it.tileId }.toSet()
|
||||
val scaleTolerance = PDF_TILE_SCALE_TOLERANCE
|
||||
val validCurrentTileIds = tiles.filter { abs(it.renderScale - renderScale) <= scaleTolerance }.map { it.tileId }.toSet()
|
||||
val tilesToRenderIds = requiredTileIds - validCurrentTileIds
|
||||
val tilesToRecycleIds = currentTileIds - requiredTileIds
|
||||
|
||||
val duration = (System.nanoTime() - tileCalcStart) / 1_000_000f
|
||||
if (duration > 2f) {
|
||||
|
|
@ -1441,21 +1485,26 @@ internal fun PdfPageComposable(
|
|||
)
|
||||
}
|
||||
|
||||
if (requiredTileIds != validCurrentTileIds) {
|
||||
|
||||
val tilesToRenderIds = requiredTileIds - validCurrentTileIds
|
||||
val tilesToRecycleIds = currentTileIds - requiredTileIds
|
||||
|
||||
if (tilesToRecycleIds.isNotEmpty()) {
|
||||
val (tilesToRecycle, tilesToKeep) = tiles.partition { it.tileId in tilesToRecycleIds }
|
||||
tiles = tilesToKeep
|
||||
withContext(Dispatchers.IO) {
|
||||
tilesToRecycle.forEach { PdfBitmapPool.recycle(it.bitmap) }
|
||||
}
|
||||
if (tilesToRecycleIds.isNotEmpty()) {
|
||||
val (tilesToRecycle, tilesToKeep) = tiles.partition { it.tileId in tilesToRecycleIds }
|
||||
tiles = tilesToKeep
|
||||
withContext(Dispatchers.IO) {
|
||||
tilesToRecycle.forEach { PdfBitmapPool.recycle(it.bitmap) }
|
||||
}
|
||||
}
|
||||
|
||||
if (isScrolling && renderScale > 1f) {
|
||||
return@collectLatest
|
||||
}
|
||||
|
||||
if (requiredTileIds != validCurrentTileIds) {
|
||||
if (tilesToRenderIds.isNotEmpty()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
delay(PDF_TILE_IDLE_RENDER_DELAY_MS)
|
||||
if (!isActive) return@collectLatest
|
||||
if (isScrolling && latestEffectiveScale > 1f) return@collectLatest
|
||||
|
||||
val renderedTiles = withContext(Dispatchers.IO) {
|
||||
val newTiles = mutableListOf<PdfTile>()
|
||||
tilesToRenderIds.forEach { tileId ->
|
||||
if (!isActive) return@forEach
|
||||
|
||||
|
|
@ -1469,14 +1518,18 @@ internal fun PdfPageComposable(
|
|||
(col + 1) * tileSizePx,
|
||||
(row + 1) * tileSizePx
|
||||
)
|
||||
val tileRenderSize = (tileSizePx * effectiveScale).toInt().coerceAtLeast(1)
|
||||
val tileRenderScale = min(
|
||||
renderScale,
|
||||
PDF_MAX_TILE_BITMAP_SIZE_PX.toFloat() / tileSizePx.toFloat()
|
||||
)
|
||||
val tileRenderSize = (tileSizePx * tileRenderScale).toInt().coerceAtLeast(1)
|
||||
|
||||
val tileBitmap = PdfBitmapPool.get(tileRenderSize)
|
||||
|
||||
val fullPageRenderWidth = (actualBitmapWidthPx * effectiveScale).toInt()
|
||||
val fullPageRenderHeight = (actualBitmapHeightPx * effectiveScale).toInt()
|
||||
val tileRenderX = (col * tileSizePx * effectiveScale).toInt()
|
||||
val tileRenderY = (row * tileSizePx * effectiveScale).toInt()
|
||||
val fullPageRenderWidth = (actualBitmapWidthPx * tileRenderScale).toInt()
|
||||
val fullPageRenderHeight = (actualBitmapHeightPx * tileRenderScale).toInt()
|
||||
val tileRenderX = (col * tileSizePx * tileRenderScale).toInt()
|
||||
val tileRenderY = (row * tileSizePx * tileRenderScale).toInt()
|
||||
|
||||
page?.renderPageBitmap(
|
||||
bitmap = tileBitmap,
|
||||
|
|
@ -1487,24 +1540,26 @@ internal fun PdfPageComposable(
|
|||
renderAnnot = true
|
||||
)
|
||||
|
||||
val newTile = PdfTile(tileBitmap, tileRect, tileId, effectiveScale)
|
||||
var handedOver = false
|
||||
try {
|
||||
withContext(Dispatchers.Main) {
|
||||
val oldTile = tiles.find { it.tileId == tileId }
|
||||
tiles = tiles.filter { it.tileId != tileId } + newTile
|
||||
handedOver = true
|
||||
newTiles += PdfTile(tileBitmap, tileRect, tileId, renderScale)
|
||||
}
|
||||
newTiles
|
||||
}
|
||||
|
||||
oldTile?.let {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
PdfBitmapPool.recycle(it.bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!handedOver) {
|
||||
PdfBitmapPool.recycle(tileBitmap)
|
||||
}
|
||||
if (!isActive) {
|
||||
withContext(Dispatchers.IO) {
|
||||
renderedTiles.forEach { PdfBitmapPool.recycle(it.bitmap) }
|
||||
}
|
||||
return@collectLatest
|
||||
}
|
||||
|
||||
if (renderedTiles.isNotEmpty()) {
|
||||
val renderedIds = renderedTiles.map { it.tileId }.toSet()
|
||||
val replacedTiles = tiles.filter { it.tileId in renderedIds }
|
||||
tiles = tiles.filterNot { it.tileId in renderedIds } + renderedTiles
|
||||
|
||||
if (replacedTiles.isNotEmpty()) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
replacedTiles.forEach { PdfBitmapPool.recycle(it.bitmap) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2799,7 +2854,7 @@ internal fun PdfPageComposable(
|
|||
|
||||
Timber.tag("PdfLinkDiagnostic").i("Extracted docPtr: $docPtr | pagePtr: $pagePtr")
|
||||
|
||||
val linkInfo = NativePdfiumBridge.getLinkInfoAtPoint(
|
||||
val linkInfo = PdfiumEngineProvider.bridge.getLinkInfoAtPoint(
|
||||
docPtr, pagePtr, pdfCoords.x.toDouble(), pdfCoords.y.toDouble()
|
||||
)
|
||||
|
||||
|
|
@ -2818,7 +2873,7 @@ internal fun PdfPageComposable(
|
|||
}
|
||||
}
|
||||
|
||||
val clickHandled = NativePdfiumBridge.performClick(pagePtr, pdfCoords.x.toDouble(), pdfCoords.y.toDouble())
|
||||
val clickHandled = PdfiumEngineProvider.bridge.performClick(pagePtr, pdfCoords.x.toDouble(), pdfCoords.y.toDouble())
|
||||
if (clickHandled) {
|
||||
return@withContext 2
|
||||
}
|
||||
|
|
@ -3007,25 +3062,21 @@ internal fun PdfPageComposable(
|
|||
awaitEachGesture {
|
||||
@Suppress("UnusedVariable", "Unused") val down =
|
||||
awaitFirstDown(requireUnconsumed = false)
|
||||
paginationPanFlingJob?.cancel()
|
||||
paginationPanFlingJob = null
|
||||
velocityTracker.resetTracking()
|
||||
|
||||
var mode = 0
|
||||
var accumulatedZoom = 1f
|
||||
var accumulatedPan = Offset.Zero
|
||||
var swipeAccumulatorX = 0f
|
||||
var velocityAccumulator = Offset.Zero
|
||||
|
||||
do {
|
||||
val event = awaitPointerEvent()
|
||||
val canceled = event.changes.any { it.isConsumed }
|
||||
val pointerCount = event.changes.size
|
||||
|
||||
val currentCentroid = event.calculateCentroid(useCurrent = true)
|
||||
if (pointerCount > 0 && currentCentroid != Offset.Unspecified) {
|
||||
velocityTracker.addPosition(
|
||||
event.changes[0].uptimeMillis, currentCentroid
|
||||
)
|
||||
}
|
||||
|
||||
if (!canceled) {
|
||||
val rawPanChange = event.calculatePan()
|
||||
val panChange = if (isScrollLocked && pointerCount == 1) {
|
||||
|
|
@ -3077,6 +3128,14 @@ internal fun PdfPageComposable(
|
|||
Timber.tag("PdfZoomDebug").v("Panning: Offset $offset -> $newX, $newY (Max: $maxOffsetX, $maxOffsetY)")
|
||||
offset = Offset(newX, newY)
|
||||
|
||||
if (event.changes.isNotEmpty() && panChange != Offset.Zero) {
|
||||
velocityAccumulator += panChange
|
||||
velocityTracker.addPosition(
|
||||
event.changes[0].uptimeMillis,
|
||||
velocityAccumulator
|
||||
)
|
||||
}
|
||||
|
||||
event.changes.forEach {
|
||||
if (it.positionChanged()) it.consume()
|
||||
}
|
||||
|
|
@ -3189,38 +3248,54 @@ internal fun PdfPageComposable(
|
|||
}
|
||||
}
|
||||
} else if (mode == 1 && scale > 1f) {
|
||||
val velocity = velocityTracker.calculateVelocity()
|
||||
val contentWidth = actualBitmapWidthPx * scale
|
||||
val contentHeight = actualBitmapHeightPx * scale
|
||||
val maxOffsetX = (contentWidth - size.width).coerceAtLeast(0f) / 2f
|
||||
val maxOffsetY = (contentHeight - size.height).coerceAtLeast(0f) / 2f
|
||||
|
||||
val startX = offset.x
|
||||
val startY = offset.y
|
||||
val velocity = velocityTracker.calculateVelocity()
|
||||
val flingX = if (!isScrollLocked && abs(velocity.x) > PDF_PAGINATION_PAN_FLING_MIN_VELOCITY) {
|
||||
velocity.x * PDF_PAGINATION_PAN_FLING_MULTIPLIER
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val flingY = if (abs(velocity.y) > PDF_PAGINATION_PAN_FLING_MIN_VELOCITY) {
|
||||
velocity.y * PDF_PAGINATION_PAN_FLING_MULTIPLIER
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
|
||||
coroutineScope.launch {
|
||||
coroutineScope {
|
||||
launch {
|
||||
if (!isScrollLocked) {
|
||||
Animatable(startX).animateDecay(
|
||||
velocity.x, decay
|
||||
) {
|
||||
val newX = value.coerceIn(
|
||||
-maxOffsetX, maxOffsetX
|
||||
)
|
||||
offset = offset.copy(x = newX)
|
||||
if (flingX == 0f && flingY == 0f) {
|
||||
offset = Offset(
|
||||
x = offset.x.coerceIn(-maxOffsetX, maxOffsetX),
|
||||
y = offset.y.coerceIn(-maxOffsetY, maxOffsetY)
|
||||
)
|
||||
} else {
|
||||
val startOffset = offset
|
||||
paginationPanFlingJob = coroutineScope.launch {
|
||||
try {
|
||||
coroutineScope {
|
||||
launch {
|
||||
if (flingX != 0f) {
|
||||
Animatable(startOffset.x).animateDecay(flingX, decay) {
|
||||
offset = offset.copy(
|
||||
x = value.coerceIn(-maxOffsetX, maxOffsetX)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
if (flingY != 0f) {
|
||||
Animatable(startOffset.y).animateDecay(flingY, decay) {
|
||||
offset = offset.copy(
|
||||
y = value.coerceIn(-maxOffsetY, maxOffsetY)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
Animatable(startY).animateDecay(
|
||||
velocity.y, decay
|
||||
) {
|
||||
val newY = value.coerceIn(
|
||||
-maxOffsetY, maxOffsetY
|
||||
)
|
||||
offset = offset.copy(y = newY)
|
||||
}
|
||||
} finally {
|
||||
paginationPanFlingJob = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3256,7 +3331,7 @@ internal fun PdfPageComposable(
|
|||
}
|
||||
|
||||
val buttons = currentEvent.buttons
|
||||
Timber.tag("StylusEraserDiagnostic").d(
|
||||
Timber.tag("StylusDebug").d(
|
||||
"Page $pageIndex | Type: ${down.type} | isPrimary: ${buttons.isPrimaryPressed} | isSecondary: ${buttons.isSecondaryPressed} | isTertiary: ${buttons.isTertiaryPressed} | buttonsString: $buttons"
|
||||
)
|
||||
|
||||
|
|
@ -3881,7 +3956,10 @@ internal fun PdfPageComposable(
|
|||
stableColorFilter,
|
||||
isDarkMode,
|
||||
excludeImages,
|
||||
stableImageRects
|
||||
stableImageRects,
|
||||
textureBitmap,
|
||||
effectiveTextureAlpha,
|
||||
textureBlendMode
|
||||
) {
|
||||
Timber.tag("PdfDrawPerf").v(
|
||||
"STATIC DATA GENERATED: Scale=$effectiveScale, Tiles=${stableTiles.item.size}"
|
||||
|
|
@ -3899,7 +3977,10 @@ internal fun PdfPageComposable(
|
|||
colorFilter = stableColorFilter,
|
||||
isDarkMode = isDarkMode,
|
||||
excludeImages = excludeImages,
|
||||
imageRects = stableImageRects
|
||||
imageRects = stableImageRects,
|
||||
textureBitmap = StableHolder(textureBitmap),
|
||||
textureAlpha = effectiveTextureAlpha,
|
||||
textureBlendMode = textureBlendMode
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -4263,7 +4344,10 @@ private fun PdfBitmapLayer(
|
|||
colorFilter: ColorFilter? = null,
|
||||
isDarkMode: Boolean = false,
|
||||
excludeImages: Boolean = false,
|
||||
imageRects: List<android.graphics.Rect> = emptyList()
|
||||
imageRects: List<android.graphics.Rect> = emptyList(),
|
||||
textureBitmap: ImageBitmap? = null,
|
||||
textureAlpha: Float = 0f,
|
||||
textureBlendMode: BlendMode = BlendMode.Multiply
|
||||
) {
|
||||
Canvas(modifier = Modifier.fillMaxSize().graphicsLayer()) {
|
||||
translate(left = centeringOffsetX, top = centeringOffsetY) {
|
||||
|
|
@ -4363,6 +4447,15 @@ private fun PdfBitmapLayer(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (textureBitmap != null && textureAlpha > 0f) {
|
||||
drawRect(
|
||||
brush = ShaderBrush(ImageShader(textureBitmap, TileMode.Repeated, TileMode.Repeated)),
|
||||
size = Size(dstW.toFloat(), dstH.toFloat()),
|
||||
blendMode = textureBlendMode,
|
||||
alpha = textureAlpha
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4884,7 +4977,10 @@ private fun PdfPageStaticLayer(data: PageStaticData) {
|
|||
colorFilter = data.colorFilter.item,
|
||||
isDarkMode = data.isDarkMode,
|
||||
excludeImages = data.excludeImages,
|
||||
imageRects = data.imageRects.item
|
||||
imageRects = data.imageRects.item,
|
||||
textureBitmap = data.textureBitmap.item,
|
||||
textureAlpha = data.textureAlpha,
|
||||
textureBlendMode = data.textureBlendMode
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue