This commit is contained in:
Horis 2024-02-25 19:46:40 +08:00
parent 822db28d88
commit 92d6e00ab0
5 changed files with 110 additions and 8 deletions

View File

@ -422,7 +422,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
var curLine = curTextLines[lineIndex] var curLine = curTextLines[lineIndex]
length = length - currentPage.text.length + curLine.text.length length = length - currentPage.text.length + curLine.text.length
if (curLine.isParagraphEnd) length++ if (curLine.isParagraphEnd) length++
while (length < contentPosition && lineIndex + 1 < curTextLines.size) { while (length <= contentPosition && lineIndex + 1 < curTextLines.size) {
lineIndex += 1 lineIndex += 1
curLine = curTextLines[lineIndex] curLine = curTextLines[lineIndex]
length += curLine.text.length length += curLine.text.length

View File

@ -2,13 +2,18 @@ package io.legado.app.ui.book.read.page.entities
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint.FontMetrics import android.graphics.Paint.FontMetrics
import android.os.Build
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.core.graphics.withTranslation
import io.legado.app.help.PaintPool
import io.legado.app.help.book.isImage import io.legado.app.help.book.isImage
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.entities.TextPage.Companion.emptyTextPage import io.legado.app.ui.book.read.page.entities.TextPage.Companion.emptyTextPage
import io.legado.app.ui.book.read.page.entities.column.BaseColumn import io.legado.app.ui.book.read.page.entities.column.BaseColumn
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.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw
@ -32,6 +37,13 @@ data class TextLine(
val isTitle: Boolean = false, val isTitle: Boolean = false,
var isParagraphEnd: Boolean = false, var isParagraphEnd: Boolean = false,
var isImage: Boolean = false, var isImage: Boolean = false,
var startX: Float = 0f,
var indentSize: Int = 0,
var extraLetterSpacing: Float = 0f,
var extraLetterSpacingOffsetX: Float = 0f,
var wordSpacing: Float = 0f,
var exceed: Boolean = false,
var onlyTextColumn: Boolean = true,
) { ) {
val columns: List<BaseColumn> get() = textColumns val columns: List<BaseColumn> get() = textColumns
@ -41,6 +53,7 @@ data class TextLine(
val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize
val height: Float inline get() = lineBottom - lineTop val height: Float inline get() = lineBottom - lineTop
val canvasRecorder = CanvasRecorderFactory.create() val canvasRecorder = CanvasRecorderFactory.create()
var searchResultColumnCount = 0
var isReadAloud: Boolean = false var isReadAloud: Boolean = false
set(value) { set(value) {
if (field != value) { if (field != value) {
@ -52,6 +65,9 @@ data class TextLine(
var isLeftLine = true var isLeftLine = true
fun addColumn(column: BaseColumn) { fun addColumn(column: BaseColumn) {
if (column !is TextColumn) {
onlyTextColumn = false
}
column.textLine = this column.textLine = this
textColumns.add(column) textColumns.add(column)
} }
@ -129,14 +145,56 @@ data class TextLine(
} }
private fun drawTextLine(view: ContentTextView, canvas: Canvas) { private fun drawTextLine(view: ContentTextView, canvas: Canvas) {
if (checkFastDraw()) {
fastDrawTextLine(view, canvas)
} else {
for (i in columns.indices) { for (i in columns.indices) {
columns[i].draw(view, canvas) columns[i].draw(view, canvas)
} }
}
if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) { if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) {
drawUnderline(canvas) drawUnderline(canvas)
} }
} }
private fun fastDrawTextLine(view: ContentTextView, canvas: Canvas) {
val textPaint = if (isTitle) {
ChapterProvider.titlePaint
} else {
ChapterProvider.contentPaint
}
val textColor = if (isReadAloud) {
ThemeStore.accentColor
} else {
ReadBookConfig.textColor
}
val paint = PaintPool.obtain()
paint.set(textPaint)
if (extraLetterSpacing != 0f) {
paint.letterSpacing += extraLetterSpacing
}
if (wordSpacing != 0f) {
paint.wordSpacing += wordSpacing
}
if (paint.color != textColor) {
paint.color = textColor
}
if (extraLetterSpacingOffsetX != 0f) {
canvas.withTranslation(extraLetterSpacingOffsetX) {
canvas.drawText(text, indentSize, text.length, startX, lineBase - lineTop, paint)
}
} else {
canvas.drawText(text, indentSize, text.length, startX, lineBase - lineTop, paint)
}
PaintPool.recycle(paint)
for (i in columns.indices) {
val column = columns[i] as TextColumn
if (column.selected) {
canvas.drawRect(column.start, 0f, column.end, height, view.selectedPaint)
}
}
}
/** /**
* 绘制下划线 * 绘制下划线
*/ */
@ -151,6 +209,16 @@ data class TextLine(
) )
} }
fun checkFastDraw(): Boolean {
if (exceed || !onlyTextColumn || textPage.isMsgPage) {
return false
}
if (!atLeastApi29 && wordSpacing != 0f) {
return false
}
return searchResultColumnCount == 0
}
fun invalidate() { fun invalidate() {
invalidateSelf() invalidateSelf()
textPage.invalidate() textPage.invalidate()
@ -166,6 +234,7 @@ data class TextLine(
companion object { companion object {
val emptyTextLine = TextLine() val emptyTextLine = TextLine()
private val atLeastApi29 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
} }
} }

View File

@ -1,11 +1,13 @@
package io.legado.app.ui.book.read.page.entities package io.legado.app.ui.book.read.page.entities
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint
import android.text.Layout import android.text.Layout
import android.text.StaticLayout import android.text.StaticLayout
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.core.graphics.withTranslation import androidx.core.graphics.withTranslation
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.PaintPool
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.entities.TextChapter.Companion.emptyTextChapter import io.legado.app.ui.book.read.page.entities.TextChapter.Companion.emptyTextChapter
@ -13,6 +15,7 @@ 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.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeeded import io.legado.app.utils.canvasrecorder.recordIfNeeded
import io.legado.app.utils.dpToPx
import splitties.init.appCtx import splitties.init.appCtx
import java.text.DecimalFormat import java.text.DecimalFormat
import kotlin.math.min import kotlin.math.min
@ -277,6 +280,21 @@ data class TextPage(
} }
} }
private fun drawDebugInfo(canvas: Canvas) {
ChapterProvider.run {
val paint = PaintPool.obtain()
paint.style = Paint.Style.STROKE
canvas.drawRect(
paddingLeft.toFloat(),
0f,
(paddingLeft + visibleWidth).toFloat(),
height - 1.dpToPx(),
paint
)
PaintPool.recycle(paint)
}
}
private fun drawPage(view: ContentTextView, canvas: Canvas) { private fun drawPage(view: ContentTextView, canvas: Canvas) {
for (i in lines.indices) { for (i in lines.indices) {
val line = lines[i] val line = lines[i]

View File

@ -33,6 +33,11 @@ data class TextColumn(
set(value) { set(value) {
if (field != value) { if (field != value) {
textLine.invalidate() textLine.invalidate()
if (value) {
textLine.searchResultColumnCount++
} else {
textLine.searchResultColumnCount--
}
} }
field = value field = value
} }

View File

@ -31,6 +31,7 @@ import kotlinx.coroutines.launch
import java.util.LinkedList import java.util.LinkedList
import java.util.Locale import java.util.Locale
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
import kotlin.math.roundToInt
class TextChapterLayout( class TextChapterLayout(
scope: CoroutineScope, scope: CoroutineScope,
@ -467,7 +468,7 @@ class TextChapterLayout(
//第一行 非标题 //第一行 非标题
textLine.text = lineText textLine.text = lineText
addCharsToLineFirst( addCharsToLineFirst(
book, absStartX, textLine, words, book, absStartX, textLine, words, textPaint,
desiredWidth, widths, srcList desiredWidth, widths, srcList
) )
} }
@ -505,7 +506,7 @@ class TextChapterLayout(
//中间行 //中间行
textLine.text = lineText textLine.text = lineText
addCharsToLineMiddle( addCharsToLineMiddle(
book, absStartX, textLine, words, book, absStartX, textLine, words, textPaint,
desiredWidth, 0f, widths, srcList desiredWidth, 0f, widths, srcList
) )
} }
@ -556,6 +557,7 @@ class TextChapterLayout(
absStartX: Int, absStartX: Int,
textLine: TextLine, textLine: TextLine,
words: List<String>, words: List<String>,
textPaint: TextPaint,
/**自然排版长度**/ /**自然排版长度**/
desiredWidth: Float, desiredWidth: Float,
textWidths: List<Float>, textWidths: List<Float>,
@ -582,11 +584,12 @@ class TextChapterLayout(
x = x1 x = x1
textLine.indentWidth = x textLine.indentWidth = x
} }
textLine.indentSize = bodyIndent.length
if (words.size > bodyIndent.length) { if (words.size > bodyIndent.length) {
val text1 = words.subList(bodyIndent.length, words.size) val text1 = words.subList(bodyIndent.length, words.size)
val textWidths1 = textWidths.subList(bodyIndent.length, textWidths.size) val textWidths1 = textWidths.subList(bodyIndent.length, textWidths.size)
addCharsToLineMiddle( addCharsToLineMiddle(
book, absStartX, textLine, text1, book, absStartX, textLine, text1, textPaint,
desiredWidth, x, textWidths1, srcList desiredWidth, x, textWidths1, srcList
) )
} }
@ -600,6 +603,7 @@ class TextChapterLayout(
absStartX: Int, absStartX: Int,
textLine: TextLine, textLine: TextLine,
words: List<String>, words: List<String>,
textPaint: TextPaint,
/**自然排版长度**/ /**自然排版长度**/
desiredWidth: Float, desiredWidth: Float,
/**起始x坐标**/ /**起始x坐标**/
@ -616,8 +620,10 @@ class TextChapterLayout(
} }
val residualWidth = visibleWidth - desiredWidth val residualWidth = visibleWidth - desiredWidth
val spaceSize = words.count { it == " " } val spaceSize = words.count { it == " " }
textLine.startX = absStartX + startX
if (spaceSize > 1) { if (spaceSize > 1) {
val d = residualWidth / spaceSize val d = residualWidth / (spaceSize - 1)
textLine.wordSpacing = d
var x = startX var x = startX
for (index in words.indices) { for (index in words.indices) {
val char = words[index] val char = words[index]
@ -636,6 +642,8 @@ class TextChapterLayout(
} else { } else {
val gapCount: Int = words.lastIndex val gapCount: Int = words.lastIndex
val d = residualWidth / gapCount val d = residualWidth / gapCount
textLine.extraLetterSpacingOffsetX = -d / 2
textLine.extraLetterSpacing = d / textPaint.textSize
var x = startX var x = startX
for (index in words.indices) { for (index in words.indices) {
val char = words[index] val char = words[index]
@ -666,6 +674,7 @@ class TextChapterLayout(
) { ) {
val indentLength = ReadBookConfig.paragraphIndent.length val indentLength = ReadBookConfig.paragraphIndent.length
var x = startX var x = startX
textLine.startX = absStartX + startX
for (index in words.indices) { for (index in words.indices) {
val char = words[index] val char = words[index]
val cw = textWidths[index] val cw = textWidths[index]
@ -727,8 +736,9 @@ class TextChapterLayout(
*/ */
private fun exceed(absStartX: Int, textLine: TextLine, words: List<String>) { private fun exceed(absStartX: Int, textLine: TextLine, words: List<String>) {
val visibleEnd = absStartX + visibleWidth val visibleEnd = absStartX + visibleWidth
val endX = textLine.columns.lastOrNull()?.end ?: return val endX = textLine.columns.lastOrNull()?.end?.roundToInt() ?: return
if (endX > visibleEnd) { if (endX > visibleEnd) {
textLine.exceed = true
val cc = (endX - visibleEnd) / words.size val cc = (endX - visibleEnd) / words.size
for (i in 0..words.lastIndex) { for (i in 0..words.lastIndex) {
textLine.getColumnReverseAt(i).let { textLine.getColumnReverseAt(i).let {