mirror of
https://github.com/gedoor/legado.git
synced 2025-08-10 00:52:30 +00:00
Merge branch 'gedoor:master' into master
This commit is contained in:
@@ -21,8 +21,8 @@ import io.legado.app.model.localBook.EpubFile
|
||||
import io.legado.app.model.localBook.LocalBook
|
||||
import io.legado.app.model.localBook.UmdFile
|
||||
import io.legado.app.model.webBook.WebBook
|
||||
import io.legado.app.utils.*
|
||||
import io.legado.app.ui.book.read.page.provider.ImageProvider
|
||||
import io.legado.app.utils.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import splitties.init.appCtx
|
||||
@@ -87,9 +87,11 @@ object BookController {
|
||||
this.bookSource = appDb.bookSourceDao.getBookSource(book.origin)
|
||||
}
|
||||
this.bookUrl = bookUrl
|
||||
return ImageProvider.getImage(book, src, bookSource, width, width)?.let {
|
||||
returnData.setData(it)
|
||||
} ?: returnData.setErrorMsg("图片加载失败或不存在")
|
||||
val bitmap = runBlocking {
|
||||
ImageProvider.cacheImage(book, src, bookSource)
|
||||
ImageProvider.getImage(book, src, width, width)
|
||||
}
|
||||
return returnData.setData(bitmap)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,7 @@ object AppLog {
|
||||
}
|
||||
|
||||
fun putDebug(message: String?, throwable: Throwable? = null) {
|
||||
if (AppConfig.recordLog) {
|
||||
if (AppConfig.recordLog || BuildConfig.DEBUG) {
|
||||
put(message, throwable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,14 @@ data class Book(
|
||||
}
|
||||
|
||||
fun getUseReplaceRule(): Boolean {
|
||||
return config.useReplaceRule ?: AppConfig.replaceEnableDefault
|
||||
val useReplaceRule = config.useReplaceRule
|
||||
if (useReplaceRule != null) {
|
||||
return useReplaceRule
|
||||
}
|
||||
if (type == BookType.image) {
|
||||
return false
|
||||
}
|
||||
return AppConfig.replaceEnableDefault
|
||||
}
|
||||
|
||||
fun setReSegment(reSegment: Boolean) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.legado.app.help
|
||||
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.constant.AppPattern
|
||||
import io.legado.app.constant.EventBus
|
||||
import io.legado.app.data.appDb
|
||||
@@ -126,7 +127,7 @@ object BookHelp {
|
||||
).writeBytes(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printOnDebug()
|
||||
AppLog.putDebug("${src}下载错误", e)
|
||||
} finally {
|
||||
downloadImages.remove(src)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.legado.app.help.http.cronet
|
||||
|
||||
import io.legado.app.help.http.okHttpClient
|
||||
import io.legado.app.utils.DebugLog
|
||||
import io.legado.app.utils.rethrowAsIOException
|
||||
import io.legado.app.utils.asIOException
|
||||
import okhttp3.*
|
||||
import okhttp3.EventListener
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
@@ -27,12 +27,24 @@ abstract class AbsCallBack(
|
||||
|
||||
var mResponse: Response
|
||||
|
||||
var mException: IOException? = null
|
||||
private var followCount = 0
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun waitForDone(urlRequest: UrlRequest): Response
|
||||
|
||||
/**
|
||||
* 当发生错误时,通知子类终止阻塞抛出错误
|
||||
* @param error
|
||||
*/
|
||||
abstract fun onError(error: IOException)
|
||||
|
||||
/**
|
||||
* 请求成功后,通知子类结束阻塞,返回response
|
||||
* @param response
|
||||
*/
|
||||
abstract fun onSuccess(response: Response)
|
||||
|
||||
|
||||
override fun onRedirectReceived(
|
||||
request: UrlRequest,
|
||||
@@ -41,10 +53,13 @@ abstract class AbsCallBack(
|
||||
) {
|
||||
if (followCount > MAX_FOLLOW_COUNT) {
|
||||
request.cancel()
|
||||
mException = IOException("Too many redirect")
|
||||
onError(IOException("Too many redirect"))
|
||||
return
|
||||
}
|
||||
if (mCall.isCanceled()) {
|
||||
mException = IOException("Request Canceled")
|
||||
onError(IOException("Request Canceled"))
|
||||
request.cancel()
|
||||
return
|
||||
}
|
||||
followCount += 1
|
||||
val client = okHttpClient
|
||||
@@ -55,7 +70,7 @@ abstract class AbsCallBack(
|
||||
} else if (okHttpClient.followRedirects) {
|
||||
request.followRedirect()
|
||||
} else {
|
||||
mException = IOException("Too many redirect")
|
||||
onError(IOException("Too many redirect"))
|
||||
request.cancel()
|
||||
}
|
||||
}
|
||||
@@ -73,7 +88,7 @@ abstract class AbsCallBack(
|
||||
}
|
||||
|
||||
|
||||
@Throws(Exception::class)
|
||||
@Throws(IOException::class)
|
||||
override fun onReadCompleted(
|
||||
request: UrlRequest,
|
||||
info: UrlResponseInfo,
|
||||
@@ -83,7 +98,7 @@ abstract class AbsCallBack(
|
||||
|
||||
if (mCall.isCanceled()) {
|
||||
request.cancel()
|
||||
mException = IOException("Request Canceled")
|
||||
onError(IOException("Request Canceled"))
|
||||
}
|
||||
|
||||
byteBuffer.flip()
|
||||
@@ -91,9 +106,9 @@ abstract class AbsCallBack(
|
||||
try {
|
||||
buffer.write(byteBuffer)
|
||||
} catch (e: IOException) {
|
||||
DebugLog.i(javaClass.name, "IOException during ByteBuffer read. Details: ", e)
|
||||
mException = IOException("IOException during ByteBuffer read. Details:", e)
|
||||
throw e
|
||||
DebugLog.e(javaClass.name, "IOException during ByteBuffer read. Details: ", e)
|
||||
onError(IOException("IOException during ByteBuffer read. Details:", e))
|
||||
return
|
||||
}
|
||||
byteBuffer.clear()
|
||||
request.read(byteBuffer)
|
||||
@@ -108,7 +123,8 @@ abstract class AbsCallBack(
|
||||
buffer.asResponseBody(contentType)
|
||||
val newRequest = originalRequest.newBuilder().url(info.url).build()
|
||||
this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build()
|
||||
DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}")
|
||||
onSuccess(this.mResponse)
|
||||
//DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}")
|
||||
|
||||
eventListener?.callEnd(mCall)
|
||||
if (responseCallback != null) {
|
||||
@@ -123,8 +139,8 @@ abstract class AbsCallBack(
|
||||
|
||||
//UrlResponseInfo可能为null
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||
DebugLog.i(javaClass.name, error.message.toString())
|
||||
mException = error.rethrowAsIOException()
|
||||
DebugLog.e(javaClass.name, error.message.toString())
|
||||
onError(error.asIOException())
|
||||
this.eventListener?.callFailed(mCall, error)
|
||||
responseCallback?.onFailure(mCall, error)
|
||||
}
|
||||
@@ -132,7 +148,7 @@ abstract class AbsCallBack(
|
||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||
super.onCanceled(request, info)
|
||||
this.eventListener?.callEnd(mCall)
|
||||
mException = IOException("Cronet Request Canceled")
|
||||
onError(IOException("Cronet Request Canceled"))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.legado.app.help.http.cronet
|
||||
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.http.okHttpClient
|
||||
import io.legado.app.utils.DebugLog
|
||||
import okhttp3.Headers
|
||||
import okhttp3.MediaType
|
||||
@@ -13,11 +14,8 @@ import org.chromium.net.UploadDataProviders
|
||||
import org.chromium.net.UrlRequest
|
||||
import splitties.init.appCtx
|
||||
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
val executor: ExecutorService by lazy { Executors.newCachedThreadPool() }
|
||||
//val executor: ExecutorService by lazy { Executors.newCachedThreadPool() }
|
||||
|
||||
val cronetEngine: ExperimentalCronetEngine? by lazy {
|
||||
if (!AppConfig.isGooglePlay) {
|
||||
@@ -48,7 +46,11 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {
|
||||
val url = request.url.toString()
|
||||
val headers: Headers = request.headers
|
||||
val requestBody = request.body
|
||||
return cronetEngine?.newUrlRequestBuilder(url, callback, executor)?.apply {
|
||||
return cronetEngine?.newUrlRequestBuilder(
|
||||
url,
|
||||
callback,
|
||||
okHttpClient.dispatcher.executorService
|
||||
)?.apply {
|
||||
setHttpMethod(request.method)//设置
|
||||
allowDirectExecutor()
|
||||
headers.forEachIndexed { index, _ ->
|
||||
@@ -65,7 +67,7 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {
|
||||
requestBody.writeTo(buffer)
|
||||
setUploadDataProvider(
|
||||
UploadDataProviders.create(buffer.readByteArray()),
|
||||
executor
|
||||
okHttpClient.dispatcher.executorService
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
|
||||
OldCallback(request, call)
|
||||
}
|
||||
buildRequest(request, callBack)?.let {
|
||||
it.start()
|
||||
return callBack.waitForDone(it)
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -268,7 +268,8 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
|
||||
return
|
||||
}
|
||||
download = true
|
||||
executor.execute {
|
||||
|
||||
Coroutine.async {
|
||||
val result = downloadFileIfNotExist(url, downloadTempFile)
|
||||
DebugLog.d(javaClass.simpleName, "download result:$result")
|
||||
//文件md5再次校验
|
||||
@@ -279,7 +280,7 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
|
||||
downloadTempFile.deleteOnExit()
|
||||
}
|
||||
download = false
|
||||
return@execute
|
||||
return@async
|
||||
}
|
||||
DebugLog.d(javaClass.simpleName, "download success, copy to $destSuccessFile")
|
||||
//下载成功拷贝文件
|
||||
@@ -289,6 +290,9 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
|
||||
@Suppress("SameParameterValue")
|
||||
deleteHistoryFile(parentFile!!, null)
|
||||
}
|
||||
// executor.execute {
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,11 +6,8 @@ import androidx.annotation.RequiresApi
|
||||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.chromium.net.CronetException
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -21,45 +18,29 @@ class NewCallBack(originalRequest: Request, mCall: Call) : AbsCallBack(originalR
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun waitForDone(urlRequest: UrlRequest): Response {
|
||||
return responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS)
|
||||
}
|
||||
|
||||
override fun onRedirectReceived(
|
||||
request: UrlRequest,
|
||||
info: UrlResponseInfo,
|
||||
newLocationUrl: String
|
||||
) {
|
||||
super.onRedirectReceived(request, info, newLocationUrl)
|
||||
if (mException != null) {
|
||||
responseFuture.completeExceptionally(mException)
|
||||
urlRequest.start()
|
||||
return if (mCall.timeout().timeoutNanos() > 0) {
|
||||
responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS)
|
||||
} else {
|
||||
return responseFuture.get()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onReadCompleted(
|
||||
request: UrlRequest,
|
||||
info: UrlResponseInfo,
|
||||
byteBuffer: ByteBuffer
|
||||
) {
|
||||
super.onReadCompleted(request, info, byteBuffer)
|
||||
if (mException != null) {
|
||||
responseFuture.completeExceptionally(mException)
|
||||
}
|
||||
/**
|
||||
* 当发生错误时,通知子类终止阻塞抛出错误
|
||||
* @param error
|
||||
*/
|
||||
override fun onError(error: IOException) {
|
||||
responseFuture.completeExceptionally(error)
|
||||
}
|
||||
|
||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
||||
super.onSucceeded(request, info)
|
||||
responseFuture.complete(mResponse)
|
||||
}
|
||||
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||
super.onFailed(request, info, error)
|
||||
responseFuture.completeExceptionally(mException)
|
||||
}
|
||||
|
||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||
super.onCanceled(request, info)
|
||||
responseFuture.completeExceptionally(mException)
|
||||
/**
|
||||
* 请求成功后,通知子类结束阻塞,返回response
|
||||
* @param response
|
||||
*/
|
||||
override fun onSuccess(response: Response) {
|
||||
responseFuture.complete(response)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,20 +4,19 @@ import android.os.ConditionVariable
|
||||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.chromium.net.CronetException
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalRequest, mCall) {
|
||||
|
||||
private val mResponseCondition = ConditionVariable()
|
||||
private var mException: IOException? = null
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun waitForDone(urlRequest: UrlRequest): Response {
|
||||
//获取okhttp call的完整请求的超时时间
|
||||
val timeOutMs: Long = mCall.timeout().timeoutNanos() / 1000000
|
||||
urlRequest.start()
|
||||
if (timeOutMs > 0) {
|
||||
mResponseCondition.block(timeOutMs)
|
||||
} else {
|
||||
@@ -32,35 +31,25 @@ class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalR
|
||||
if (mException != null) {
|
||||
throw mException as IOException
|
||||
}
|
||||
return this.mResponse
|
||||
return mResponse
|
||||
}
|
||||
|
||||
|
||||
override fun onReadCompleted(
|
||||
request: UrlRequest,
|
||||
info: UrlResponseInfo,
|
||||
byteBuffer: ByteBuffer
|
||||
) {
|
||||
super.onReadCompleted(request, info, byteBuffer)
|
||||
if (mException != null) {
|
||||
mResponseCondition.open()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
||||
super.onSucceeded(request, info)
|
||||
/**
|
||||
* 当发生错误时,通知子类终止阻塞抛出错误
|
||||
* @param error
|
||||
*/
|
||||
override fun onError(error: IOException) {
|
||||
mException = error
|
||||
mResponseCondition.open()
|
||||
}
|
||||
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||
super.onFailed(request, info, error)
|
||||
/**
|
||||
* 请求成功后,通知子类结束阻塞,返回response
|
||||
* @param response
|
||||
*/
|
||||
override fun onSuccess(response: Response) {
|
||||
mResponseCondition.open()
|
||||
}
|
||||
|
||||
|
||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||
super.onCanceled(request, info)
|
||||
mResponseCondition.open()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import android.view.View
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseDialogFragment
|
||||
import io.legado.app.databinding.DialogPhotoViewBinding
|
||||
import io.legado.app.help.BookHelp
|
||||
import io.legado.app.help.glide.ImageLoader
|
||||
import io.legado.app.model.BookCover
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.ui.book.read.page.provider.ImageProvider
|
||||
import io.legado.app.utils.setLayout
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
|
||||
@@ -40,17 +41,12 @@ class PhotoDialog() : BaseDialogFragment(R.layout.dialog_photo_view) {
|
||||
arguments?.let {
|
||||
val path = it.getString("path")
|
||||
if (path.isNullOrEmpty()) {
|
||||
val chapterIndex = it.getInt("chapterIndex")
|
||||
val src = it.getString("src")
|
||||
ReadBook.book?.let { book ->
|
||||
src?.let {
|
||||
execute {
|
||||
ImageProvider.getImage(book, src, ReadBook.bookSource)
|
||||
}.onSuccess { bitmap ->
|
||||
if (bitmap != null) {
|
||||
binding.photoView.setImageBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
it.getString("src")?.let { src ->
|
||||
val file = BookHelp.getImage(book, src)
|
||||
ImageLoader.load(requireContext(), file)
|
||||
.error(R.drawable.image_loading_error)
|
||||
.into(binding.photoView)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -82,43 +82,36 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
private fun drawPage(canvas: Canvas) {
|
||||
var relativeOffset = relativeOffset(0)
|
||||
textPage.textLines.forEach { textLine ->
|
||||
draw(canvas, textLine, relativeOffset)
|
||||
draw(canvas, textPage, textLine, relativeOffset)
|
||||
}
|
||||
if (!callBack.isScroll) return
|
||||
//滚动翻页
|
||||
if (!pageFactory.hasNext()) return
|
||||
val nextPage = relativePage(1)
|
||||
val textPage1 = relativePage(1)
|
||||
relativeOffset = relativeOffset(1)
|
||||
nextPage.textLines.forEach { textLine ->
|
||||
draw(canvas, textLine, relativeOffset)
|
||||
textPage1.textLines.forEach { textLine ->
|
||||
draw(canvas, textPage1, textLine, relativeOffset)
|
||||
}
|
||||
if (!pageFactory.hasNextPlus()) return
|
||||
relativeOffset = relativeOffset(2)
|
||||
if (relativeOffset < ChapterProvider.visibleHeight) {
|
||||
relativePage(2).textLines.forEach { textLine ->
|
||||
draw(canvas, textLine, relativeOffset)
|
||||
val textPage2 = relativePage(2)
|
||||
textPage2.textLines.forEach { textLine ->
|
||||
draw(canvas, textPage2, textLine, relativeOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun draw(
|
||||
canvas: Canvas,
|
||||
textPage: TextPage,
|
||||
textLine: TextLine,
|
||||
relativeOffset: Float,
|
||||
) {
|
||||
val lineTop = textLine.lineTop + relativeOffset
|
||||
val lineBase = textLine.lineBase + relativeOffset
|
||||
val lineBottom = textLine.lineBottom + relativeOffset
|
||||
drawChars(
|
||||
canvas,
|
||||
textLine.textChars,
|
||||
lineTop,
|
||||
lineBase,
|
||||
lineBottom,
|
||||
textLine.isTitle,
|
||||
textLine.isReadAloud,
|
||||
textLine.isImage
|
||||
)
|
||||
drawChars(canvas, textPage, textLine, lineTop, lineBase, lineBottom)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,23 +119,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
*/
|
||||
private fun drawChars(
|
||||
canvas: Canvas,
|
||||
textChars: List<TextChar>,
|
||||
textPage: TextPage,
|
||||
textLine: TextLine,
|
||||
lineTop: Float,
|
||||
lineBase: Float,
|
||||
lineBottom: Float,
|
||||
isTitle: Boolean,
|
||||
isReadAloud: Boolean,
|
||||
isImageLine: Boolean
|
||||
) {
|
||||
val textPaint = if (isTitle) {
|
||||
val textPaint = if (textLine.isTitle) {
|
||||
ChapterProvider.titlePaint
|
||||
} else {
|
||||
ChapterProvider.contentPaint
|
||||
}
|
||||
val textColor = if (isReadAloud) context.accentColor else ReadBookConfig.textColor
|
||||
textChars.forEach {
|
||||
val textColor = if (textLine.isReadAloud) context.accentColor else ReadBookConfig.textColor
|
||||
textLine.textChars.forEach {
|
||||
if (it.isImage) {
|
||||
drawImage(canvas, it, lineTop, lineBottom, isImageLine)
|
||||
drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
|
||||
} else {
|
||||
textPaint.color = textColor
|
||||
if (it.isSearchResult) {
|
||||
@@ -159,34 +150,34 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
/**
|
||||
* 绘制图片
|
||||
*/
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun drawImage(
|
||||
canvas: Canvas,
|
||||
textPage: TextPage,
|
||||
textLine: TextLine,
|
||||
textChar: TextChar,
|
||||
lineTop: Float,
|
||||
lineBottom: Float,
|
||||
isImageLine: Boolean
|
||||
lineBottom: Float
|
||||
) {
|
||||
val book = ReadBook.book ?: return
|
||||
ImageProvider.getImage(
|
||||
val bitmap = ImageProvider.getImage(
|
||||
book,
|
||||
textChar.charData,
|
||||
ReadBook.bookSource,
|
||||
(textChar.end - textChar.start).toInt(),
|
||||
(lineBottom - lineTop).toInt()
|
||||
)?.let { bitmap ->
|
||||
val rectF = if (isImageLine) {
|
||||
RectF(textChar.start, lineTop, textChar.end, lineBottom)
|
||||
} else {
|
||||
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
|
||||
val h = (textChar.end - textChar.start) / bitmap.width * bitmap.height
|
||||
val div = (lineBottom - lineTop - h) / 2
|
||||
RectF(textChar.start, lineTop + div, textChar.end, lineBottom - div)
|
||||
}
|
||||
kotlin.runCatching {
|
||||
canvas.drawBitmap(bitmap, null, rectF, null)
|
||||
}.onFailure { e ->
|
||||
context.toastOnUi(e.localizedMessage)
|
||||
}
|
||||
)
|
||||
val rectF = if (textLine.isImage) {
|
||||
RectF(textChar.start, lineTop, textChar.end, lineBottom)
|
||||
} else {
|
||||
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
|
||||
val h = (textChar.end - textChar.start) / bitmap.width * bitmap.height
|
||||
val div = (lineBottom - lineTop - h) / 2
|
||||
RectF(textChar.start, lineTop + div, textChar.end, lineBottom - div)
|
||||
}
|
||||
kotlin.runCatching {
|
||||
canvas.drawBitmap(bitmap, null, rectF, null)
|
||||
}.onFailure { e ->
|
||||
context.toastOnUi(e.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -208,22 +208,23 @@ object ChapterProvider {
|
||||
imageStyle: String?,
|
||||
): Float {
|
||||
var durY = y
|
||||
ImageProvider.getImageSize(book, src, ReadBook.bookSource).let {
|
||||
val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)
|
||||
if (size.width > 0 && size.height > 0) {
|
||||
if (durY > visibleHeight) {
|
||||
textPages.last().height = durY
|
||||
textPages.add(TextPage())
|
||||
durY = 0f
|
||||
}
|
||||
var height = it.height
|
||||
var width = it.width
|
||||
var height = size.height
|
||||
var width = size.width
|
||||
when (imageStyle?.toUpperCase(Locale.ROOT)) {
|
||||
Book.imgStyleFull -> {
|
||||
width = visibleWidth
|
||||
height = it.height * visibleWidth / it.width
|
||||
height = size.height * visibleWidth / size.width
|
||||
}
|
||||
else -> {
|
||||
if (it.width > visibleWidth) {
|
||||
height = it.height * visibleWidth / it.width
|
||||
if (size.width > visibleWidth) {
|
||||
height = size.height * visibleWidth / size.width
|
||||
width = visibleWidth
|
||||
}
|
||||
if (height > visibleHeight) {
|
||||
|
||||
@@ -9,16 +9,13 @@ import io.legado.app.data.entities.Book
|
||||
import io.legado.app.data.entities.BookSource
|
||||
import io.legado.app.help.BookHelp
|
||||
import io.legado.app.help.coroutine.Coroutine
|
||||
import io.legado.app.help.glide.ImageLoader
|
||||
import io.legado.app.model.localBook.EpubFile
|
||||
import io.legado.app.utils.BitmapUtils
|
||||
import io.legado.app.utils.FileUtils
|
||||
import io.legado.app.utils.isXml
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
object ImageProvider {
|
||||
|
||||
@@ -26,7 +23,7 @@ object ImageProvider {
|
||||
BitmapFactory.decodeResource(appCtx.resources, R.drawable.image_loading_error)
|
||||
}
|
||||
|
||||
private suspend fun cacheImage(
|
||||
suspend fun cacheImage(
|
||||
book: Book,
|
||||
src: String,
|
||||
bookSource: BookSource?
|
||||
@@ -54,59 +51,26 @@ object ImageProvider {
|
||||
bookSource: BookSource?
|
||||
): Size {
|
||||
val file = cacheImage(book, src, bookSource)
|
||||
return suspendCancellableCoroutine { block ->
|
||||
kotlin.runCatching {
|
||||
ImageLoader.loadBitmap(appCtx, file.absolutePath).submit()
|
||||
.getSize { width, height ->
|
||||
block.resume(Size(width, height))
|
||||
}
|
||||
}.onFailure {
|
||||
block.resume(Size(errorBitmap.width, errorBitmap.height))
|
||||
}
|
||||
}
|
||||
val op = BitmapFactory.Options()
|
||||
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
|
||||
op.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFile(file.absolutePath, op)
|
||||
return Size(op.outWidth, op.outHeight)
|
||||
}
|
||||
|
||||
fun getImage(
|
||||
book: Book,
|
||||
src: String,
|
||||
bookSource: BookSource?,
|
||||
width: Int,
|
||||
height: Int
|
||||
): Bitmap? {
|
||||
val vFile = runBlocking {
|
||||
cacheImage(book, src, bookSource)
|
||||
}
|
||||
): Bitmap {
|
||||
val vFile = BookHelp.getImage(book, src)
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
return try {
|
||||
ImageLoader.loadBitmap(appCtx, vFile.absolutePath)
|
||||
.submit(width, height)
|
||||
.get()
|
||||
BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
|
||||
} catch (e: Exception) {
|
||||
Coroutine.async {
|
||||
putDebug("${vFile.absolutePath} 解码失败\n${e.toString()}", e)
|
||||
if (FileUtils.readText(vFile.absolutePath).isXml()) {
|
||||
putDebug("${vFile.absolutePath}为xml,自动删除")
|
||||
vFile.delete()
|
||||
}
|
||||
}
|
||||
errorBitmap
|
||||
}
|
||||
}
|
||||
|
||||
fun getImage(
|
||||
book: Book,
|
||||
src: String,
|
||||
bookSource: BookSource?
|
||||
): Bitmap? {
|
||||
val vFile = runBlocking {
|
||||
cacheImage(book, src, bookSource)
|
||||
}
|
||||
return try {
|
||||
ImageLoader.loadBitmap(appCtx, vFile.absolutePath)
|
||||
.submit(ChapterProvider.visibleWidth, ChapterProvider.visibleHeight)
|
||||
.get()
|
||||
} catch (e: Exception) {
|
||||
Coroutine.async {
|
||||
putDebug("${vFile.absolutePath} 解码失败\n${e.toString()}", e)
|
||||
putDebug("${vFile.absolutePath} 解码失败\n$e", e)
|
||||
if (FileUtils.readText(vFile.absolutePath).isXml()) {
|
||||
putDebug("${vFile.absolutePath}为xml,自动删除")
|
||||
vFile.delete()
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.graphics.*
|
||||
import android.graphics.Bitmap.Config
|
||||
import android.view.View
|
||||
import com.google.android.renderscript.Toolkit
|
||||
import java.io.FileInputStream
|
||||
import java.io.IOException
|
||||
import kotlin.math.*
|
||||
|
||||
@@ -27,10 +26,9 @@ object BitmapUtils {
|
||||
@Throws(IOException::class)
|
||||
fun decodeBitmap(path: String, width: Int, height: Int): Bitmap {
|
||||
val op = BitmapFactory.Options()
|
||||
val ips = FileInputStream(path)
|
||||
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
|
||||
op.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFileDescriptor(ips.fd, null, op)
|
||||
BitmapFactory.decodeFile(path, op)
|
||||
//获取比例大小
|
||||
val wRatio = ceil((op.outWidth / width).toDouble()).toInt()
|
||||
val hRatio = ceil((op.outHeight / height).toDouble()).toInt()
|
||||
@@ -43,7 +41,7 @@ object BitmapUtils {
|
||||
}
|
||||
}
|
||||
op.inJustDecodeBounds = false
|
||||
return BitmapFactory.decodeFileDescriptor(ips.fd, null, op)
|
||||
return BitmapFactory.decodeFile(path, op)
|
||||
}
|
||||
|
||||
/** 从path中获取Bitmap图片
|
||||
@@ -53,12 +51,11 @@ object BitmapUtils {
|
||||
@Throws(IOException::class)
|
||||
fun decodeBitmap(path: String): Bitmap {
|
||||
val opts = BitmapFactory.Options()
|
||||
val ips = FileInputStream(path)
|
||||
opts.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFileDescriptor(ips.fd, null, opts)
|
||||
BitmapFactory.decodeFile(path, opts)
|
||||
opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128)
|
||||
opts.inJustDecodeBounds = false
|
||||
return BitmapFactory.decodeFileDescriptor(ips.fd, null, opts)
|
||||
return BitmapFactory.decodeFile(path, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,8 +12,8 @@ val Throwable.msg: String
|
||||
}
|
||||
}
|
||||
|
||||
fun Throwable.rethrowAsIOException(): IOException {
|
||||
fun Throwable.asIOException(): IOException {
|
||||
val newException = IOException(this.message)
|
||||
newException.initCause(this)
|
||||
throw newException
|
||||
return newException
|
||||
}
|
||||
Reference in New Issue
Block a user