Skip to content

Commit d82e731

Browse files
committed
Bug fixes
1 parent 36f3411 commit d82e731

5 files changed

Lines changed: 85 additions & 83 deletions

File tree

.idea/deploymentTargetSelector.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/main/kotlin/io/bashpsk/emptylibs/PdfViewerScreen.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ fun PdfViewerScreen() {
3333

3434
var pdfSource by retain { mutableStateOf<PdfSource>(PdfSource.Empty) }
3535

36+
val pdfLazyColumnState = rememberPdfLazyColumnState(source = pdfSource)
37+
3638
val pdfPicker = rememberLauncherForActivityResult(
3739
contract = ActivityResultContracts.GetContent(),
3840
onResult = { resultUri ->
@@ -83,8 +85,8 @@ fun PdfViewerScreen() {
8385
modifier = Modifier
8486
.fillMaxSize()
8587
.padding(paddingValues),
86-
state = rememberPdfLazyColumnState(source = pdfSource),
87-
colorFilter = ImageFilterType.Invert.colorFilter
88+
state = pdfLazyColumnState,
89+
colorFilter = ImageFilterType.Original.colorFilter
8890
)
8991
}
9092
}

pdf-viewer/src/main/kotlin/io/bashpsk/emptylibs/pdfviewer/pdf/PdfLazyColumn.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.compose.runtime.remember
1818
import androidx.compose.ui.Alignment
1919
import androidx.compose.ui.Modifier
2020
import androidx.compose.ui.UiComposable
21+
import androidx.compose.ui.draw.clipToBounds
2122
import androidx.compose.ui.geometry.Offset
2223
import androidx.compose.ui.graphics.Color
2324
import androidx.compose.ui.graphics.ColorFilter
@@ -67,11 +68,13 @@ fun PdfLazyColumn(
6768
}
6869

6970
BoxWithConstraints(
70-
modifier = modifier.transformableGestures(
71-
state = state.transformable,
72-
onClick = onClick,
73-
onLongClick = {}
74-
),
71+
modifier = modifier
72+
.clipToBounds()
73+
.transformableGestures(
74+
state = state.transformable,
75+
onClick = onClick,
76+
onLongClick = {}
77+
),
7578
contentAlignment = Alignment.Center
7679
) {
7780

@@ -119,7 +122,8 @@ fun PdfLazyColumn(
119122
derivedStateOf {
120123
when (visibleItemsCount) {
121124

122-
0, 1 -> "${index + 1}/$pageCount"
125+
0 -> "${index}/$pageCount"
126+
1 -> "${index + 1}/$pageCount"
123127
else -> "${index + 1}-${index + visibleItemsCount}/$pageCount"
124128
}
125129
}

pdf-viewer/src/main/kotlin/io/bashpsk/emptylibs/pdfviewer/pdf/PdfLazyColumnState.kt

Lines changed: 61 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import androidx.compose.runtime.Stable
1212
import androidx.compose.runtime.getValue
1313
import androidx.compose.runtime.mutableIntStateOf
1414
import androidx.compose.runtime.mutableStateOf
15-
import androidx.compose.runtime.rememberCoroutineScope
1615
import androidx.compose.runtime.retain.retain
1716
import androidx.compose.runtime.setValue
1817
import androidx.compose.ui.graphics.Color
@@ -26,11 +25,13 @@ import io.bashpsk.emptylibs.formatter.resolution.ResolutionType
2625
import io.bashpsk.emptylibs.gestureui.transform.TransformableGesturesState
2726
import io.bashpsk.emptylibs.gestureui.transform.rememberTransformableGesturesState
2827
import io.bashpsk.emptylibs.lrucachemanager.manager.EmptyCacheManager
28+
import io.bashpsk.emptylibs.pdfviewer.utils.LOG_TAG
2929
import kotlinx.collections.immutable.persistentMapOf
3030
import kotlinx.collections.immutable.toPersistentMap
3131
import kotlinx.coroutines.CoroutineScope
3232
import kotlinx.coroutines.Dispatchers
3333
import kotlinx.coroutines.Job
34+
import kotlinx.coroutines.SupervisorJob
3435
import kotlinx.coroutines.async
3536
import kotlinx.coroutines.currentCoroutineContext
3637
import 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
}

pdf-viewer/src/main/kotlin/io/bashpsk/emptylibs/pdfviewer/pdf/PdfPageView.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ internal fun PdfPageView(
5252
state: PdfLazyColumnState,
5353
pageData: PdfPageData = PdfPageData(),
5454
isScrolling: Boolean = false,
55-
placeholder: Color = MaterialTheme.colorScheme.surface,
55+
placeholder: Color = MaterialTheme.colorScheme.surfaceVariant,
5656
colorFilter: ColorFilter? = null
5757
) {
5858

@@ -100,7 +100,7 @@ internal fun PdfPageView(
100100
snapshotFlow {
101101

102102
state.transformable.zoom
103-
}.debounce(200.milliseconds).distinctUntilChanged().collectLatest {
103+
}.distinctUntilChanged().debounce(200.milliseconds).collectLatest {
104104

105105
if (isScrolling.not() && isImageZoomed) state.coroutineScope.launch(Dispatchers.IO) {
106106

0 commit comments

Comments
 (0)