@@ -12,7 +12,6 @@ import androidx.compose.runtime.Stable
1212import androidx.compose.runtime.getValue
1313import androidx.compose.runtime.mutableIntStateOf
1414import androidx.compose.runtime.mutableStateOf
15- import androidx.compose.runtime.rememberCoroutineScope
1615import androidx.compose.runtime.retain.retain
1716import androidx.compose.runtime.setValue
1817import androidx.compose.ui.graphics.Color
@@ -26,11 +25,13 @@ import io.bashpsk.emptylibs.formatter.resolution.ResolutionType
2625import io.bashpsk.emptylibs.gestureui.transform.TransformableGesturesState
2726import io.bashpsk.emptylibs.gestureui.transform.rememberTransformableGesturesState
2827import io.bashpsk.emptylibs.lrucachemanager.manager.EmptyCacheManager
28+ import io.bashpsk.emptylibs.pdfviewer.utils.LOG_TAG
2929import kotlinx.collections.immutable.persistentMapOf
3030import kotlinx.collections.immutable.toPersistentMap
3131import kotlinx.coroutines.CoroutineScope
3232import kotlinx.coroutines.Dispatchers
3333import kotlinx.coroutines.Job
34+ import kotlinx.coroutines.SupervisorJob
3435import kotlinx.coroutines.async
3536import kotlinx.coroutines.currentCoroutineContext
3637import kotlinx.coroutines.ensureActive
@@ -58,11 +59,10 @@ fun rememberPdfLazyColumnState(
5859 initialZoom : Float = 1.0F,
5960 enableZoom : Boolean = true,
6061 enableDoubleTapZoom : Boolean = true,
61- zoomRange : ClosedFloatingPointRange <Float > = 0.5F..6 .0F
62+ zoomRange : ClosedFloatingPointRange <Float > = 0.5F..4 .0F
6263): PdfLazyColumnState {
6364
6465 val context = LocalContext .current
65- val coroutineScope = rememberCoroutineScope()
6666
6767 val transformableState = rememberTransformableGesturesState(
6868 initialZoom = initialZoom,
@@ -74,26 +74,19 @@ fun rememberPdfLazyColumnState(
7474 )
7575
7676 val state = retain(transformableState) {
77- PdfLazyColumnState (
78- context = context,
79- coroutineScope = coroutineScope,
80- transformable = transformableState
81- )
82- }
83-
84- LaunchedEffect (source) {
85-
86- state.setLoadPdfSource(source = source)
77+ PdfLazyColumnState (transformable = transformableState)
8778 }
8879
8980 LaunchedEffect (cacheSize) {
9081
9182 state.scaledBitmapManager.resize(maxSize = cacheSize)
9283 }
9384
94- DisposableEffect (Unit ) {
85+ DisposableEffect (context, source) {
86+
87+ state.setLoadPdfSource(context = context, source = source)
9588
96- onDispose { /* state.close()*/ }
89+ onDispose { state.close() }
9790 }
9891
9992 return state
@@ -102,22 +95,21 @@ fun rememberPdfLazyColumnState(
10295/* *
10396 * A state object that can be hoisted to control and observe scrolling and zooming of a PDF.
10497 *
105- * @param context The application context.
106- * @param coroutineScope A coroutine scope for managing background tasks.
10798 * @param transformable The state for transformable gestures.
10899 */
109100@Stable
110- class PdfLazyColumnState (
111- private val context : Context ,
112- internal val coroutineScope : CoroutineScope ,
113- internal val transformable : TransformableGesturesState
114- ) {
101+ class PdfLazyColumnState (internal val transformable : TransformableGesturesState ) {
115102
116103 /* *
117104 * A mutex to ensure thread-safe access to PDF rendering operations.
118105 */
119106 private val mutex = Mutex ()
120107
108+ /* *
109+ * A coroutine scope for managing background tasks.
110+ */
111+ internal val coroutineScope = CoroutineScope (context = SupervisorJob () + Dispatchers .Default )
112+
121113 /* *
122114 * The file descriptor of the PDF file.
123115 */
@@ -161,10 +153,11 @@ class PdfLazyColumnState(
161153 * coroutine to load the PDF from the given source. The PDF is loaded in the IO dispatcher.
162154 * After loading, the [pageDataList] is populated with the data of each page.
163155 *
156+ * @param context The application context.
164157 * @param source The [PdfSource] to load.
165158 * It can be a [PdfSource.URI], [PdfSource.Path], or [PdfSource.Empty].
166159 */
167- internal fun setLoadPdfSource (source : PdfSource ) {
160+ internal fun setLoadPdfSource (context : Context , source : PdfSource ) {
168161
169162 fileLoadJob?.cancel()
170163 close()
@@ -197,6 +190,8 @@ class PdfLazyColumnState(
197190
198191 pageDataList = (0 until renderer.pageCount).associate { pageIndex ->
199192
193+ currentCoroutineContext().ensureActive()
194+
200195 renderer.openPage(pageIndex).use { page ->
201196
202197 page.index to PdfPageData (
@@ -209,7 +204,7 @@ class PdfLazyColumnState(
209204 } catch (exception: Exception ) {
210205
211206 currentCoroutineContext().ensureActive()
212- Log .e(" PDF-VIEWER " , exception.message, exception)
207+ Log .e(LOG_TAG , exception.message, exception)
213208 }
214209 }
215210 }
@@ -220,27 +215,24 @@ class PdfLazyColumnState(
220215 *
221216 * @param pageIndex The index of the page to render.
222217 */
223- internal fun setRenderNormalBitmap (pageIndex : Int ) {
218+ internal fun setRenderNormalBitmap (pageIndex : Int ) = coroutineScope.launch( Dispatchers . IO ) {
224219
225- coroutineScope.launch(context = Dispatchers .IO ) {
220+ val renderer = pdfRenderer ? : return @launch
221+ val pageData = pageDataList[pageIndex] ? : return @launch
226222
227- val renderer = pdfRenderer ? : return @launch
228- val pageData = pageDataList[pageIndex] ? : return @launch
223+ val targetWidth = containerWidth * transformable.initialZoom.toInt()
224+ val targetHeight = ((targetWidth.toFloat() / pageData.width) * pageData.height).toInt()
229225
230- val targetWidth = containerWidth * transformable.initialZoom.toInt()
231- val targetHeight = ((targetWidth.toFloat() / pageData.width) * pageData.height).toInt()
232-
233- ((targetWidth > 0 || targetHeight > 0 ) && pageData.bitmap == null ).takeIf { it }?.run {
226+ ((targetWidth > 0 || targetHeight > 0 ) && pageData.bitmap == null ).takeIf { it }?.run {
234227
235- getRenderBitmap(
236- renderer = renderer,
237- pageIndex = pageIndex,
238- targetWidth = targetWidth,
239- targetHeight = targetHeight
240- )?.let { bitmap ->
228+ getRenderBitmap(
229+ renderer = renderer,
230+ pageIndex = pageIndex,
231+ targetWidth = targetWidth,
232+ targetHeight = targetHeight
233+ )?.let { bitmap ->
241234
242- pageDataList = pageDataList.put(pageIndex, pageData.copy(bitmap = bitmap))
243- }
235+ pageDataList = pageDataList.put(pageIndex, pageData.copy(bitmap = bitmap))
244236 }
245237 }
246238 }
@@ -254,42 +246,40 @@ class PdfLazyColumnState(
254246 * @param pageIndex The index of the page.
255247 * @return The high-quality bitmap, or null if it's not available.
256248 */
257- internal suspend fun getScaledImageBitmap (pageIndex : Int ): ImageBitmap ? {
249+ internal suspend fun getScaledImageBitmap (
250+ pageIndex : Int
251+ ): ImageBitmap ? = coroutineScope.async(context = Dispatchers .IO ) {
258252
259- return coroutineScope.async(context = Dispatchers . IO ) {
253+ if (hasNeedHighQualityBitmap(pageData = getQualityPageData(pageIndex = pageIndex)) ) {
260254
261- if (hasNeedHighQualityBitmap(pageData = getQualityPageData(pageIndex = pageIndex))) {
255+ val renderer = pdfRenderer ? : return @async null
256+ val pageData = pageDataList[pageIndex] ? : return @async null
262257
263- val renderer = pdfRenderer ? : return @async null
264- val pageData = pageDataList[pageIndex] ? : return @async null
258+ val quality = findContentQuality()
259+ val targetWidth = (containerWidth * quality).toInt().coerceAtMost(
260+ ResolutionType ._4K_UHD .width
261+ )
262+ val targetHeight = ((targetWidth.toFloat() / pageData.width) * pageData.height).toInt()
265263
266- val quality = findContentQuality()
267- val targetWidth = (containerWidth * quality).toInt().coerceAtMost(
268- ResolutionType ._4K_UHD .width
264+ getRenderBitmap(
265+ renderer = renderer,
266+ pageIndex = pageIndex,
267+ targetWidth = targetWidth,
268+ targetHeight = targetHeight
269+ )?.let { bitmap ->
270+
271+ val newPageData = PdfQualityPageData (
272+ page = pageIndex,
273+ quality = quality,
274+ bitmap = bitmap
269275 )
270- val targetHeight = ((targetWidth.toFloat() / pageData.width) * pageData.height)
271- .toInt()
272-
273- getRenderBitmap(
274- renderer = renderer,
275- pageIndex = pageIndex,
276- targetWidth = targetWidth,
277- targetHeight = targetHeight
278- )?.let { bitmap ->
279-
280- val newPageData = PdfQualityPageData (
281- page = pageIndex,
282- quality = quality,
283- bitmap = bitmap
284- )
285-
286- scaledBitmapManager.add(newPageData.page.toString(), newPageData)
287- }
276+
277+ scaledBitmapManager.add(newPageData.page.toString(), newPageData)
288278 }
279+ }
289280
290- getQualityPageData(pageIndex = pageIndex)?.bitmap
291- }.await()
292- }
281+ getQualityPageData(pageIndex = pageIndex)?.bitmap
282+ }.await()
293283
294284 /* *
295285 * Checks if the PDF content is currently zoomed in beyond the initial zoom level.
@@ -354,7 +344,7 @@ class PdfLazyColumnState(
354344
355345 return @withContext mutex.withLock {
356346
357- renderer.openPage(pageIndex).use { currentPage ->
347+ renderer.openPage(pageIndex).use { pdfPage ->
358348
359349 if (targetWidth <= 0 || targetHeight <= 0 ) return @use null
360350
@@ -365,7 +355,7 @@ class PdfLazyColumnState(
365355 drawColor(Color .White .toArgb())
366356 }
367357
368- currentPage .render(newBitmap, null , null , PdfRenderer .Page .RENDER_MODE_FOR_DISPLAY )
358+ pdfPage .render(newBitmap, null , null , PdfRenderer .Page .RENDER_MODE_FOR_DISPLAY )
369359 newBitmap.asImageBitmap()
370360 }
371361 }
@@ -376,13 +366,11 @@ class PdfLazyColumnState(
376366 */
377367 internal fun close () {
378368
379- transformable.resetAllValues()
380369 fileLoadJob?.cancel()
381370 pdfRenderer?.close()
382371 fileDescriptor?.close()
383372 pdfRenderer = null
384373 fileDescriptor = null
385- pageDataList = persistentMapOf()
386374 scaledBitmapManager.evictAll()
387375 }
388376}
0 commit comments