Pdf reader improvments (#113)
* Improve PDF rendering quality and performance. - Set `FilterQuality.High` when drawing base bitmaps and high-res tiles in `PdfPageComposable`. - Implement `inSampleSize` calculation in `UniversalDocument` to optimize memory usage during region decoding. - Enhance bitmap drawing quality by adding `ANTI_ALIAS_FLAG` and `DITHER_FLAG` to the paint configuration. * Implement custom reader themes for PDF viewer. This change integrates the `ReaderThemePanel` and theme management logic into the PDF viewer, allowing users to apply preset or custom themes. Previously, the PDF viewer only supported a simple dark mode toggle. Specific changes: - Moved `ReaderTheme`, `ReaderTexture`, and theme-related utility functions from `EpubReaderScreen.kt` to `Common.kt` to allow sharing between EPUB and PDF readers. - Updated `PdfPageComposable` to use `activeTheme` instead of `isDarkMode`, implementing a `ColorMatrix` to apply custom background and text colors to PDF pages. - Replaced the dark mode toggle in `PdfViewerScreen` with a theme selection button that opens `ReaderThemePanel`. - Introduced `PdfBuiltInThemes` to provide PDF-specific theme presets. * Reset page transformation on constraint changes and refine tile rendering logic. - Reset scale to 1.0 and offset to zero in `PdfPageComposable` when constraints change. - Update `UniversalDocument` to use floating-point precision for source rectangle calculations and `RectF` for destination mapping to improve rendering accuracy. * Improve eraser hit detection and visual thickness settings in PDF reader.
This commit is contained in:
parent
4db2c97a30
commit
60dacf9c12
9 changed files with 834 additions and 741 deletions
|
|
@ -407,7 +407,7 @@ internal fun PdfPageComposable(
|
|||
clearSelectionTrigger: Long = 0L,
|
||||
onTtsHighlightCenterCalculated: ((Float) -> Unit)? = null,
|
||||
onSearchHighlightCenterCalculated: ((Float) -> Unit)? = null,
|
||||
isDarkMode: Boolean = false,
|
||||
activeTheme: com.aryan.reader.ReaderTheme = com.aryan.reader.ReaderTheme("no_theme", "No Theme", Color.Unspecified, Color.Unspecified, false),
|
||||
onDoubleTap: ((Offset) -> Unit)? = null,
|
||||
isEditMode: Boolean = false,
|
||||
drawingState: PdfDrawingState? = null,
|
||||
|
|
@ -534,41 +534,55 @@ internal fun PdfPageComposable(
|
|||
val canvasWidthPx = remember { mutableFloatStateOf(0f) }
|
||||
val canvasHeightPx = remember { mutableFloatStateOf(0f) }
|
||||
|
||||
val colorFilter = remember(isDarkMode) {
|
||||
if (isDarkMode) {
|
||||
val colorMatrix = floatArrayOf(
|
||||
-1f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
255f,
|
||||
0f,
|
||||
-1f,
|
||||
0f,
|
||||
0f,
|
||||
255f,
|
||||
0f,
|
||||
0f,
|
||||
-1f,
|
||||
0f,
|
||||
255f,
|
||||
0f,
|
||||
0f,
|
||||
0f,
|
||||
1f,
|
||||
0f
|
||||
)
|
||||
ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
|
||||
} else {
|
||||
null
|
||||
val isDarkMode = activeTheme.isDark || activeTheme.id == "reverse"
|
||||
|
||||
val colorFilter = remember(activeTheme) {
|
||||
when (activeTheme.id) {
|
||||
"no_theme", "system" -> null
|
||||
"reverse" -> {
|
||||
val colorMatrix = floatArrayOf(
|
||||
-1f, 0f, 0f, 0f, 255f,
|
||||
0f, -1f, 0f, 0f, 255f,
|
||||
0f, 0f, -1f, 0f, 255f,
|
||||
0f, 0f, 0f, 1f, 0f
|
||||
)
|
||||
ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
|
||||
}
|
||||
else -> {
|
||||
val bgR = activeTheme.backgroundColor.red * 255f
|
||||
val bgG = activeTheme.backgroundColor.green * 255f
|
||||
val bgB = activeTheme.backgroundColor.blue * 255f
|
||||
|
||||
val fgR = activeTheme.textColor.red * 255f
|
||||
val fgG = activeTheme.textColor.green * 255f
|
||||
val fgB = activeTheme.textColor.blue * 255f
|
||||
|
||||
val dr = (bgR - fgR) / 255f
|
||||
val dg = (bgG - fgG) / 255f
|
||||
val db = (bgB - fgB) / 255f
|
||||
|
||||
val lumR = 0.2126f
|
||||
val lumG = 0.7152f
|
||||
val lumB = 0.0722f
|
||||
|
||||
val colorMatrix = floatArrayOf(
|
||||
dr * lumR, dr * lumG, dr * lumB, 0f, fgR,
|
||||
dg * lumR, dg * lumG, dg * lumB, 0f, fgG,
|
||||
db * lumR, db * lumG, db * lumB, 0f, fgB,
|
||||
0f, 0f, 0f, 1f, 0f
|
||||
)
|
||||
ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val backgroundColor = remember(isDarkMode, isVerticalScroll) {
|
||||
if (isDarkMode) {
|
||||
Color(0xFF2A2A2A)
|
||||
} else {
|
||||
val backgroundColor = remember(activeTheme, isVerticalScroll) {
|
||||
if (activeTheme.id == "no_theme" || activeTheme.id == "system") {
|
||||
if (isVerticalScroll) Color.White else Color.Black
|
||||
} else if (activeTheme.id == "reverse") {
|
||||
if (isVerticalScroll) Color.Black else Color.White
|
||||
} else {
|
||||
activeTheme.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1628,10 +1642,6 @@ internal fun PdfPageComposable(
|
|||
}
|
||||
}
|
||||
|
||||
if (selectedTool == InkType.ERASER) {
|
||||
eraserPosition = down.position
|
||||
}
|
||||
|
||||
if (dragStartedOnHandle) {
|
||||
Timber.d(
|
||||
"PointerInput: Drag started on handle $activeDraggingHandle"
|
||||
|
|
@ -3091,8 +3101,12 @@ internal fun PdfPageComposable(
|
|||
}
|
||||
|
||||
LaunchedEffect(
|
||||
this@BoxWithConstraints.maxWidth, this@BoxWithConstraints.maxHeight, pageIndex
|
||||
this@BoxWithConstraints.maxWidth, this@BoxWithConstraints.maxHeight
|
||||
) {
|
||||
scale = 1f
|
||||
offset = Offset.Zero
|
||||
onScaleChanged(1f)
|
||||
|
||||
Timber.d(
|
||||
"PdfPageComposable Page $pageIndex | Constraints: maxWidth=${this@BoxWithConstraints.maxWidth}, maxHeight=${this@BoxWithConstraints.maxHeight}"
|
||||
)
|
||||
|
|
@ -3861,7 +3875,6 @@ private fun PdfBitmapLayer(
|
|||
.fillMaxSize()
|
||||
.graphicsLayer()) {
|
||||
translate(left = centeringOffsetX, top = centeringOffsetY) {
|
||||
// THIS is the fix: Hard clip to the target bounds so edge tiles can't bleed out.
|
||||
clipRect(left = 0f, top = 0f, right = targetWidth.toFloat(), bottom = targetHeight.toFloat()) {
|
||||
if (bitmapState != null && !bitmapState.isRecycled) {
|
||||
val dstW = if (targetWidth > 0) targetWidth else bitmapState.width
|
||||
|
|
@ -3869,17 +3882,16 @@ private fun PdfBitmapLayer(
|
|||
val srcSize = IntSize(bitmapState.width, bitmapState.height)
|
||||
val dstSize = IntSize(dstW, dstH)
|
||||
|
||||
// 1. Draw Base Bitmap
|
||||
drawImage(
|
||||
image = bitmapState.asImageBitmap(),
|
||||
srcOffset = IntOffset.Zero,
|
||||
srcSize = srcSize,
|
||||
dstOffset = IntOffset.Zero,
|
||||
dstSize = dstSize,
|
||||
colorFilter = colorFilter
|
||||
colorFilter = colorFilter,
|
||||
filterQuality = androidx.compose.ui.graphics.FilterQuality.High
|
||||
)
|
||||
|
||||
// 2. Draw High-Res Tiles
|
||||
if (effectiveScale > 1f) {
|
||||
tiles.forEach { tile ->
|
||||
if (!tile.bitmap.isRecycled) {
|
||||
|
|
@ -3891,7 +3903,8 @@ private fun PdfBitmapLayer(
|
|||
dstSize = IntSize(
|
||||
tile.renderRect.width(), tile.renderRect.height()
|
||||
),
|
||||
colorFilter = colorFilter
|
||||
colorFilter = colorFilter,
|
||||
filterQuality = androidx.compose.ui.graphics.FilterQuality.High
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ import com.aryan.reader.SearchResult
|
|||
import com.aryan.reader.pdf.data.PdfAnnotation
|
||||
import com.aryan.reader.pdf.data.PdfTextBox
|
||||
import com.aryan.reader.pdf.data.VirtualPage
|
||||
import io.legere.pdfiumandroid.suspend.PdfDocumentKt
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -177,7 +176,7 @@ private data class DividerLayout(val y: Float, val width: Float, val height: Flo
|
|||
internal fun PdfVerticalReader(
|
||||
state: VerticalPdfReaderState,
|
||||
pdfDocument: StableHolder<ReaderDocument>,
|
||||
isDarkMode: Boolean,
|
||||
activeTheme: com.aryan.reader.ReaderTheme,
|
||||
totalPages: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
virtualPages: List<VirtualPage> = emptyList(),
|
||||
|
|
@ -243,6 +242,7 @@ internal fun PdfVerticalReader(
|
|||
}
|
||||
}
|
||||
var globalEraserPosition by remember { mutableStateOf<Offset?>(null) }
|
||||
val isDarkMode = activeTheme.isDark || activeTheme.id == "reverse"
|
||||
BoxWithConstraints(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.TopStart) {
|
||||
val imeInsets = WindowInsets.ime
|
||||
val density = LocalDensity.current
|
||||
|
|
@ -1497,7 +1497,7 @@ internal fun PdfVerticalReader(
|
|||
pageIndex = page.index,
|
||||
virtualPage = virtualPage,
|
||||
totalPages = totalPages,
|
||||
isDarkMode = isDarkMode,
|
||||
activeTheme = activeTheme,
|
||||
externalScale = highResScale,
|
||||
onScaleChanged = {},
|
||||
showAllTextHighlights = showAllTextHighlights,
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@ import com.aryan.reader.DeviceVoiceSettingsSheet
|
|||
import com.aryan.reader.FileType
|
||||
import com.aryan.reader.MainViewModel
|
||||
import com.aryan.reader.R
|
||||
import com.aryan.reader.ReaderThemePanel
|
||||
import com.aryan.reader.SearchResult
|
||||
import com.aryan.reader.SearchTopBar
|
||||
import com.aryan.reader.SummarizationPopup
|
||||
|
|
@ -270,6 +271,7 @@ import com.aryan.reader.epubreader.AutoScrollControls
|
|||
import com.aryan.reader.epubreader.DictionarySettingsDialog
|
||||
import com.aryan.reader.epubreader.ExternalDictionaryHelper
|
||||
import com.aryan.reader.fetchAiDefinition
|
||||
import com.aryan.reader.loadCustomThemes
|
||||
import com.aryan.reader.paginatedreader.TtsChunk
|
||||
import com.aryan.reader.pdf.data.AnnotationSettingsRepository
|
||||
import com.aryan.reader.pdf.data.PdfAnnotation
|
||||
|
|
@ -281,6 +283,7 @@ import com.aryan.reader.pdf.data.SmartSearchResult
|
|||
import com.aryan.reader.pdf.data.TextStyleConfig
|
||||
import com.aryan.reader.pdf.data.VirtualPage
|
||||
import com.aryan.reader.rememberSearchState
|
||||
import com.aryan.reader.saveCustomThemes
|
||||
import com.aryan.reader.summarizationUrl
|
||||
import com.aryan.reader.tts.SpeakerSamplePlayer
|
||||
import com.aryan.reader.tts.TtsPlaybackManager
|
||||
|
|
@ -343,6 +346,27 @@ private const val PREF_USE_ONLINE_DICT = "use_online_dictionary"
|
|||
private const val PREF_EXTERNAL_DICT_PKG = "external_dictionary_package"
|
||||
private const val PREF_EXTERNAL_TRANSLATE_PKG = "external_translate_package"
|
||||
private const val PREF_EXTERNAL_SEARCH_PKG = "external_search_package"
|
||||
private const val PDF_THEME_KEY = "pdf_reader_theme"
|
||||
|
||||
private fun savePdfThemeId(context: Context, themeId: String) {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit { putString(PDF_THEME_KEY, themeId) }
|
||||
}
|
||||
|
||||
private fun loadPdfThemeId(context: Context): String {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
return prefs.getString(PDF_THEME_KEY, "no_theme") ?: "no_theme"
|
||||
}
|
||||
|
||||
val PdfBuiltInThemes = listOf(
|
||||
com.aryan.reader.ReaderTheme("no_theme", "No Theme", Color.Unspecified, Color.Unspecified, false),
|
||||
com.aryan.reader.ReaderTheme("reverse", "Reverse", Color.Black, Color.White, true),
|
||||
com.aryan.reader.ReaderTheme("light", "Light", Color(0xFFFFFFFF), Color(0xFF000000), false),
|
||||
com.aryan.reader.ReaderTheme("dark", "Dark", Color(0xFF121212), Color(0xFFE0E0E0), true),
|
||||
com.aryan.reader.ReaderTheme("sepia", "Sepia", Color(0xFFFBF0D9), Color(0xFF5F4B32), false),
|
||||
com.aryan.reader.ReaderTheme("slate", "Slate", Color(0xFF2E3440), Color(0xFFECEFF4), true),
|
||||
com.aryan.reader.ReaderTheme("oled", "OLED", Color(0xFF000000), Color(0xFFB0B0B0), true)
|
||||
)
|
||||
|
||||
object PdfiumCoreProvider {
|
||||
val core: PdfiumCoreKt by lazy {
|
||||
|
|
@ -1065,7 +1089,16 @@ fun PdfViewerScreen(
|
|||
val focusManager = LocalFocusManager.current
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
var displayMode by remember { mutableStateOf(loadDisplayMode(context)) }
|
||||
var isPdfDarkMode by remember { mutableStateOf(loadPdfDarkMode(context)) }
|
||||
var showThemePanel by remember { mutableStateOf(false) }
|
||||
var currentThemeId by remember { mutableStateOf(loadPdfThemeId(context)) }
|
||||
var customThemes by remember { mutableStateOf(loadCustomThemes(context)) }
|
||||
|
||||
val activeTheme = remember(currentThemeId, customThemes) {
|
||||
PdfBuiltInThemes.find { it.id == currentThemeId }
|
||||
?: customThemes.find { it.id == currentThemeId }
|
||||
?: PdfBuiltInThemes[0]
|
||||
}
|
||||
val isPdfDarkMode = activeTheme.isDark || activeTheme.id == "reverse"
|
||||
var pageAspectRatios by remember { mutableStateOf<List<Float>>(emptyList()) }
|
||||
var showBars by rememberSaveable { mutableStateOf(true) }
|
||||
var isFullScreen by remember { mutableStateOf(false) }
|
||||
|
|
@ -1364,7 +1397,6 @@ fun PdfViewerScreen(
|
|||
LaunchedEffect(ocrLanguage) { OcrHelper.init(ocrLanguage) }
|
||||
|
||||
LaunchedEffect(displayMode) { saveDisplayMode(context, displayMode) }
|
||||
LaunchedEffect(isPdfDarkMode) { savePdfDarkMode(context, isPdfDarkMode) }
|
||||
|
||||
val annotationSettingsRepo = remember(context) { AnnotationSettingsRepository(context) }
|
||||
val toolSettings by annotationSettingsRepo.settings.collectAsState()
|
||||
|
|
@ -2724,21 +2756,24 @@ fun PdfViewerScreen(
|
|||
): Boolean {
|
||||
if (annotation.points.isEmpty()) return false
|
||||
|
||||
val effectiveThreshold = threshold + (annotation.strokeWidth / 2f)
|
||||
val thresholdSq = effectiveThreshold * effectiveThreshold
|
||||
|
||||
if (annotation.points.size == 1) {
|
||||
val p = annotation.points[0]
|
||||
val dx = (p.x - hitPoint.x) * pageAspectRatio
|
||||
val dy = (p.y - hitPoint.y)
|
||||
return (dx * dx + dy * dy) < (threshold * threshold)
|
||||
val dx = (p.x - hitPoint.x)
|
||||
val dy = (p.y - hitPoint.y) / pageAspectRatio
|
||||
return (dx * dx + dy * dy) < thresholdSq
|
||||
}
|
||||
|
||||
for (i in 0 until annotation.points.size - 1) {
|
||||
val a = annotation.points[i]
|
||||
val b = annotation.points[i + 1]
|
||||
|
||||
val pax = (hitPoint.x - a.x) * pageAspectRatio
|
||||
val pay = (hitPoint.y - a.y)
|
||||
val bax = (b.x - a.x) * pageAspectRatio
|
||||
val bay = (b.y - a.y)
|
||||
val pax = (hitPoint.x - a.x)
|
||||
val pay = (hitPoint.y - a.y) / pageAspectRatio
|
||||
val bax = (b.x - a.x)
|
||||
val bay = (b.y - a.y) / pageAspectRatio
|
||||
|
||||
val segmentLenSq = (bax * bax + bay * bay).coerceAtLeast(1e-6f)
|
||||
val t = (pax * bax + pay * bay) / segmentLenSq
|
||||
|
|
@ -2749,7 +2784,7 @@ fun PdfViewerScreen(
|
|||
|
||||
val distSq = (pax - closestX) * (pax - closestX) + (pay - closestY) * (pay - closestY)
|
||||
|
||||
if (distSq < (threshold * threshold)) return true
|
||||
if (distSq < thresholdSq) return true
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
@ -3580,6 +3615,7 @@ fun PdfViewerScreen(
|
|||
}
|
||||
|
||||
showTtsSettingsSheet -> showTtsSettingsSheet = false
|
||||
showThemePanel -> showThemePanel = false
|
||||
|
||||
else -> {
|
||||
saveStateAndExit()
|
||||
|
|
@ -4257,7 +4293,7 @@ fun PdfViewerScreen(
|
|||
pageIndex = pageIndex,
|
||||
virtualPage = virtualPage,
|
||||
totalPages = totalDisplayPages,
|
||||
isDarkMode = isPdfDarkMode,
|
||||
activeTheme = activeTheme,
|
||||
isScrollLocked = isScrollLocked,
|
||||
onScaleChanged = { newScale ->
|
||||
if (pagerState.currentPage == pageIndex) {
|
||||
|
|
@ -4636,7 +4672,7 @@ fun PdfViewerScreen(
|
|||
PdfVerticalReader(
|
||||
state = verticalReaderState,
|
||||
pdfDocument = docHolder,
|
||||
isDarkMode = isPdfDarkMode,
|
||||
activeTheme = activeTheme,
|
||||
isScrollLocked = isScrollLocked,
|
||||
totalPages = totalDisplayPages,
|
||||
pageAspectRatios = ratiosHolder,
|
||||
|
|
@ -5223,22 +5259,14 @@ fun PdfViewerScreen(
|
|||
)
|
||||
|
||||
TooltipIconButton(
|
||||
text = if (isPdfDarkMode)
|
||||
stringResource(R.string.tooltip_dark_mode_off)
|
||||
else
|
||||
stringResource(R.string.tooltip_dark_mode_on),
|
||||
description = if (isPdfDarkMode)
|
||||
stringResource(R.string.tooltip_dark_mode_off_desc)
|
||||
else
|
||||
stringResource(R.string.tooltip_dark_mode_on_desc),
|
||||
onClick = { isPdfDarkMode = !isPdfDarkMode }
|
||||
text = "Theme",
|
||||
description = "Theme Settings",
|
||||
onClick = { showThemePanel = true }
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.dark_mode),
|
||||
contentDescription = if (isPdfDarkMode) "Disable Dark Mode"
|
||||
else "Enable Dark Mode",
|
||||
tint = if (isPdfDarkMode) MaterialTheme.colorScheme.primary
|
||||
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||
painter = painterResource(id = R.drawable.palette),
|
||||
contentDescription = "Theme Settings",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -6926,6 +6954,25 @@ fun PdfViewerScreen(
|
|||
)
|
||||
}
|
||||
|
||||
if (showThemePanel) {
|
||||
ReaderThemePanel(
|
||||
isVisible = true,
|
||||
currentThemeId = currentThemeId,
|
||||
builtInThemes = PdfBuiltInThemes,
|
||||
onThemeSelected = {
|
||||
currentThemeId = it
|
||||
savePdfThemeId(context, it)
|
||||
showThemePanel = false
|
||||
},
|
||||
onDismiss = { showThemePanel = false },
|
||||
customThemes = customThemes,
|
||||
onCustomThemesUpdated = {
|
||||
customThemes = it
|
||||
saveCustomThemes(context, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (clickedLinkUrl != null) {
|
||||
val url = clickedLinkUrl!!
|
||||
AlertDialog(
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ fun ToolSettingsPopup(
|
|||
|
||||
val thicknessRange = when {
|
||||
isHighlighter -> 0.01f..0.06f
|
||||
isEraser -> 0.01f..0.1f
|
||||
isEraser -> 0.002f..0.1f
|
||||
else -> 0.001f..0.015f
|
||||
}
|
||||
@Suppress("UnusedExpression") if (isHighlighter) 0.005f else 0.001f
|
||||
|
|
@ -170,9 +170,9 @@ fun ToolSettingsPopup(
|
|||
.height(125.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val radiusDp = (activeToolThickness * 1000).coerceIn(10f, 100f).dp
|
||||
val diameterDp = (activeToolThickness * 800).coerceIn(4f, 150f).dp
|
||||
Box(
|
||||
modifier = Modifier.size(radiusDp),
|
||||
modifier = Modifier.size(diameterDp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
|
|
|
|||
|
|
@ -219,8 +219,7 @@ class ArchiveDocumentWrapper(private val file: File) : ReaderDocument {
|
|||
tempEntries.add(Pair(path, extractedFile))
|
||||
|
||||
var pfd: android.os.ParcelFileDescriptor? = null
|
||||
try {
|
||||
// Extract seamlessly using fd to avoid ByteBuffer's state sync bug
|
||||
@Suppress("ConvertTryFinallyToUseCall") try {
|
||||
pfd = android.os.ParcelFileDescriptor.open(extractedFile, android.os.ParcelFileDescriptor.MODE_READ_WRITE or android.os.ParcelFileDescriptor.MODE_CREATE)
|
||||
Archive.readDataIntoFd(archive, pfd.fd)
|
||||
} finally {
|
||||
|
|
@ -231,7 +230,7 @@ class ArchiveDocumentWrapper(private val file: File) : ReaderDocument {
|
|||
}
|
||||
}
|
||||
|
||||
tempEntries.sortBy { it.first } // Natural sorting order based on the filename inside the archive
|
||||
tempEntries.sortBy { it.first }
|
||||
tempEntries.forEach { imageEntries.add(it.second.absolutePath) }
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -306,15 +305,42 @@ class ArchivePageWrapper(imageBytes: ByteArray) : ReaderPage {
|
|||
val scaleX = drawSizeX.toFloat() / originalWidth
|
||||
val scaleY = drawSizeY.toFloat() / originalHeight
|
||||
|
||||
val srcLeft = (-startX / scaleX).toInt().coerceAtLeast(0)
|
||||
val srcTop = (-startY / scaleY).toInt().coerceAtLeast(0)
|
||||
val srcRight = (srcLeft + (bitmap.width / scaleX).toInt()).coerceAtMost(originalWidth)
|
||||
val srcBottom = (srcTop + (bitmap.height / scaleY).toInt()).coerceAtMost(originalHeight)
|
||||
val pageOffsetX = -startX.toFloat()
|
||||
val pageOffsetY = -startY.toFloat()
|
||||
|
||||
val exactSrcLeft = pageOffsetX / scaleX
|
||||
val exactSrcTop = pageOffsetY / scaleY
|
||||
val exactSrcRight = (pageOffsetX + bitmap.width) / scaleX
|
||||
val exactSrcBottom = (pageOffsetY + bitmap.height) / scaleY
|
||||
|
||||
val srcLeft = kotlin.math.floor(exactSrcLeft).toInt().coerceAtLeast(0)
|
||||
val srcTop = kotlin.math.floor(exactSrcTop).toInt().coerceAtLeast(0)
|
||||
val srcRight = kotlin.math.ceil(exactSrcRight).toInt().coerceAtMost(originalWidth)
|
||||
val srcBottom = kotlin.math.ceil(exactSrcBottom).toInt().coerceAtMost(originalHeight)
|
||||
|
||||
val rect = Rect(srcLeft, srcTop, srcRight, srcBottom)
|
||||
if (rect.width() <= 0 || rect.height() <= 0) return
|
||||
|
||||
val options = BitmapFactory.Options().apply { inPreferredConfig = Bitmap.Config.ARGB_8888 }
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inPreferredConfig = Bitmap.Config.ARGB_8888
|
||||
inScaled = false
|
||||
@Suppress("DEPRECATION")
|
||||
inDither = true
|
||||
|
||||
var sampleSize = 1
|
||||
val srcWidth = rect.width()
|
||||
val srcHeight = rect.height()
|
||||
|
||||
if (srcHeight > bitmap.height || srcWidth > bitmap.width) {
|
||||
val halfHeight = srcHeight / 2
|
||||
val halfWidth = srcWidth / 2
|
||||
while (halfHeight / sampleSize >= bitmap.height && halfWidth / sampleSize >= bitmap.width) {
|
||||
sampleSize *= 2
|
||||
}
|
||||
}
|
||||
inSampleSize = sampleSize
|
||||
}
|
||||
|
||||
val region = try {
|
||||
decoder.decodeRegion(rect, options)
|
||||
} catch (_: Exception) {
|
||||
|
|
@ -323,8 +349,17 @@ class ArchivePageWrapper(imageBytes: ByteArray) : ReaderPage {
|
|||
|
||||
if (region != null) {
|
||||
val canvas = Canvas(bitmap)
|
||||
val destRect = Rect(0, 0, bitmap.width, bitmap.height)
|
||||
canvas.drawBitmap(region, null, destRect, Paint(Paint.FILTER_BITMAP_FLAG))
|
||||
|
||||
val destLeft = (srcLeft * scaleX) - pageOffsetX
|
||||
val destTop = (srcTop * scaleY) - pageOffsetY
|
||||
val destRight = (srcRight * scaleX) - pageOffsetX
|
||||
val destBottom = (srcBottom * scaleY) - pageOffsetY
|
||||
|
||||
val destRect = RectF(destLeft, destTop, destRight, destBottom)
|
||||
|
||||
val paint = Paint(Paint.FILTER_BITMAP_FLAG or Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG)
|
||||
|
||||
canvas.drawBitmap(region, null, destRect, paint)
|
||||
region.recycle()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue