mirror of
https://github.com/gedoor/legado.git
synced 2024-07-04 23:36:56 +08:00
支持PDF格式
This commit is contained in:
parent
17a828cf40
commit
342a08400f
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
||||
/**
|
||||
* 所有标点
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
//已有书籍说明是更新,删除原有目录
|
||||
|
|
225
app/src/main/java/io/legado/app/model/localBook/PdfFile.kt
Normal file
225
app/src/main/java/io/legado/app/model/localBook/PdfFile.kt
Normal 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()
|
||||
}
|
||||
}
|
|
@ -4,4 +4,5 @@
|
|||
* LocalBook.kt 导入解析总入口
|
||||
* TextFile.kt 解析txt
|
||||
* EpubFile.kt 解析epub
|
||||
* PdfFile.kt 解析pdf 纯图片形式
|
||||
* UmdFile.kt 解析umd
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@ object ConvertUtils {
|
|||
return sb.toString()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
val Int.hexString: String
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user