支持PDF格式

This commit is contained in:
ag2s20150909 2022-12-19 12:49:59 +08:00
parent 17a828cf40
commit 342a08400f
15 changed files with 343 additions and 14 deletions

View File

@ -16,7 +16,8 @@ def version = "3." + releaseTime()
def gitCommits = Integer.parseInt('git rev-list HEAD --count'.execute().text.trim())
android {
compileSdk 33
compileSdk = compile_sdk_version
buildToolsVersion = build_tool_version
namespace 'io.legado.app'
kotlinOptions {
jvmTarget = "11"

View File

@ -23,7 +23,7 @@ object AppPattern {
val debugMessageSymbolRegex = Regex("[⇒◇┌└≡]")
//本地书籍支持类型
val bookFileRegex = Regex(".*\\.(txt|epub|umd)", RegexOption.IGNORE_CASE)
val bookFileRegex = Regex(".*\\.(txt|epub|umd|pdf)", RegexOption.IGNORE_CASE)
/**
* 所有标点

View File

@ -49,6 +49,10 @@ val Book.isUmd: Boolean
get() {
return isLocal && originName.endsWith(".umd", true)
}
val Book.isPdf: Boolean
get() {
return isLocal && originName.endsWith(".pdf", true)
}
val Book.isOnLineTxt: Boolean
get() {

View File

@ -0,0 +1,51 @@
package io.legado.app.help.http.cronet
import okhttp3.RequestBody
import okio.Buffer
import org.chromium.net.UploadDataProvider
import org.chromium.net.UploadDataSink
import java.io.IOException
import java.nio.ByteBuffer
class BodyUploadProvider(body: RequestBody) : UploadDataProvider(), AutoCloseable {
private val body: RequestBody
private val buffer: Buffer?
init {
buffer = Buffer()
this.body = body
try {
body.writeTo(buffer)
} catch (e: IOException) {
e.printStackTrace()
}
}
@Throws(IOException::class)
override fun getLength(): Long {
return body.contentLength()
}
@Throws(IOException::class)
override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) {
check(byteBuffer.hasRemaining()) { "Cronet passed a buffer with no bytes remaining" }
var read: Int
var bytesRead = 0
while (bytesRead == 0) {
read = buffer!!.read(byteBuffer)
bytesRead += read
}
uploadDataSink.onReadSucceeded(false)
}
@Throws(IOException::class)
override fun rewind(uploadDataSink: UploadDataSink) {
uploadDataSink.onRewindSucceeded()
}
@Throws(IOException::class)
override fun close() {
buffer?.close()
super.close()
}
}

View File

@ -7,10 +7,8 @@ import io.legado.app.utils.DebugLog
import okhttp3.Headers
import okhttp3.MediaType
import okhttp3.Request
import okio.Buffer
import org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISK
import org.chromium.net.ExperimentalCronetEngine
import org.chromium.net.UploadDataProviders
import org.chromium.net.UrlRequest
import org.json.JSONObject
import splitties.init.appCtx
@ -85,10 +83,8 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {
} else {
addHeader("Content-Type", "text/plain")
}
val buffer = Buffer()
requestBody.writeTo(buffer)
setUploadDataProvider(
UploadDataProviders.create(buffer.readByteArray()),
BodyUploadProvider(requestBody),
okHttpClient.dispatcher.executorService
)

View File

@ -76,6 +76,9 @@ object LocalBook {
book.isUmd -> {
UmdFile.getChapterList(book)
}
book.isPdf -> {
PdfFile.getChapterList(book)
}
else -> {
TextFile.getChapterList(book)
}
@ -100,6 +103,9 @@ object LocalBook {
book.isUmd -> {
UmdFile.getContent(book, chapter)
}
book.isPdf -> {
PdfFile.getContent(book, chapter)
}
else -> {
TextFile.getContent(book, chapter)
}
@ -166,6 +172,7 @@ object LocalBook {
)
if (book.isEpub) EpubFile.upBookInfo(book)
if (book.isUmd) UmdFile.upBookInfo(book)
if (book.isPdf) PdfFile.upBookInfo(book)
appDb.bookDao.insert(book)
} else {
//已有书籍说明是更新,删除原有目录

View File

@ -0,0 +1,225 @@
package io.legado.app.model.localBook
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import io.legado.app.constant.AppLog
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.book.getLocalUri
import io.legado.app.utils.*
import splitties.init.appCtx
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import kotlin.math.ceil
class PdfFile(var book: Book) {
companion object : BaseLocalBookParse {
private var pFile: PdfFile? = null
/**
* pdf分页尺寸
*/
const val PAGE_SIZE = 10
@Synchronized
private fun getPFile(book: Book): PdfFile {
if (pFile == null || pFile?.book?.bookUrl != book.bookUrl) {
pFile = PdfFile(book)
return pFile!!
}
pFile?.book = book
return pFile!!
}
@Synchronized
override fun upBookInfo(book: Book) {
getPFile(book).upBookInfo()
}
@Synchronized
override fun getChapterList(book: Book): ArrayList<BookChapter> {
return getPFile(book).getChapterList()
}
@Synchronized
override fun getContent(book: Book, chapter: BookChapter): String? {
return getPFile(book).getContent(chapter)
}
@Synchronized
override fun getImage(book: Book, href: String): InputStream? {
return getPFile(book).getImage(href)
}
}
private var fileDescriptor: ParcelFileDescriptor? = null
private var pdfRenderer: PdfRenderer? = null
get() {
if (field != null) {
return field
}
field = readPdf()
return field
}
init {
try {
pdfRenderer?.let { renderer ->
if (book.coverUrl.isNullOrEmpty()) {
book.coverUrl = FileUtils.getPath(
appCtx.externalFiles,
"covers",
"${MD5Utils.md5Encode16(book.bookUrl)}.jpg"
)
}
if (!File(book.coverUrl!!).exists()) {
FileOutputStream(FileUtils.createFileIfNotExist(book.coverUrl!!)).use { out ->
openPdfPage(renderer, 0)?.let { cover ->
cover.compress(Bitmap.CompressFormat.JPEG, 90, out)
}
out.flush()
}
}
}
} catch (e: Exception) {
AppLog.put("加载书籍封面失败\n${e.localizedMessage}", e)
e.printOnDebug()
}
}
/**
* 读取PDF文件
*
* @return
*/
private fun readPdf(): PdfRenderer? {
val uri = book.getLocalUri()
if (uri.isContentScheme()) {
fileDescriptor = appCtx.contentResolver.openFileDescriptor(uri, "r")?.also {
pdfRenderer = PdfRenderer(it)
}
} else {
fileDescriptor =
ParcelFileDescriptor.open(File(uri.path), ParcelFileDescriptor.MODE_READ_ONLY)
?.also {
pdfRenderer = PdfRenderer(it)
}
}
return pdfRenderer
}
/**
* 关闭pdf文件
*
*/
private fun closePdf() {
pdfRenderer?.close()
fileDescriptor?.close()
}
/**
* 渲染PDF页面
* 根据index打开pdf页面,并渲染到Bitmap
*
* @param renderer
* @param index
* @return
*/
private fun openPdfPage(renderer: PdfRenderer, index: Int): Bitmap? {
if (index >= renderer.pageCount) {
return null
}
return renderer.openPage(index)?.use { page ->
Bitmap.createBitmap(
SystemUtils.screenWidthPx,
(SystemUtils.screenWidthPx.toDouble() * page.height / page.width).toInt(),
Bitmap.Config.ARGB_8888
)
.apply {
page.render(this, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
}
}
}
private fun getContent(chapter: BookChapter): String? =
if (pdfRenderer == null) {
null
} else {
pdfRenderer?.let { renderer ->
buildString {
val start = chapter.index * PAGE_SIZE
val end = Math.min((chapter.index + 1) * PAGE_SIZE, renderer.pageCount)
(start until end).forEach {
append("<img src=").append('"').append(it).append('"').append(" >")
.append('\n')
}
}
}
}
private fun getImage(href: String): InputStream? {
if (pdfRenderer == null) {
return null
}
return try {
val index = href.toInt()
val bitmap = openPdfPage(pdfRenderer!!, index)
if (bitmap != null) {
BitmapUtils.toInputStream(bitmap).also { bitmap.recycle() }
} else {
null
}
} catch (e: Exception) {
return null
}
}
private fun getChapterList(): ArrayList<BookChapter> {
val chapterList = ArrayList<BookChapter>()
pdfRenderer?.let { renderer ->
if (renderer.pageCount > 0) {
val chapterCount = ceil((renderer.pageCount.toDouble() / PAGE_SIZE)).toInt()
(0 until chapterCount).forEach {
val chapter = BookChapter()
chapter.index = it
chapter.bookUrl = book.bookUrl
chapter.title = "分段_${it}"
chapter.url = "pdf_${it}"
chapterList.add(chapter)
}
}
}
return chapterList
}
private fun upBookInfo() {
if (pdfRenderer == null) {
pFile = null
book.intro = "书籍导入异常"
} else {
if (book.name.isEmpty()) {
book.name = book.originName.replace(".pdf", "")
}
}
}
protected fun finalize() {
closePdf()
}
}

View File

@ -4,4 +4,5 @@
* LocalBook.kt 导入解析总入口
* TextFile.kt 解析txt
* EpubFile.kt 解析epub
* PdfFile.kt 解析pdf 纯图片形式
* UmdFile.kt 解析umd

View File

@ -140,7 +140,10 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
if (docItem.isDir) {
scanDoc(docItem, false, scope)
} else if (docItem.name.endsWith(".txt", true)
|| docItem.name.endsWith(".epub", true)
|| docItem.name.endsWith(".epub", true) || docItem.name.endsWith(
".pdf",
true
) || docItem.name.endsWith(".umd", true)
) {
list.add(docItem)
}

View File

@ -12,13 +12,15 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.isEpub
import io.legado.app.help.book.isPdf
import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.ReadBook
import io.legado.app.model.localBook.EpubFile
import io.legado.app.model.localBook.PdfFile
import io.legado.app.utils.BitmapUtils
import io.legado.app.utils.SvgUtils
import io.legado.app.utils.FileUtils
import io.legado.app.utils.SvgUtils
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
@ -81,6 +83,14 @@ object ImageProvider {
input.copyTo(output)
}
}
} else if (book.isPdf) {
PdfFile.getImage(book, src)?.use { input ->
val newFile = FileUtils.createFileIfNotExist(vFile.absolutePath)
@Suppress("BlockingMethodInNonBlockingContext")
FileOutputStream(newFile).use { output ->
input.copyTo(output)
}
}
} else {
BookHelp.saveImage(bookSource, book, src)
}

