From 8e4c25d54fd89425f946ff65c020ce908a0ed66c Mon Sep 17 00:00:00 2001 From: ocean <503259349@qq.com> Date: Thu, 23 Oct 2025 17:16:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=90=9C=E7=B4=A2=E6=96=87?= =?UTF-8?q?=E5=AD=97=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=90=8E?= =?UTF-8?q?=E7=BB=AD=E7=9C=8B=E6=98=AF=E5=90=A6=E4=BD=BF=E7=94=A8=E3=80=82?= =?UTF-8?q?=EF=BC=88=E6=9C=AA=E5=AE=8C=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 7 +- .../app/ui/act/PdfTextSearchTestActivity.kt | 226 ++++++++++++ .../pro/app/ui/act/PdfViewActivity.kt | 2 + .../app/ui/adapter/SearchResultsAdapter.kt | 41 +++ .../pdfreader/pro/app/ui/fragment/HomeFrag.kt | 9 +- .../pdfreader/pro/app/util/PDFHighlighter.kt | 81 +++++ .../pro/app/util/PDFSearchManager.kt | 332 ++++++++++++++++++ app/src/main/res/layout/activity_pdf_view.xml | 108 ++++-- .../res/layout/activity_pdf_viewer_test.xml | 74 ++++ .../main/res/layout/item_search_result.xml | 34 ++ 10 files changed, 890 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfTextSearchTestActivity.kt create mode 100644 app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SearchResultsAdapter.kt create mode 100644 app/src/main/java/com/all/pdfreader/pro/app/util/PDFHighlighter.kt create mode 100644 app/src/main/java/com/all/pdfreader/pro/app/util/PDFSearchManager.kt create mode 100644 app/src/main/res/layout/activity_pdf_viewer_test.xml create mode 100644 app/src/main/res/layout/item_search_result.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 48ca91a..4073b57 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -105,13 +105,18 @@ android:label="@string/app_name" android:screenOrientation="portrait" /> - + + () + private lateinit var adapter: SearchResultsAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityPdfViewerTestBinding.inflate(layoutInflater) + setContentView(binding.root) + val pdfPath = intent.getStringExtra("pdf_path") ?: "" + if (pdfPath.isEmpty()) { + finish() + return + } + pdfFile = File(pdfPath) + initViews() + setupPDFViewer() + loadPDF() + } + + private fun initViews() { + adapter = SearchResultsAdapter(searchResults) { result -> + onSearchResultClicked(result, true) + } + binding.searchResultsRecyclerView.adapter = adapter + binding.searchResultsRecyclerView.layoutManager = LinearLayoutManager(this) + + binding.searchButton.alpha = 0.5f + binding.searchButton.isEnabled = false + binding.searchButton.setOnClickListener { + performSearch() + } + binding.clearButton.setOnClickListener { + clearHighlights() + } + } + + private fun setupPDFViewer() { + searchManager = PDFSearchManager(binding.pdfView) + highlighter = PDFHighlighter(binding.pdfView, searchManager) + + initSearchDocument() + } + + private fun initSearchDocument() { + lifecycleScope.launch(Dispatchers.IO) { + searchManager.getDocument( + pdfFile!!, + onLoaded = { doc -> + lifecycleScope.launch(Dispatchers.IO) { + checkPageHasText(doc, binding.pdfView.currentPage) + } + }, + onError = { e -> + runOnUiThread { + binding.searchButton.alpha = 0.5f + binding.searchButton.isEnabled = false + } + } + ) + } + } + + + /** + * 异步检查指定页是否有文字并更新按钮 + */ + private suspend fun checkPageHasText(doc: PDDocument, pageIndex: Int): Boolean { + val hasText = searchManager.pageHasText(doc, pageIndex, minChars = 8) // 可调整阈值 + withContext(Dispatchers.Main) { + if (hasText) { + binding.searchButton.alpha = 1f + binding.searchButton.isEnabled = true + } else { + binding.searchButton.alpha = 0.5f + binding.searchButton.isEnabled = false + } + } + return hasText + } + + private var pageCheckJob: Job? = null + + private fun loadPDF() { + binding.pdfView.fromFile(pdfFile) + .enableSwipe(true) + .swipeHorizontal(true) + .enableDoubletap(true) + .defaultPage(0) + .onDraw(highlighter) // 设置绘制监听器 + .enableAnnotationRendering(true) + .password(null) + .scrollHandle(null) + .swipeHorizontal(false) + .enableAntialiasing(true) + .onPageChange(object : OnPageChangeListener { + override fun onPageChanged(page: Int, pageCount: Int) { + logDebug("page->$page") + logDebug("pageCount->$pageCount") + val doc = searchManager.getCachedDoc() + if (doc != null) { + lifecycleScope.launch(Dispatchers.IO) { + pageCheckJob?.cancel() + pageCheckJob = lifecycleScope.launch(Dispatchers.IO) { + delay(50) // 防抖 + val hasText = checkPageHasText(doc, page) + //确定这页有文字,是否进行搜索过,当前搜索的文字 + if (hasText && searchManager.hasSearched && searchManager.currentSearchText != null) { + searchTextIng( + pdfFile!!, + searchManager.currentSearchText ?: "", + binding.pdfView.currentPage + ) + } + } + } + } + + } + }) + .spacing(0) + .load() + } + + private fun performSearch() { + val searchText = binding.searchEditText.text.toString().trim() + if (searchText.isEmpty()) { + Toast.makeText(this, "请输入搜索关键词", Toast.LENGTH_SHORT).show() + return + } + + val file = pdfFile ?: return + + binding.progressBar.visibility = View.VISIBLE + + lifecycleScope.launch(Dispatchers.IO) { + searchTextIng(file, searchText, binding.pdfView.currentPage) + } + } + + private fun searchTextIng(pdfFile: File, key: String, targetPage: Int? = null) { + lifecycleScope.launch(Dispatchers.IO) { + try { + val results = searchManager.searchText(pdfFile, key, targetPage) + + withContext(Dispatchers.Main) { + binding.progressBar.visibility = View.GONE + searchResults.clear() + searchResults.addAll(results) + adapter.notifyDataSetChanged() + + if (!results.isEmpty()) { + results.firstOrNull()?.let { firstResult -> + onSearchResultClicked(firstResult) + } + } + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + binding.progressBar.visibility = View.GONE + } + } + } + } + + private fun onSearchResultClicked( + result: PDFSearchManager.TextSearchResult, + isJumpTo: Boolean = false + ) { + if (isJumpTo) { + // 跳转到对应页面 (PDFView 页面索引从0开始) + binding.pdfView.jumpTo(result.pageNumber - 1) + } + + // 设置高亮 + highlighter.setCurrentPageHighlights(result.pageNumber) + + // 标记已搜索高亮 + searchManager.currentSearchText = result.text + searchManager.hasSearched = true + + // 可选:滚动到搜索结果列表中的对应项 + val index = searchResults.indexOf(result) + if (index != -1) { + binding.searchResultsRecyclerView.scrollToPosition(index) + } + } + + private fun clearHighlights() { + searchManager.clearHighlights() + highlighter.clearCurrentHighlights() + searchResults.clear() + adapter.notifyDataSetChanged() + } + + override fun onDestroy() { + super.onDestroy() + searchManager.closeCachedDocument() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfViewActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfViewActivity.kt index 3c100c0..c74a303 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfViewActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/PdfViewActivity.kt @@ -276,7 +276,9 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList onTap(this@PdfViewActivity) // 单击回调 onPageChange(this@PdfViewActivity) // 页面改变回调 scrollHandle(CustomScrollHandle(this@PdfViewActivity)) // 自定义的页数展示 + onDraw { canvas, pageWidth, pageHeight, displayedPage -> + } if (appStore.isPageFling) { pageSnap(true)//页面可在逐页中,可居中展示,非逐页模式,滑动停止后可自动定格在居中位置。 pageFling(true)//逐页 diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SearchResultsAdapter.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SearchResultsAdapter.kt new file mode 100644 index 0000000..ef9bfd5 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/SearchResultsAdapter.kt @@ -0,0 +1,41 @@ +package com.all.pdfreader.pro.app.ui.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.all.pdfreader.pro.app.R +import com.all.pdfreader.pro.app.util.PDFSearchManager + +class SearchResultsAdapter( + private val results: List, + private val onItemClick: (PDFSearchManager.TextSearchResult) -> Unit +) : RecyclerView.Adapter() { + + class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val pageText: TextView = view.findViewById(R.id.pageText) + val matchCount: TextView = view.findViewById(R.id.matchCount) + val previewText: TextView = view.findViewById(R.id.previewText) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_search_result, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val result = results[position] + + holder.pageText.text = "第 ${result.pageNumber} 页" + holder.matchCount.text = "${result.positions.size} 处匹配" + holder.previewText.text = "搜索: \"${result.text}\"" + + holder.itemView.setOnClickListener { + onItemClick(result) + } + } + + override fun getItemCount() = results.size +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt index c2a618c..6b38f44 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt @@ -1,13 +1,12 @@ package com.all.pdfreader.pro.app.ui.fragment import android.content.Context +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.all.pdfreader.pro.app.databinding.FragmentHomeBinding import com.all.pdfreader.pro.app.model.FragmentType @@ -15,6 +14,7 @@ import com.all.pdfreader.pro.app.model.SortConfig import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity import com.all.pdfreader.pro.app.room.repository.PdfRepository import com.all.pdfreader.pro.app.ui.act.MainActivity +import com.all.pdfreader.pro.app.ui.act.PdfTextSearchTestActivity import com.all.pdfreader.pro.app.ui.act.PdfViewActivity import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter import com.all.pdfreader.pro.app.ui.dialog.ListMoreDialogFragment @@ -53,6 +53,11 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment { adapter = PdfAdapter(pdfList = mutableListOf(), onItemClick = { pdf -> val intent = PdfViewActivity.createIntent(requireContext(), pdf.filePath) startActivity(intent) + +// val intent = Intent(context, PdfTextSearchTestActivity::class.java).apply { +// putExtra("pdf_path", pdf.filePath) +// } +// startActivity(intent) }, onMoreClick = { pdf -> ListMoreDialogFragment(pdf.filePath).show(parentFragmentManager, FRAG_TAG) }, onLongClick = { pdf -> diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/PDFHighlighter.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/PDFHighlighter.kt new file mode 100644 index 0000000..901cd92 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/PDFHighlighter.kt @@ -0,0 +1,81 @@ +package com.all.pdfreader.pro.app.util + +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import com.github.barteksc.pdfviewer.PDFView +import com.github.barteksc.pdfviewer.listener.OnDrawListener + +class PDFHighlighter( + private val pdfView: PDFView, + private val searchManager: PDFSearchManager +) : OnDrawListener { + + private val highlightPaint = Paint().apply { + color = 0x80FFD700.toInt() // 半透明黄色 + style = Paint.Style.FILL + } + + private val highlightStrokePaint = Paint().apply { + color = 0xFFFFA500.toInt() // 橙色边框 + style = Paint.Style.STROKE + strokeWidth = 2f + } + + private var currentPageHighlights: List = emptyList() + + /** + * 设置当前页面的高亮 + */ + fun setCurrentPageHighlights(page: Int) { + currentPageHighlights = searchManager.getHighlightsForPage(page) + pdfView.invalidate() + } + + /** + * 清除当前高亮 + */ + fun clearCurrentHighlights() { + currentPageHighlights = emptyList() + pdfView.invalidate() + } + + override fun onLayerDrawn(canvas: Canvas, pageWidth: Float, pageHeight: Float, displayedPage: Int) { + // 只绘制当前显示页面的高亮 + if (displayedPage != searchManager.currentHighlightPage) { + searchManager.currentHighlightPage = displayedPage + setCurrentPageHighlights(displayedPage + 1) // PDFView 页面从0开始,我们的搜索从1开始 + } + + // 绘制高亮区域 + currentPageHighlights.forEach { position -> + try { + val rect = position.getRelativeRect() + val absoluteRect = RectF( + rect.left * pageWidth, + rect.top * pageHeight, + rect.right * pageWidth, + rect.bottom * pageHeight + ) + + // 可选:添加小的边距让高亮更明显 + val margin = 1f + val paddedRect = RectF( + absoluteRect.left - margin, + absoluteRect.top - margin, + absoluteRect.right + margin, + absoluteRect.bottom + margin + ) + + // 绘制半透明高亮 + canvas.drawRect(paddedRect, highlightPaint) + + // 绘制边框 + canvas.drawRect(paddedRect, highlightStrokePaint) + + } catch (e: Exception) { + e.printStackTrace() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/PDFSearchManager.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/PDFSearchManager.kt new file mode 100644 index 0000000..962f41e --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/PDFSearchManager.kt @@ -0,0 +1,332 @@ +package com.all.pdfreader.pro.app.util + +import android.util.Log +import com.github.barteksc.pdfviewer.PDFView +import com.tom_roush.pdfbox.pdmodel.PDDocument +import com.tom_roush.pdfbox.pdmodel.PDPage +import com.tom_roush.pdfbox.text.PDFTextStripper +import com.tom_roush.pdfbox.text.TextPosition +import java.io.File + +class PDFSearchManager(private val pdfView: PDFView) { + + private var currentSearchResults: List = emptyList() + var currentHighlightPage = -1 + + private var cachedDoc: PDDocument? = null // 缓存已打开的文档 + private var cachedPath: String? = null // 记录缓存对应的文件路径 + + var currentSearchText: String? = null//记录当前的搜索文字 + var hasSearched: Boolean = false//记录当前是否进行过搜索 + + data class TextSearchResult( + val pageNumber: Int, + val text: String, + val positions: List + ) + + data class TextPositionInfo( + val text: String, // 当前字符或文本片段 + val x: Float, // 字符左下角的 X 坐标(PDF页面坐标系) + val y: Float, // 字符基线(baseline)在页面上的 Y 坐标(PDF页面坐标系) + val width: Float, // 字符宽度 + val height: Float, // 字符高度 + val pageWidth: Float, // 当前 PDF 页宽 + val pageHeight: Float // 当前 PDF 页高 + ) { + /** + * 将字符位置信息转换为 PDFView 可用的相对矩形 (RectF) + * + * @return RectF 返回一个相对页面比例的矩形(左上角和右下角的值在 0~1 范围) + * + * 说明: + * - PDFBox 中 y 是字符基线 (baseline),不是顶部,所以顶部需要计算为 y - height。(如果不这样那选中的位子会在文字的正下方) + * - RectF 的 left/top/right/bottom 坐标是相对于页面尺寸的比例。 + * - 添加了 padding,使高亮框稍微大于文字,避免高亮框太紧。 + */ + fun getRelativeRect(): android.graphics.RectF { + // padding:用于微调高亮框大小,按页面比例计算 + val padding = 2f / pageHeight + + // 计算相对页面坐标 + val left = x / pageWidth - padding // 左边界 + val top = (y - height) / pageHeight - padding // 顶部边界(基线减去字符高度,再减去 margin) + val right = (x + width) / pageWidth + padding // 右边界 + val bottom = y / pageHeight + padding // 底部边界(基线加上 margin) + + // 返回相对页面坐标的矩形 + return android.graphics.RectF(left, top, right, bottom) + } + } + + /** + * 获取 PDDocument,若已缓存则直接返回,否则加载并缓存。 + * @param file 要加载的 PDF 文件 + * @param onLoaded 加载成功时回调(带 PDDocument 参数) + * @param onError 加载失败时回调(可选) + */ + fun getDocument( + file: File, + onLoaded: ((PDDocument) -> Unit)? = null, + onError: ((Exception) -> Unit)? = null + ): PDDocument { + if (cachedDoc == null || cachedPath != file.absolutePath) { + try { + // 如果是新的文件,则关闭旧文档 + cachedDoc?.close() + cachedDoc = PDDocument.load(file) + cachedPath = file.absolutePath + + // 通知加载成功 + onLoaded?.invoke(cachedDoc!!) + } catch (e: Exception) { + onError?.invoke(e) + throw e + } + } else { + // 已缓存文档,仍可调用回调 + onLoaded?.invoke(cachedDoc!!) + } + return cachedDoc!! + } + + fun getCachedDoc(): PDDocument? { + return cachedDoc + } + + /** + * 释放 PDF 资源。 + */ + fun closeCachedDocument() { + try { + cachedDoc?.close() + } catch (e: Exception) { + e.printStackTrace() + } finally { + cachedDoc = null + cachedPath = null + } + } + + /** + * 主搜索方法 + * @param pdfFile 要搜索的 PDF 文件 + * @param key 搜索关键词 + * @param targetPage 可选参数:指定页码(从 0 开始)。如果为 null 则搜索整本。 + */ + fun searchText(pdfFile: File, key: String, targetPage: Int? = null): List { + Log.d("ocean", "searchText pdfFile->$pdfFile") + Log.d("ocean", "searchText key->$key") + Log.d("ocean", "searchText targetPage->$targetPage") + val results = mutableListOf() + + try { + val doc = getDocument(pdfFile) // 这里不再每次重新 load + + val startPage = targetPage ?: 0 + val endPage = targetPage ?: (doc.numberOfPages - 1) + + for (pageIndex in startPage..endPage) { + val page = doc.getPage(pageIndex) + val pageResults = searchInPage(doc, pageIndex, key, page) + results.add(pageResults) + } + + } catch (e: Exception) { + e.printStackTrace() + } + + currentSearchResults = results + return results + } + + private fun searchInPage( + document: PDDocument, + pageIndex: Int, + searchText: String, + page: PDPage + ): TextSearchResult { + val positions = mutableListOf() + val searchTextLower = searchText.toLowerCase() + val pageWidth = page.mediaBox.width + val pageHeight = page.mediaBox.height + + val textStripper = object : PDFTextStripper() { + private val currentPagePositions = mutableListOf() + + override fun writeString(text: String, textPositions: List) { + // 收集文本位置信息 + for (tp in textPositions) { + val positionInfo = TextPositionInfo( + text = tp.unicode, + x = tp.xDirAdj, + y = tp.yDirAdj, + width = tp.widthDirAdj, + height = tp.heightDir, + pageWidth = pageWidth, + pageHeight = pageHeight + ) + currentPagePositions.add(positionInfo) + } + super.writeString(text, textPositions) + } + + override fun endPage(page: PDPage?) { + // 在页面结束时进行搜索匹配 + val matchedPositions = findTextMatches(currentPagePositions, searchTextLower) + if (matchedPositions.isNotEmpty()) { + positions.addAll(matchedPositions) + } + currentPagePositions.clear() + super.endPage(page) + } + } + + textStripper.startPage = pageIndex + 1 + textStripper.endPage = pageIndex + 1 + + try { + textStripper.getText(document) + } catch (e: Exception) { + e.printStackTrace() + } + + return TextSearchResult( + pageNumber = pageIndex + 1, + text = searchText, + positions = positions + ) + } + + private fun findTextMatches( + positions: List, + searchText: String + ): List { + val matches = mutableListOf() + val fullText = positions.joinToString("") { it.text }.toLowerCase() + + var index = 0 + while (index < fullText.length) { + val matchIndex = fullText.indexOf(searchText, index) + if (matchIndex == -1) break + + // 精确查找匹配的文本段在positions中的范围 + var currentLength = 0 + val matchedPositions = mutableListOf() + + for (pos in positions) { + val positionStart = currentLength + val positionEnd = currentLength + pos.text.length + + // 精确检查:这个位置是否在当前匹配范围内 + if (positionStart < matchIndex + searchText.length && + positionEnd > matchIndex + ) { + matchedPositions.add(pos) + } + + currentLength += pos.text.length + + // 如果已经超过当前匹配范围,立即停止 + if (currentLength >= matchIndex + searchText.length) break + } + + // 计算合并后的矩形区域 + if (matchedPositions.isNotEmpty()) { + val mergedPosition = mergePositions(matchedPositions, searchText) + matches.add(mergedPosition) + } + + index = matchIndex + searchText.length + } + + Log.d("PDF_DEBUG", "找到 ${matches.size} 个独立匹配") + return matches + } + + private fun mergePositions( + positions: List, + originalText: String + ): TextPositionInfo { + val first = positions.first() + val minX = positions.minByOrNull { it.x }?.x ?: first.x + val maxX = positions.maxByOrNull { it.x + it.width }?.let { it.x + it.width } + ?: (first.x + first.width) + val minY = positions.minByOrNull { it.y }?.y ?: first.y + val maxY = positions.maxByOrNull { it.y + it.height }?.let { it.y + it.height } + ?: (first.y + first.height) + + return TextPositionInfo( + text = originalText, + x = minX, + y = minY, + width = maxX - minX, + height = maxY - minY, + pageWidth = first.pageWidth, + pageHeight = first.pageHeight + ) + } + + /** + * 获取当前页面的高亮区域 + */ + fun getHighlightsForPage(page: Int): List { + return currentSearchResults + .find { it.pageNumber == page } + ?.positions ?: emptyList() + } + + /** + * 清除所有高亮 + */ + fun clearHighlights() { + currentSearchResults = emptyList() + pdfView.invalidate() + currentSearchText = null + hasSearched = false + } + + /** + * 判断当前页是否有可搜索文字 + * @param doc 当前 PDF 文档 + * @param pageIndex 当前页索引 + * @param minChars 阈值:认为一页有文字的最少字符数 + */ + fun pageHasText(doc: PDDocument, pageIndex: Int, minChars: Int = 10): Boolean { + var charCount = 0 + + val stripper = object : PDFTextStripper() { + override fun processTextPosition(text: TextPosition) { + Log.d( + "ocean", + "processTextPosition text->$text width->${text.width}height->${text.height}" + ) + //太小的文字当没有 + if (text.width > 1 && text.height > 2) { + charCount++ + } + // 如果超过阈值,直接停止 + if (charCount >= minChars) { + throw StopParsingException() + } + } + } + + return try { + stripper.startPage = pageIndex + 1 + stripper.endPage = pageIndex + 1 + stripper.getText(doc) + // 解析完,如果字符数达不到阈值就返回 false + charCount >= minChars + } catch (e: StopParsingException) { + // 遇到足够字符,认为有文字 + true + } catch (e: Exception) { + e.printStackTrace() + false + } + } + + + private class StopParsingException : RuntimeException() +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pdf_view.xml b/app/src/main/res/layout/activity_pdf_view.xml index c6afa4e..767f606 100644 --- a/app/src/main/res/layout/activity_pdf_view.xml +++ b/app/src/main/res/layout/activity_pdf_view.xml @@ -39,33 +39,99 @@ - + android:visibility="gone"> + + + + + + + + + + + + + + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:orientation="horizontal"> - + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_pdf_viewer_test.xml b/app/src/main/res/layout/activity_pdf_viewer_test.xml new file mode 100644 index 0000000..e4fd264 --- /dev/null +++ b/app/src/main/res/layout/activity_pdf_viewer_test.xml @@ -0,0 +1,74 @@ + + + + + + + + +