fix pdf viewer
This commit is contained in:
+42
-16
@@ -43,6 +43,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.vaultdrop.mobile.R
|
import com.vaultdrop.mobile.R
|
||||||
import com.vaultdrop.mobile.data.local.entity.FileEntity
|
import com.vaultdrop.mobile.data.local.entity.FileEntity
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -307,24 +308,32 @@ private class PdfDocumentState(
|
|||||||
private val contentResolver: ContentResolver,
|
private val contentResolver: ContentResolver,
|
||||||
private val uri: String,
|
private val uri: String,
|
||||||
) {
|
) {
|
||||||
|
@Volatile
|
||||||
private var renderer: PdfRenderer? = null
|
private var renderer: PdfRenderer? = null
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var closed = false
|
||||||
|
|
||||||
private val cache = object : LruCache<PageKey, Bitmap>(CACHE_MAX_KILOBYTES) {
|
private val cache = object : LruCache<PageKey, Bitmap>(CACHE_MAX_KILOBYTES) {
|
||||||
override fun sizeOf(key: PageKey, value: Bitmap): Int = value.byteCount / 1024
|
override fun sizeOf(key: PageKey, value: Bitmap): Int = value.byteCount / 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sérialise toute lecture/écriture du renderer (non thread-safe). */
|
||||||
private val renderMutex = Mutex()
|
private val renderMutex = Mutex()
|
||||||
|
|
||||||
val pageCount: Int get() = renderer?.pageCount ?: 0
|
val pageCount: Int get() = renderer?.pageCount ?: 0
|
||||||
|
|
||||||
/** Ouvre le document si ce n'est pas déjà fait. Échoue si illisible. */
|
/** Ouvre le document si ce n'est pas déjà fait. Échoue si illisible. */
|
||||||
suspend fun load(): Boolean = withContext(Dispatchers.IO) {
|
suspend fun load(): Boolean = withContext(Dispatchers.IO) {
|
||||||
if (renderer != null) return@withContext true
|
renderMutex.withLock {
|
||||||
val opened = runCatching {
|
if (closed) return@withLock false
|
||||||
val pfd: ParcelFileDescriptor? = DocumentContent.openFileDescriptor(contentResolver, uri)
|
if (renderer != null) return@withLock true
|
||||||
if (pfd != null) PdfRenderer(pfd) else null
|
val opened = runCatching {
|
||||||
}.getOrNull()
|
val pfd: ParcelFileDescriptor? = DocumentContent.openFileDescriptor(contentResolver, uri)
|
||||||
if (opened == null) false else {
|
if (pfd != null) PdfRenderer(pfd) else null
|
||||||
renderer = opened
|
}.getOrNull()
|
||||||
true
|
if (opened != null) renderer = opened
|
||||||
|
opened != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +349,7 @@ private class PdfDocumentState(
|
|||||||
): Bitmap? = withContext(Dispatchers.IO) {
|
): Bitmap? = withContext(Dispatchers.IO) {
|
||||||
val key = PageKey(page, targetScale)
|
val key = PageKey(page, targetScale)
|
||||||
cache.get(key) ?: renderMutex.withLock {
|
cache.get(key) ?: renderMutex.withLock {
|
||||||
|
if (closed) return@withLock null
|
||||||
cache.get(key) ?: runCatching { renderPage(page, maxWidthPx, maxHeightPx, targetScale) }
|
cache.get(key) ?: runCatching { renderPage(page, maxWidthPx, maxHeightPx, targetScale) }
|
||||||
.getOrNull()
|
.getOrNull()
|
||||||
?.also { cache.put(key, it) }
|
?.also { cache.put(key, it) }
|
||||||
@@ -352,15 +362,15 @@ private class PdfDocumentState(
|
|||||||
try {
|
try {
|
||||||
// Ajuste le rendu à la résolution cible (passe au strict besoin) :
|
// Ajuste le rendu à la résolution cible (passe au strict besoin) :
|
||||||
// fit dans la zone, grossi de targetScale pour rester net au zoom,
|
// fit dans la zone, grossi de targetScale pour rester net au zoom,
|
||||||
// borné par MAX_SCALE pour ne pas exploser la mémoire des pages
|
// borné par MAX_SCALE et MAX_RENDER_PIXELS pour ne pas exploser la
|
||||||
// vectorielles très grandes.
|
// mémoire des pages vectorielles très grandes.
|
||||||
val baseFit = minOf(
|
val baseFit = minOf(
|
||||||
maxWidthPx.toFloat() / pdfPage.width,
|
maxWidthPx.toFloat() / pdfPage.width,
|
||||||
maxHeightPx.toFloat() / pdfPage.height,
|
maxHeightPx.toFloat() / pdfPage.height,
|
||||||
)
|
)
|
||||||
val scale = minOf(MAX_SCALE, baseFit * targetScale)
|
val scale = minOf(MAX_SCALE, baseFit * targetScale)
|
||||||
val width = (pdfPage.width * scale).toInt().coerceAtLeast(1)
|
val width = (pdfPage.width * scale).toInt().coerceIn(1, MAX_RENDER_PIXELS)
|
||||||
val height = (pdfPage.height * scale).toInt().coerceAtLeast(1)
|
val height = (pdfPage.height * scale).toInt().coerceIn(1, MAX_RENDER_PIXELS)
|
||||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
pdfPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
|
pdfPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
|
||||||
return bitmap
|
return bitmap
|
||||||
@@ -369,11 +379,24 @@ private class PdfDocumentState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fermeture à la sortie du lecteur (dispose). Attend la fin d'un éventuel
|
||||||
|
* rendu en cours via [renderMutex] avant de fermer le renderer : évite
|
||||||
|
* d'utiliser un `PdfPage` après destruction native du document. Les bitmaps
|
||||||
|
* du cache sont simplement relâchées (GC) plutôt que recyclées : elles
|
||||||
|
* peuvent encore être référencées par des `Image` Composées pendant la
|
||||||
|
* sortie, et recycler une bitmap encore dessinée fait planter l'app.
|
||||||
|
*/
|
||||||
fun close() {
|
fun close() {
|
||||||
cache.snapshot().values.forEach { it.recycle() }
|
if (closed) return
|
||||||
cache.evictAll()
|
closed = true
|
||||||
renderer?.close()
|
runBlocking {
|
||||||
renderer = null
|
renderMutex.withLock {
|
||||||
|
cache.evictAll()
|
||||||
|
renderer?.close()
|
||||||
|
renderer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clé de cache : page + résolution de rendu demandée. */
|
/** Clé de cache : page + résolution de rendu demandée. */
|
||||||
@@ -385,6 +408,9 @@ private class PdfDocumentState(
|
|||||||
|
|
||||||
/** Borne du ratio de rendu (résolution écran) appliquée à la page source. */
|
/** Borne du ratio de rendu (résolution écran) appliquée à la page source. */
|
||||||
private const val MAX_SCALE = 3f
|
private const val MAX_SCALE = 3f
|
||||||
|
|
||||||
|
/** Borne d'une dimension de rendu en pixels (pages vectorielles très grandes). */
|
||||||
|
private const val MAX_RENDER_PIXELS = 4096
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user