View File

@ -8,8 +8,7 @@ import android.graphics.Bitmap.Config
import android.graphics.BitmapFactory
import android.graphics.Color
import com.google.android.renderscript.Toolkit
import java.io.FileInputStream
import java.io.IOException
import java.io.*
import kotlin.math.*
@ -207,6 +206,18 @@ object BitmapUtils {
}
}
/**
* 将Bitmap转换成InputStream
*
* @param bitmap
* @return
*/
fun toInputStream(bitmap: Bitmap): InputStream {
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos)
return ByteArrayInputStream(bos.toByteArray()).also { bos.close() }
}
}
/**
@ -255,4 +266,5 @@ fun Bitmap.getMeanColor(): Int {
averagePixelGreen + 3,
averagePixelBlue + 3
)
}

View File

@ -112,6 +112,7 @@ object ConvertUtils {
return sb.toString()
}
}
val Int.hexString: String

View File

@ -6,6 +6,7 @@ import android.content.Intent
import android.net.Uri
import android.provider.Settings
import android.view.Display
import splitties.init.appCtx
import splitties.systemservices.displayManager
import splitties.systemservices.powerManager
@ -35,4 +36,18 @@ object SystemUtils {
it.state != Display.STATE_OFF
}
}
/**
* 屏幕像素宽度
*/
val screenWidthPx by lazy {
appCtx.resources.displayMetrics.widthPixels
}
/**
* 屏幕像素高度
*/
val screenHeightPx by lazy {
appCtx.resources.displayMetrics.heightPixels
}
}

View File

@ -9,6 +9,8 @@ buildscript {
exoplayer_version = '2.18.2'
splitties_version = '3.0.0'
room_version = '2.4.3'
compile_sdk_version = 33
build_tool_version = '33.0.1'
}
}

View File

@ -3,7 +3,8 @@ plugins {
}
android {
compileSdk 33
compileSdk = compile_sdk_version
buildToolsVersion = build_tool_version
namespace 'me.ag2s.epublib'
defaultConfig {
minSdk 21
@ -21,8 +22,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
lint {
checkDependencies true