epub 显示图片,未完善

pull/274/head
gedoor 4 years ago
parent b2b8d45911
commit 6f936c377c
  1. 4
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  2. 13
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  3. 7
      app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt
  4. 46
      app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt
  5. 17
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  6. 51
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  7. 76
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  8. 46
      app/src/main/java/io/legado/app/ui/book/read/page/ImageProvider.kt
  9. 1
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
  10. 2
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
  11. 20
      app/src/main/java/io/legado/app/utils/MD5Utils.kt

@ -41,11 +41,11 @@ interface JsExtensions {
return EncoderUtils.base64Encode(str, flags)
}
fun md5Encode(str: String): String? {
fun md5Encode(str: String): String {
return MD5Utils.md5Encode(str)
}
fun md5Encode16(str: String): String? {
fun md5Encode16(str: String): String {
return MD5Utils.md5Encode16(str)
}

@ -49,6 +49,17 @@ object HttpHelper {
return null
}
fun getBytes(url: String): ByteArray? {
NetworkUtils.getBaseUrl(url)?.let { baseUrl ->
return getByteRetrofit(baseUrl)
.create(HttpGetApi::class.java)
.getMapByte(url, mapOf(), mapOf(Pair(AppConst.UA_NAME, AppConst.userAgent)))
.execute()
.body()
}
return null
}
suspend fun simpleGetAsync(url: String, encode: String? = null): String? {
NetworkUtils.getBaseUrl(url)?.let { baseUrl ->
val response = getApiService<HttpGetApi>(baseUrl, encode)
@ -58,7 +69,7 @@ object HttpHelper {
return null
}
suspend fun simpleGetByteAsync(url: String): ByteArray? {
suspend fun simpleGetBytesAsync(url: String): ByteArray? {
NetworkUtils.getBaseUrl(url)?.let { baseUrl ->
return getByteRetrofit(baseUrl)
.create(HttpGetApi::class.java)

@ -39,6 +39,13 @@ interface HttpGetApi {
@HeaderMap headers: Map<String, String>
): Call<String>
@GET
fun getMapByte(
@Url url: String,
@QueryMap(encoded = true) queryMap: Map<String, String>,
@HeaderMap headers: Map<String, String>
): Call<ByteArray>
@GET
suspend fun getMapByteAsync(
@Url url: String,

@ -6,7 +6,10 @@ import android.net.Uri
import android.text.TextUtils
import io.legado.app.App
import io.legado.app.data.entities.BookChapter
import io.legado.app.utils.*
import io.legado.app.utils.FileUtils
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.externalFilesDir
import io.legado.app.utils.isContentPath
import nl.siegmann.epublib.domain.Book
import nl.siegmann.epublib.domain.TOCReference
import nl.siegmann.epublib.epub.EpubReader
@ -14,6 +17,7 @@ import org.jsoup.Jsoup
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.nio.charset.Charset
import java.util.*
@ -21,7 +25,6 @@ class EPUBFile(val book: io.legado.app.data.entities.Book) {
companion object {
private var eFile: EPUBFile? = null
private val coverDir = App.INSTANCE.externalFilesDir
@Synchronized
private fun getEFile(book: io.legado.app.data.entities.Book): EPUBFile {
@ -41,6 +44,14 @@ class EPUBFile(val book: io.legado.app.data.entities.Book) {
fun getContent(book: io.legado.app.data.entities.Book, chapter: BookChapter): String? {
return getEFile(book).getContent(chapter)
}
@Synchronized
fun getImage(
book: io.legado.app.data.entities.Book,
href: String
): InputStream? {
return getEFile(book).getImage(href)
}
}
private var epubBook: Book? = null
@ -64,13 +75,12 @@ class EPUBFile(val book: io.legado.app.data.entities.Book) {
)
}
if (!File(book.coverUrl!!).exists()) {
epubBook!!.coverImage?.inputStream?.let {
epubBook!!.coverImage?.inputStream?.use {
val cover = BitmapFactory.decodeStream(it)
val out = FileOutputStream(FileUtils.createFileIfNotExist(book.coverUrl!!))
cover.compress(Bitmap.CompressFormat.JPEG, 90, out)
out.flush()
out.close()
it.close()
}
}
} catch (e: Exception) {
@ -81,30 +91,20 @@ class EPUBFile(val book: io.legado.app.data.entities.Book) {
private fun getContent(chapter: BookChapter): String? {
epubBook?.let { eBook ->
val resource = eBook.resources.getByHref(chapter.url)
val content = StringBuilder()
val doc = Jsoup.parse(String(resource.data, mCharset))
val elements = doc.body().allElements
for (element in elements) {
val contentEs = element.textNodes()
for (i in contentEs.indices) {
val text = contentEs[i].text().trim { it <= ' ' }.htmlFormat()
if (elements.size > 1) {
if (text.isNotEmpty()) {
if (content.isNotEmpty()) {
content.append("\r\n")
}
content.append("\u3000\u3000").append(text)
}
} else {
content.append(text)
}
}
}
return content.toString()
val elements = doc.body().children()
elements.select("script, style").remove()
return elements.outerHtml()
.replace("</?(?:div|p|b|br|hr|h\\d|article|dd|dl)[^>]*>".toRegex(), "\n")
}
return null
}
private fun getImage(href: String): InputStream? {
val abHref = href.replace("../", "")
return epubBook?.resources?.getByHref(abHref)?.inputStream
}
private fun getChapterList(): ArrayList<BookChapter> {
val chapterList = ArrayList<BookChapter>()
epubBook?.let { eBook ->

@ -211,7 +211,7 @@ object ReadBook {
Coroutine.async {
App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter ->
BookHelp.getContent(book, chapter)?.let {
contentLoadFinish(chapter, it, upContent, resetPageOffset)
contentLoadFinish(book, chapter, it, upContent, resetPageOffset)
removeLoading(chapter.index)
} ?: download(chapter, resetPageOffset = resetPageOffset)
} ?: removeLoading(index)
@ -247,6 +247,7 @@ object ReadBook {
?.onSuccess(Dispatchers.IO) { content ->
if (content.isEmpty()) {
contentLoadFinish(
book,
chapter,
App.INSTANCE.getString(R.string.content_empty),
resetPageOffset = resetPageOffset
@ -254,11 +255,12 @@ object ReadBook {
removeLoading(chapter.index)
} else {
BookHelp.saveContent(book, chapter, content)
contentLoadFinish(chapter, content, resetPageOffset = resetPageOffset)
contentLoadFinish(book, chapter, content, resetPageOffset = resetPageOffset)
removeLoading(chapter.index)
}
}?.onError {
contentLoadFinish(
book,
chapter,
it.localizedMessage ?: "未知错误",
resetPageOffset = resetPageOffset
@ -286,6 +288,7 @@ object ReadBook {
* 内容加载完成
*/
private fun contentLoadFinish(
book: Book,
chapter: BookChapter,
content: String,
upContent: Boolean = true,
@ -300,15 +303,15 @@ object ReadBook {
}
val contents = BookHelp.disposeContent(
chapter.title,
book!!.name,
book.name,
webBook?.bookSource?.bookSourceUrl,
content,
book!!.useReplaceRule
book.useReplaceRule
)
when (chapter.index) {
durChapterIndex -> {
curTextChapter =
ChapterProvider.getTextChapter(chapter, contents, chapterSize)
ChapterProvider.getTextChapter(book, chapter, contents, chapterSize)
if (upContent) callBack?.upContent(resetPageOffset = resetPageOffset)
callBack?.upView()
curPageChanged()
@ -316,12 +319,12 @@ object ReadBook {
}
durChapterIndex - 1 -> {
prevTextChapter =
ChapterProvider.getTextChapter(chapter, contents, chapterSize)
ChapterProvider.getTextChapter(book, chapter, contents, chapterSize)
if (upContent) callBack?.upContent(-1, resetPageOffset)
}
durChapterIndex + 1 -> {
nextTextChapter =
ChapterProvider.getTextChapter(chapter, contents, chapterSize)
ChapterProvider.getTextChapter(book, chapter, contents, chapterSize)
if (upContent) callBack?.upContent(1, resetPageOffset)
}
}

@ -8,6 +8,7 @@ import android.text.TextPaint
import android.text.TextUtils
import io.legado.app.App
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
@ -15,6 +16,7 @@ import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextLine
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.*
import java.util.regex.Pattern
@Suppress("DEPRECATION")
@ -34,6 +36,7 @@ object ChapterProvider {
var typeface: Typeface = Typeface.SANS_SERIF
lateinit var titlePaint: TextPaint
lateinit var contentPaint: TextPaint
private val srcPattern = Pattern.compile("<img .*?src=\"(.*?)\".*?>", Pattern.CASE_INSENSITIVE)
init {
upStyle()
@ -43,6 +46,7 @@ object ChapterProvider {
* 获取拆分完的章节数据
*/
fun getTextChapter(
book: Book,
bookChapter: BookChapter,
contents: List<String>,
chapterSize: Int
@ -54,12 +58,27 @@ object ChapterProvider {
var durY = 0f
textPages.add(TextPage())
contents.forEachIndexed { index, text ->
val isTitle = index == 0
if (!(isTitle && ReadBookConfig.titleMode == 2)) {
durY = setTypeText(
text, durY, textPages, pageLines,
pageLengths, stringBuilder, isTitle
)
val matcher = srcPattern.matcher(text)
if (matcher.find()) {
var src = matcher.group(1)
if (!book.isEpub()) {
src = NetworkUtils.getAbsoluteURL(bookChapter.url, src)
}
src?.let {
durY = setTypeImage(
book, src, durY,
textPages, pageLines,
pageLengths, stringBuilder
)
}
} else {
val isTitle = index == 0
if (!(isTitle && ReadBookConfig.titleMode == 2)) {
durY = setTypeText(
text, durY, textPages, pageLines,
pageLengths, stringBuilder, isTitle
)
}
}
}
textPages.last().height = durY + 20.dp
@ -90,6 +109,26 @@ object ChapterProvider {
)
}
private fun setTypeImage(
book: Book,
src: String,
y: Float,
textPages: ArrayList<TextPage>,
pageLines: ArrayList<Int>,
pageLengths: ArrayList<Int>,
stringBuilder: StringBuilder
): Float {
var durY = y
ImageProvider.getImage(book, src)?.let {
val textLine = TextLine(text = src, isImage = true)
textLine.lineTop = durY
durY += it.height
textLine.lineBottom = durY
textPages.last().textLines.add(textLine)
}
return durY + paragraphSpacing / 10f
}
/**
* 排版文字
*/

@ -10,7 +10,9 @@ import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.accentColor
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.entities.TextChar
import io.legado.app.ui.book.read.page.entities.TextLine
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.activity
import io.legado.app.utils.getCompatColor
@ -78,18 +80,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
private fun drawPage(canvas: Canvas) {
var relativeOffset = relativeOffset(0)
textPage.textLines.forEach { textLine ->
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
)
draw(canvas, textLine, relativeOffset)
}
if (!ReadBookConfig.isScroll) return
//滚动翻页
@ -97,37 +88,38 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
val nextPage = relativePage(1)
relativeOffset = relativeOffset(1)
nextPage.textLines.forEach { textLine ->
val lineTop = textLine.lineTop + relativeOffset
val lineBase = textLine.lineBase + relativeOffset
val lineBottom = textLine.lineBottom + relativeOffset
draw(canvas, textLine, relativeOffset)
}
if (!pageFactory.hasNextPlus()) return
relativeOffset = relativeOffset(2)
if (relativeOffset < ChapterProvider.visibleHeight) {
relativePage(2).textLines.forEach { textLine ->
draw(canvas, textLine, relativeOffset)
}
}
}
private fun draw(
canvas: Canvas,
textLine: TextLine,
relativeOffset: Float
) {
val lineTop = textLine.lineTop + relativeOffset
val lineBase = textLine.lineBase + relativeOffset
val lineBottom = textLine.lineBottom + relativeOffset
if (textLine.isImage) {
drawImage(canvas, textLine, lineTop, lineBottom)
} else {
drawChars(
canvas,
textLine.textChars,
lineTop,
lineBase,
lineBottom,
textLine.isTitle,
textLine.isReadAloud
isTitle = textLine.isTitle,
isReadAloud = textLine.isReadAloud
)
}
if (!pageFactory.hasNextPlus()) return
relativeOffset = relativeOffset(2)
if (relativeOffset < ChapterProvider.visibleHeight) {
relativePage(2).textLines.forEach { textLine ->
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
)
}
}
}
/**
@ -153,6 +145,20 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
}
}
/**
* 绘制图片
*/
private fun drawImage(
canvas: Canvas,
textLine: TextLine,
lineTop: Float,
lineBottom: Float
) {
ImageProvider.getImage(ReadBook.book!!, textLine.text)?.let {
canvas.drawBitmap(it, ChapterProvider.paddingLeft.toFloat(), lineTop, null)
}
}
/**
* 滚动事件
*/

@ -0,0 +1,46 @@
package io.legado.app.ui.book.read.page
import android.graphics.Bitmap
import io.legado.app.App
import io.legado.app.data.entities.Book
import io.legado.app.help.http.HttpHelper
import io.legado.app.model.localBook.EPUBFile
import io.legado.app.utils.BitmapUtils
import io.legado.app.utils.FileUtils
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.externalFilesDir
import java.io.FileOutputStream
object ImageProvider {
fun getImage(book: Book, src: String): Bitmap? {
val vFile = FileUtils.getFile(
App.INSTANCE.externalFilesDir,
"${MD5Utils.md5Encode16(src)}.jpg",
"images", book.name
)
if (!vFile.exists()) {
if (book.isEpub()) {
EPUBFile.getImage(book, src).use {
val out = FileOutputStream(FileUtils.createFileIfNotExist(vFile.absolutePath))
it?.copyTo(out)
out.flush()
out.close()
}
} else {
HttpHelper.getBytes(src)
}
}
return try {
BitmapUtils.decodeBitmap(
vFile.absolutePath,
ChapterProvider.visibleWidth,
ChapterProvider.visibleHeight
)
} catch (e: Exception) {
null
}
}
}

@ -11,6 +11,7 @@ data class TextLine(
var lineBase: Float = 0f,
var lineBottom: Float = 0f,
val isTitle: Boolean = false,
val isImage: Boolean = false,
var isReadAloud: Boolean = false
) {

@ -131,7 +131,7 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
private suspend fun webData2bitmap(data: String): ByteArray? {
return if (URLUtil.isValidUrl(data)) {
HttpHelper.simpleGetByteAsync(data)
HttpHelper.simpleGetBytesAsync(data)
} else {
Base64.decode(data.split(",").toTypedArray()[1], Base64.DEFAULT)
}

@ -9,15 +9,15 @@ import java.security.NoSuchAlgorithmException
@Suppress("unused")
object MD5Utils {
fun md5Encode(str: String?): String? {
if (str == null) return null
var reStr: String? = null
fun md5Encode(str: String?): String {
if (str == null) return ""
var reStr = ""
try {
val md5:MessageDigest = MessageDigest.getInstance("MD5")
val bytes:ByteArray = md5.digest(str.toByteArray())
val stringBuffer:StringBuilder = StringBuilder()
val md5: MessageDigest = MessageDigest.getInstance("MD5")
val bytes: ByteArray = md5.digest(str.toByteArray())
val stringBuffer: StringBuilder = StringBuilder()
for (b in bytes) {
val bt:Int = b.toInt() and 0xff
val bt: Int = b.toInt() and 0xff
if (bt < 16) {
stringBuffer.append(0)
}
@ -31,11 +31,9 @@ object MD5Utils {
return reStr
}
fun md5Encode16(str: String): String? {
fun md5Encode16(str: String): String {
var reStr = md5Encode(str)
if (reStr != null) {
reStr = reStr.substring(8, 24)
}
reStr = reStr.substring(8, 24)
return reStr
}
}

Loading…
Cancel
Save