This commit is contained in:
Horis
2023-09-29 13:18:57 +08:00
parent 083293df39
commit 861636d9ed
5 changed files with 98 additions and 50 deletions
@@ -213,7 +213,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
get() = appCtx.getPrefBoolean(PreferKey.autoRefresh) get() = appCtx.getPrefBoolean(PreferKey.autoRefresh)
var enableReview: Boolean var enableReview: Boolean
get() = appCtx.getPrefBoolean(PreferKey.enableReview, false) && BuildConfig.DEBUG get() = BuildConfig.DEBUG && appCtx.getPrefBoolean(PreferKey.enableReview, false)
set(value) { set(value) {
appCtx.putPrefBoolean(PreferKey.enableReview, value) appCtx.putPrefBoolean(PreferKey.enableReview, value)
} }
@@ -129,14 +129,14 @@ object ImageProvider {
bookSource: BookSource? bookSource: BookSource?
): Size { ): Size {
val file = cacheImage(book, src, bookSource) val file = cacheImage(book, src, bookSource)
//svg size
val size = SvgUtils.getSize(file.absolutePath)
if (size != null) return size
val op = BitmapFactory.Options() val op = BitmapFactory.Options()
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
op.inJustDecodeBounds = true op.inJustDecodeBounds = true
BitmapFactory.decodeFile(file.absolutePath, op) BitmapFactory.decodeFile(file.absolutePath, op)
if (op.outWidth < 1 && op.outHeight < 1) { if (op.outWidth < 1 && op.outHeight < 1) {
//svg size
val size = SvgUtils.getSize(file.absolutePath)
if (size != null) return size
putDebug("ImageProvider: $src Unsupported image type") putDebug("ImageProvider: $src Unsupported image type")
//file.delete() 重复下载 //file.delete() 重复下载
return Size(errorBitmap.width, errorBitmap.height) return Size(errorBitmap.width, errorBitmap.height)
@@ -1,5 +1,6 @@
package io.legado.app.ui.book.read.page.entities package io.legado.app.ui.book.read.page.entities
import android.graphics.Paint.FontMetrics
import android.text.TextPaint import android.text.TextPaint
import androidx.annotation.Keep import androidx.annotation.Keep
import io.legado.app.ui.book.read.page.entities.column.BaseColumn import io.legado.app.ui.book.read.page.entities.column.BaseColumn
@@ -51,10 +52,10 @@ data class TextLine(
return textColumns.size return textColumns.size
} }
fun upTopBottom(durY: Float, textPaint: TextPaint) { fun upTopBottom(durY: Float, textHeight: Float, fontMetrics: FontMetrics) {
lineTop = ChapterProvider.paddingTop + durY lineTop = ChapterProvider.paddingTop + durY
lineBottom = lineTop + textPaint.textHeight lineBottom = lineTop + textHeight
lineBase = lineBottom - textPaint.fontMetrics.descent lineBase = lineBottom - fontMetrics.descent
} }
fun isTouch(x: Float, y: Float, relativeOffset: Float): Boolean { fun isTouch(x: Float, y: Float, relativeOffset: Float): Boolean {
@@ -8,7 +8,6 @@ import io.legado.app.help.config.ReadBookConfig
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.utils.textHeight
import splitties.init.appCtx import splitties.init.appCtx
import java.text.DecimalFormat import java.text.DecimalFormat
import kotlin.math.min import kotlin.math.min
@@ -76,7 +75,7 @@ data class TextPage(
val lastLine = textLines[leftLineSize - 1] val lastLine = textLines[leftLineSize - 1]
if (lastLine.isImage) return@run if (lastLine.isImage) return@run
val lastLineHeight = with(lastLine) { lineBottom - lineTop } val lastLineHeight = with(lastLine) { lineBottom - lineTop }
val pageHeight = lastLine.lineBottom + contentPaint.textHeight * lineSpacingExtra val pageHeight = lastLine.lineBottom + contentPaintTextHeight * lineSpacingExtra
if (visibleHeight - pageHeight >= lastLineHeight) return@run if (visibleHeight - pageHeight >= lastLineHeight) return@run
val surplus = (visibleBottom - lastLine.lineBottom) val surplus = (visibleBottom - lastLine.lineBottom)
if (surplus == 0f) return@run if (surplus == 0f) return@run
@@ -94,7 +93,7 @@ data class TextPage(
val lastLine = textLines.last() val lastLine = textLines.last()
if (lastLine.isImage) return@run if (lastLine.isImage) return@run
val lastLineHeight = with(lastLine) { lineBottom - lineTop } val lastLineHeight = with(lastLine) { lineBottom - lineTop }
val pageHeight = lastLine.lineBottom + contentPaint.textHeight * lineSpacingExtra val pageHeight = lastLine.lineBottom + contentPaintTextHeight * lineSpacingExtra
if (visibleHeight - pageHeight >= lastLineHeight) return@run if (visibleHeight - pageHeight >= lastLineHeight) return@run
val surplus = (visibleBottom - lastLine.lineBottom) val surplus = (visibleBottom - lastLine.lineBottom)
if (surplus == 0f) return@run if (surplus == 0f) return@run
@@ -1,6 +1,7 @@
package io.legado.app.ui.book.read.page.provider package io.legado.app.ui.book.read.page.provider
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Paint.FontMetrics
import android.graphics.Typeface import android.graphics.Typeface
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@@ -25,6 +26,7 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.utils.* import io.legado.app.utils.*
import splitties.init.appCtx import splitties.init.appCtx
import java.util.* import java.util.*
import kotlin.collections.ArrayList
/** /**
* 解析内容生成章节和页面 * 解析内容生成章节和页面
@@ -97,7 +99,13 @@ object ChapterProvider {
private var titlePaintTextHeight = 0f private var titlePaintTextHeight = 0f
@JvmStatic @JvmStatic
private var contentPaintTextHeight = 0f var contentPaintTextHeight = 0f
@JvmStatic
private var titlePaintFontMetrics = FontMetrics()
@JvmStatic
var contentPaintFontMetrics = FontMetrics()
@JvmStatic @JvmStatic
var typeface: Typeface? = Typeface.DEFAULT var typeface: Typeface? = Typeface.DEFAULT
@@ -146,6 +154,7 @@ object ChapterProvider {
stringBuilder, stringBuilder,
titlePaint, titlePaint,
titlePaintTextHeight, titlePaintTextHeight,
titlePaintFontMetrics,
isTitle = true, isTitle = true,
emptyContent = contents.isEmpty(), emptyContent = contents.isEmpty(),
isVolumeTitle = bookChapter.isVolume isVolumeTitle = bookChapter.isVolume
@@ -180,6 +189,7 @@ object ChapterProvider {
stringBuilder, stringBuilder,
contentPaint, contentPaint,
contentPaintTextHeight, contentPaintTextHeight,
contentPaintFontMetrics,
srcList = srcList srcList = srcList
).let { ).let {
absStartX = it.first absStartX = it.first
@@ -199,7 +209,8 @@ object ChapterProvider {
textPages, textPages,
stringBuilder, stringBuilder,
contentPaint, contentPaint,
contentPaintTextHeight contentPaintTextHeight,
contentPaintFontMetrics
).let { ).let {
absStartX = it.first absStartX = it.first
durY = it.second durY = it.second
@@ -220,7 +231,8 @@ object ChapterProvider {
textPages, textPages,
stringBuilder, stringBuilder,
contentPaint, contentPaint,
contentPaintTextHeight contentPaintTextHeight,
contentPaintFontMetrics
).let { ).let {
absStartX = it.first absStartX = it.first
durY = it.second durY = it.second
@@ -331,6 +343,7 @@ object ChapterProvider {
stringBuilder: StringBuilder, stringBuilder: StringBuilder,
textPaint: TextPaint, textPaint: TextPaint,
textHeight: Float, textHeight: Float,
fontMetrics: FontMetrics,
isTitle: Boolean = false, isTitle: Boolean = false,
emptyContent: Boolean = false, emptyContent: Boolean = false,
isVolumeTitle: Boolean = false, isVolumeTitle: Boolean = false,
@@ -342,6 +355,9 @@ object ChapterProvider {
} else { } else {
StaticLayout(text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true) StaticLayout(text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true)
} }
val widthsArray = FloatArray(text.length)
textPaint.getTextWidths(text, widthsArray)
val widthsList = widthsArray.asList()
var durY = when { var durY = when {
//标题y轴居中 //标题y轴居中
emptyContent && textPages.size == 1 -> { emptyContent && textPages.size == 1 -> {
@@ -371,7 +387,7 @@ object ChapterProvider {
} }
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
val textLine = TextLine(isTitle = isTitle) val textLine = TextLine(isTitle = isTitle)
if (durY + textPaint.textHeight > visibleHeight) { if (durY + textHeight > visibleHeight) {
val textPage = textPages.last() val textPage = textPages.last()
if (doublePage && absStartX < viewWidth / 2) { if (doublePage && absStartX < viewWidth / 2) {
textPage.leftLineSize = textPage.lineSize textPage.leftLineSize = textPage.lineSize
@@ -390,16 +406,18 @@ object ChapterProvider {
} }
durY = 0f durY = 0f
} }
val words = val lineStart = layout.getLineStart(lineIndex)
text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) val lineEnd = layout.getLineEnd(lineIndex)
val desiredWidth = layout.getLineWidth(lineIndex) val words = text.substring(lineStart, lineEnd)
val textWidths = widthsList.subList(lineStart, lineEnd)
val desiredWidth = textWidths.sum()
when { when {
lineIndex == 0 && layout.lineCount > 1 && !isTitle -> { lineIndex == 0 && layout.lineCount > 1 && !isTitle -> {
//第一行 非标题 //第一行 非标题
textLine.text = words textLine.text = words
addCharsToLineFirst( addCharsToLineFirst(
book, absStartX, textLine, words.toStringArray(), book, absStartX, textLine, words,
textPaint, desiredWidth, srcList textPaint, desiredWidth, textWidths, srcList
) )
} }
@@ -412,13 +430,13 @@ object ChapterProvider {
isTitle && isTitle &&
(ReadBookConfig.isMiddleTitle || emptyContent || isVolumeTitle) (ReadBookConfig.isMiddleTitle || emptyContent || isVolumeTitle)
) { ) {
(visibleWidth - layout.getLineWidth(lineIndex)) / 2 (visibleWidth - desiredWidth) / 2
} else { } else {
0f 0f
} }
addCharsToLineNatural( addCharsToLineNatural(
book, absStartX, textLine, words.toStringArray(), textPaint, book, absStartX, textLine, words, textPaint,
startX, !isTitle && lineIndex == 0, srcList startX, !isTitle && lineIndex == 0, textWidths, srcList
) )
} }
@@ -428,17 +446,17 @@ object ChapterProvider {
(ReadBookConfig.isMiddleTitle || emptyContent || isVolumeTitle) (ReadBookConfig.isMiddleTitle || emptyContent || isVolumeTitle)
) { ) {
//标题居中 //标题居中
val startX = (visibleWidth - layout.getLineWidth(lineIndex)) / 2 val startX = (visibleWidth - desiredWidth) / 2
addCharsToLineNatural( addCharsToLineNatural(
book, absStartX, textLine, words.toStringArray(), book, absStartX, textLine, words,
textPaint, startX, false, srcList textPaint, startX, false, textWidths, srcList
) )
} else { } else {
//中间行 //中间行
textLine.text = words textLine.text = words
addCharsToLineMiddle( addCharsToLineMiddle(
book, absStartX, textLine, words.toStringArray(), book, absStartX, textLine, words,
textPaint, desiredWidth, 0f, srcList textPaint, desiredWidth, 0f, textWidths, srcList
) )
} }
} }
@@ -462,7 +480,7 @@ object ChapterProvider {
} ?: 0) + sbLength } ?: 0) + sbLength
textLine.pagePosition = sbLength textLine.pagePosition = sbLength
textPages.last().addLine(textLine) textPages.last().addLine(textLine)
textLine.upTopBottom(durY, textPaint) textLine.upTopBottom(durY, textHeight, fontMetrics)
durY += textHeight * lineSpacingExtra durY += textHeight * lineSpacingExtra
textPages.last().height = durY textPages.last().height = durY
} }
@@ -477,17 +495,18 @@ object ChapterProvider {
book: Book, book: Book,
absStartX: Int, absStartX: Int,
textLine: TextLine, textLine: TextLine,
words: Array<String>, text: String,
textPaint: TextPaint, textPaint: TextPaint,
/**自然排版长度**/ /**自然排版长度**/
desiredWidth: Float, desiredWidth: Float,
textWidths: List<Float>,
srcList: LinkedList<String>? srcList: LinkedList<String>?
) { ) {
var x = 0f var x = 0f
if (!ReadBookConfig.textFullJustify) { if (!ReadBookConfig.textFullJustify) {
addCharsToLineNatural( addCharsToLineNatural(
book, absStartX, textLine, words, textPaint, book, absStartX, textLine, text, textPaint,
x, true, srcList x, true, textWidths, srcList
) )
return return
} }
@@ -504,11 +523,12 @@ object ChapterProvider {
x = x1 x = x1
textLine.indentWidth = x textLine.indentWidth = x
} }
if (words.size > bodyIndent.length) { if (text.length > bodyIndent.length) {
val words1 = words.copyOfRange(bodyIndent.length, words.size) val text1 = text.substring(bodyIndent.length, text.length)
val textWidths1 = textWidths.subList(bodyIndent.length, textWidths.size)
addCharsToLineMiddle( addCharsToLineMiddle(
book, absStartX, textLine, words1, book, absStartX, textLine, text1,
textPaint, desiredWidth, x, srcList textPaint, desiredWidth, x, textWidths1, srcList
) )
} }
} }
@@ -520,28 +540,30 @@ object ChapterProvider {
book: Book, book: Book,
absStartX: Int, absStartX: Int,
textLine: TextLine, textLine: TextLine,
words: Array<String>, text: String,
textPaint: TextPaint, textPaint: TextPaint,
/**自然排版长度**/ /**自然排版长度**/
desiredWidth: Float, desiredWidth: Float,
/**起始x坐标**/ /**起始x坐标**/
startX: Float, startX: Float,
textWidths: List<Float>,
srcList: LinkedList<String>? srcList: LinkedList<String>?
) { ) {
if (!ReadBookConfig.textFullJustify) { if (!ReadBookConfig.textFullJustify) {
addCharsToLineNatural( addCharsToLineNatural(
book, absStartX, textLine, words, textPaint, book, absStartX, textLine, text, textPaint,
startX, false, srcList startX, false, textWidths, srcList
) )
return return
} }
val residualWidth = visibleWidth - desiredWidth val residualWidth = visibleWidth - desiredWidth
val spaceSize = words.filter { it == " " }.size val spaceSize = text.count { it == ' ' }
val (words, widths) = getStringArrayAndTextWidths(text, textWidths)
if (spaceSize > 1) { if (spaceSize > 1) {
val d = residualWidth / spaceSize val d = residualWidth / spaceSize
var x = startX var x = startX
words.forEachIndexed { index, char -> words.forEachIndexed { index, char ->
val cw = StaticLayout.getDesiredWidth(char, textPaint) val cw = widths[index]
val x1 = if (char == " ") { val x1 = if (char == " ") {
if (index != words.lastIndex) (x + cw + d) else (x + cw) if (index != words.lastIndex) (x + cw + d) else (x + cw)
} else { } else {
@@ -558,7 +580,7 @@ object ChapterProvider {
val d = residualWidth / gapCount val d = residualWidth / gapCount
var x = startX var x = startX
words.forEachIndexed { index, char -> words.forEachIndexed { index, char ->
val cw = StaticLayout.getDesiredWidth(char, textPaint) val cw = widths[index]
val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw) val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)
addCharToLine( addCharToLine(
book, absStartX, textLine, char, book, absStartX, textLine, char,
@@ -567,7 +589,7 @@ object ChapterProvider {
x = x1 x = x1
} }
} }
exceed(absStartX, textLine, words) exceed(absStartX, textLine)
} }
/** /**
@@ -577,16 +599,18 @@ object ChapterProvider {
book: Book, book: Book,
absStartX: Int, absStartX: Int,
textLine: TextLine, textLine: TextLine,
words: Array<String>, text: String,
textPaint: TextPaint, textPaint: TextPaint,
startX: Float, startX: Float,
hasIndent: Boolean, hasIndent: Boolean,
textWidths: List<Float>,
srcList: LinkedList<String>? srcList: LinkedList<String>?
) { ) {
val indentLength = ReadBookConfig.paragraphIndent.length val indentLength = ReadBookConfig.paragraphIndent.length
var x = startX var x = startX
val (words, widths) = getStringArrayAndTextWidths(text, textWidths)
words.forEachIndexed { index, char -> words.forEachIndexed { index, char ->
val cw = StaticLayout.getDesiredWidth(char, textPaint) val cw = widths[index]
val x1 = x + cw val x1 = x + cw
addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList) addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)
x = x1 x = x1
@@ -594,7 +618,28 @@ object ChapterProvider {
textLine.indentWidth = x textLine.indentWidth = x
} }
} }
exceed(absStartX, textLine, words) exceed(absStartX, textLine)
}
private fun getStringArrayAndTextWidths(
text: String,
textWidths: List<Float>
): Pair<List<String>, List<Float>> {
val strList = arrayListOf<String>()
val textWidthList = arrayListOf<Float>()
for (i in textWidths.indices) {
if (text[i].isLowSurrogate()) {
continue
}
val char = if (i + 1 < text.lastIndex && text[i + 1].isLowSurrogate()) {
text.substring(i, i + 2)
} else {
text.substring(i, i + 1)
}
strList.add(char)
textWidthList.add(textWidths[i])
}
return strList to textWidthList
} }
/** /**
@@ -643,14 +688,15 @@ object ChapterProvider {
/** /**
* 超出边界处理 * 超出边界处理
*/ */
private fun exceed(absStartX: Int, textLine: TextLine, words: Array<String>) { private fun exceed(absStartX: Int, textLine: TextLine) {
val visibleEnd = absStartX + visibleWidth val visibleEnd = absStartX + visibleWidth
val endX = textLine.columns.lastOrNull()?.end ?: return val columns = textLine.columns
val endX = columns.lastOrNull()?.end ?: return
if (endX > visibleEnd) { if (endX > visibleEnd) {
val cc = (endX - visibleEnd) / words.size val cc = (endX - visibleEnd) / columns.size
for (i in 0..words.lastIndex) { for (i in 0..columns.lastIndex) {
textLine.getColumnReverseAt(i).let { textLine.getColumnReverseAt(i).let {
val py = cc * (words.size - i) val py = cc * (columns.size - i)
it.start = it.start - py it.start = it.start - py
it.end = it.end - py it.end = it.end - py
} }
@@ -679,6 +725,8 @@ object ChapterProvider {
indentCharWidth = StaticLayout.getDesiredWidth(bodyIndent, contentPaint) / bodyIndent.length indentCharWidth = StaticLayout.getDesiredWidth(bodyIndent, contentPaint) / bodyIndent.length
titlePaintTextHeight = titlePaint.textHeight titlePaintTextHeight = titlePaint.textHeight
contentPaintTextHeight = contentPaint.textHeight contentPaintTextHeight = contentPaint.textHeight
titlePaintFontMetrics = titlePaint.fontMetrics
contentPaintFontMetrics = contentPaint.fontMetrics
upLayout() upLayout()
} }