From 7208fa3d8a4228c438756934c0400bd7334dba57 Mon Sep 17 00:00:00 2001 From: ocean <503259349@qq.com> Date: Tue, 4 Nov 2025 15:31:01 +0800 Subject: [PATCH] =?UTF-8?q?=E9=95=BF=E5=9B=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pro/app/ui/act/PdfPickerActivity.kt | 10 ++- .../pro/app/ui/act/PdfResultActivity.kt | 15 +++- .../pro/app/ui/act/PdfToImageActivity.kt | 47 +++++++--- .../pro/app/ui/fragment/ToolsFrag.kt | 4 + .../all/pdfreader/pro/app/util/PdfUtils.kt | 89 +++++++++++++++++++ app/src/main/res/layout/fragment_tools.xml | 6 ++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 156 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfPickerActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfPickerActivity.kt index 0b9ebe9..ef81d49 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfPickerActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfPickerActivity.kt @@ -106,10 +106,16 @@ class PdfPickerActivity : BaseActivity() { } PdfPickerSource.IMAGES_TO_PDF -> {} - PdfPickerSource.TO_LONG_IMAGE -> {} + PdfPickerSource.TO_LONG_IMAGE -> { + val intent = PdfToImageActivity.createIntent(this, pdf.filePath, + PdfPickerSource.TO_LONG_IMAGE) + startActivity(intent) + finish() + } PdfPickerSource.NONE -> {} PdfPickerSource.PDF_TO_IMAGES -> { - val intent = PdfToImageActivity.createIntent(this, pdf.filePath) + val intent = PdfToImageActivity.createIntent(this, pdf.filePath, + PdfPickerSource.PDF_TO_IMAGES) startActivity(intent) finish() } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfResultActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfResultActivity.kt index 3d4992d..467568c 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfResultActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfResultActivity.kt @@ -112,6 +112,7 @@ class PdfResultActivity : BaseActivity() { private var exitDialog: PromptDialogFragment? = null private val pdfRepository = getRepository() private lateinit var pdfScanner: PdfScanner + private var openedExternal = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -262,7 +263,8 @@ class PdfResultActivity : BaseActivity() { } val unlockPassword = intent.getStringExtra(EXTRA_LOCK_UNLOCK_PASSWORD) ?: "" runOnUiThread { - binding.congratulationsDesc.text = getString(R.string.remove_password_successfully) + binding.congratulationsDesc.text = + getString(R.string.remove_password_successfully) } PdfSecurityUtils.removePasswordFromPdfWithProgress( filepath, unlockPassword @@ -402,15 +404,15 @@ class PdfResultActivity : BaseActivity() { } } binding.okBtn.setOnClickListener { - if (source == PdfPickerSource.PDF_TO_IMAGES) { + if (source == PdfPickerSource.PDF_TO_IMAGES || source == PdfPickerSource.TO_LONG_IMAGE) { val selectedItem = adapter.getSelectedItem() selectedItem?.let { val string = AppUtils.openImageFile(this, File(selectedItem.filePath)) if (string.isNotEmpty()) { showToast(string) } + openedExternal = true } - finish() } else { val selectedItem = adapter.getSelectedItem() selectedItem?.let { @@ -422,6 +424,13 @@ class PdfResultActivity : BaseActivity() { } } + override fun onResume() { + super.onResume() + if (openedExternal) { + finish() + } + } + override fun onDestroy() { super.onDestroy() isProcessing = false diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfToImageActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfToImageActivity.kt index 910b0e9..583025f 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfToImageActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfToImageActivity.kt @@ -2,6 +2,7 @@ package com.all.pdfreader.pro.app.ui.act import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle import android.view.View import androidx.lifecycle.lifecycleScope @@ -10,31 +11,32 @@ import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.databinding.ActivityPdfToImgBinding import com.all.pdfreader.pro.app.model.PdfPageItem import com.all.pdfreader.pro.app.model.PdfPickerSource -import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem import com.all.pdfreader.pro.app.ui.adapter.SplitPdfAdapter import com.all.pdfreader.pro.app.ui.dialog.PdfPasswordProtectionDialogFragment import com.all.pdfreader.pro.app.util.AppUtils.setOnSingleClickListener import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted -import com.all.pdfreader.pro.app.util.FileUtils.toUnderscoreDateTime import com.all.pdfreader.pro.app.util.PdfUtils import com.gyf.immersionbar.ImmersionBar import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File +import java.io.Serializable class PdfToImageActivity : BaseActivity() { override val TAG: String = "PdfToImageActivity" var currentPassword: String? = null//只会选择一个文件。直接进行密码传递 + private lateinit var source: PdfPickerSource companion object { private const val EXTRA_PDF_PATH = "extra_pdf_path" - - fun createIntent(context: Context, filePath: String): Intent { + private const val EXTRA_SOURCE = "extra_source" + fun createIntent(context: Context, filePath: String, source: PdfPickerSource): Intent { return Intent(context, PdfToImageActivity::class.java).apply { putExtra(EXTRA_PDF_PATH, filePath) + putExtra(EXTRA_SOURCE, source) } } } @@ -52,6 +54,7 @@ class PdfToImageActivity : BaseActivity() { .navigationBarColor(R.color.bg_color).init() filePath = intent.getStringExtra(EXTRA_PDF_PATH) ?: throw IllegalArgumentException("PDF file hash is required") + source = getSerializableOrDefault(EXTRA_SOURCE, PdfPickerSource.NONE) if (filePath.isEmpty()) { showToast(getString(R.string.file_not)) finish() @@ -92,13 +95,23 @@ class PdfToImageActivity : BaseActivity() { } binding.continueNowBtn.setOnSingleClickListener { val selectedPages = pdfPageList.filter { it.isSelected }.map { it.copy() } - val intent = PdfResultActivity.createIntentPdfToImageActivityToResult( - context = this, - filepath = filePath, - list = ArrayList(selectedPages), - source = PdfPickerSource.PDF_TO_IMAGES, - password = currentPassword - ) + val intent = if (source == PdfPickerSource.PDF_TO_IMAGES) { + PdfResultActivity.createIntentPdfToImageActivityToResult( + context = this, + filepath = filePath, + list = ArrayList(selectedPages), + source = PdfPickerSource.PDF_TO_IMAGES, + password = currentPassword + ) + } else { + PdfResultActivity.createIntentPdfToImageActivityToResult( + context = this, + filepath = filePath, + list = ArrayList(selectedPages), + source = PdfPickerSource.TO_LONG_IMAGE, + password = currentPassword + ) + } startActivity(intent) finish() } @@ -172,4 +185,16 @@ class PdfToImageActivity : BaseActivity() { override fun onDestroy() { super.onDestroy() } + + private inline fun getSerializableOrDefault( + key: String, default: T + ): T { + val result: T? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getSerializableExtra(key, T::class.java) + } else { + @Suppress("DEPRECATION") intent.getSerializableExtra(key) as? T + } + return result ?: default + } + } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/ToolsFrag.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/ToolsFrag.kt index 9f5426a..ff9a0ff 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/ToolsFrag.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/ToolsFrag.kt @@ -72,6 +72,10 @@ class ToolsFrag : BaseFrag() { val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.PDF_TO_IMAGES) startActivity(intent) } + binding.pdfToLongImgBtn.setOnClickListener { + val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.TO_LONG_IMAGE) + startActivity(intent) + } } private fun openImagePicker() { diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfUtils.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfUtils.kt index c230db2..56ce28a 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfUtils.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfUtils.kt @@ -3,6 +3,7 @@ package com.all.pdfreader.pro.app.util import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Canvas import android.os.ParcelFileDescriptor import android.util.Log import androidx.core.graphics.createBitmap @@ -24,6 +25,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream +import androidx.core.graphics.scale object PdfUtils { @@ -419,4 +421,91 @@ object PdfUtils { fd.close() resultList } + + /** + * 导出选中的 PDF 页到长图文件 + */ + suspend fun exportPagesAsLongImage( + context: Context, + inputFile: File, + selectedPages: List, + outputDir: File, + password: String? = null, + format: String = "JPG", + quality: Int = 100, + onProgress: ((current: Int, total: Int) -> Unit)? = null + ): File? = withContext(Dispatchers.IO) { + + if (selectedPages.isEmpty()) return@withContext null + if (!outputDir.exists()) outputDir.mkdirs() + + val pdfium = PdfiumCore(context) + val fd = ParcelFileDescriptor.open(inputFile, ParcelFileDescriptor.MODE_READ_ONLY) + val doc = if (password.isNullOrEmpty()) pdfium.newDocument(fd) else pdfium.newDocument(fd, password) + + val sorted = selectedPages.sortedBy { it.pageIndex } + val bitmaps = mutableListOf() + + sorted.forEachIndexed { idx, item -> + pdfium.openPage(doc, item.pageIndex) + val w = pdfium.getPageWidthPoint(doc, item.pageIndex) + val h = pdfium.getPageHeightPoint(doc, item.pageIndex) + val bmp = createBitmap(w, h) + pdfium.renderPageBitmap(doc, bmp, item.pageIndex, 0, 0, w, h) + + bitmaps.add(bmp) + onProgress?.invoke(idx + 1, sorted.size) + delay(1) + } + + val longBitmap = mergeBitmapsVertical(bitmaps) + val baseName = inputFile.nameWithoutExtension + val extension = if (format.equals("PNG", true)) "png" else "jpg" + val compressType = if (format.equals("PNG", true)) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG + + var outFile = File(outputDir, "${baseName}_long.$extension") + var i = 1 + while (outFile.exists()) { + outFile = File(outputDir, "${baseName}_long($i).$extension") + i++ + } + + FileOutputStream(outFile).use { + longBitmap.compress(compressType, quality, it) + } + + pdfium.closeDocument(doc) + fd.close() + + // 释放内存 + bitmaps.forEach { it.recycle() } + + return@withContext outFile + } + + private fun mergeBitmapsVertical(bitmaps: List): Bitmap { + if (bitmaps.isEmpty()) throw IllegalArgumentException("Bitmap list is empty") + + // 统一宽度为最宽的 + val maxWidth = bitmaps.maxOf { it.width } + + // 按比例缩放每张,保持宽度一致 + val scaledBitmaps = bitmaps.map { bmp -> + if (bmp.width == maxWidth) bmp + else bmp.scale(maxWidth, (bmp.height * (maxWidth / bmp.width.toFloat())).toInt()) + } + + val totalHeight = scaledBitmaps.sumOf { it.height } + val mergedBitmap = createBitmap(maxWidth, totalHeight) + val canvas = Canvas(mergedBitmap) + + var currentHeight = 0 + scaledBitmaps.forEach { bmp -> + canvas.drawBitmap(bmp, 0f, currentHeight.toFloat(), null) + currentHeight += bmp.height + } + + return mergedBitmap + } + } diff --git a/app/src/main/res/layout/fragment_tools.xml b/app/src/main/res/layout/fragment_tools.xml index 48ba139..1ea348a 100644 --- a/app/src/main/res/layout/fragment_tools.xml +++ b/app/src/main/res/layout/fragment_tools.xml @@ -47,4 +47,10 @@ android:layout_height="wrap_content" android:text="@string/pdf_to_img" /> +