Horis 2 лет назад
Родитель
Сommit
de5ef2e165

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

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

+ 71 - 2
app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt

@@ -2,13 +2,18 @@ package io.legado.app.ui.book.read.page.entities
 
 import android.graphics.Canvas
 import android.graphics.Paint.FontMetrics
+import android.os.Build
 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.config.ReadBookConfig
+import io.legado.app.lib.theme.ThemeStore
 import io.legado.app.model.ReadBook
 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.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.utils.canvasrecorder.CanvasRecorderFactory
 import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw
@@ -32,6 +37,13 @@ data class TextLine(
     val isTitle: Boolean = false,
     var isParagraphEnd: 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
@@ -41,6 +53,7 @@ data class TextLine(
     val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize
     val height: Float inline get() = lineBottom - lineTop
     val canvasRecorder = CanvasRecorderFactory.create()
+    var searchResultColumnCount = 0
     var isReadAloud: Boolean = false
         set(value) {
             if (field != value) {
@@ -52,6 +65,9 @@ data class TextLine(
     var isLeftLine = true
 
     fun addColumn(column: BaseColumn) {
+        if (column !is TextColumn) {
+            onlyTextColumn = false
+        }
         column.textLine = this
         textColumns.add(column)
     }
@@ -129,14 +145,56 @@ data class TextLine(
     }
 
     private fun drawTextLine(view: ContentTextView, canvas: Canvas) {
-        for (i in columns.indices) {
-            columns[i].draw(view, canvas)
+        if (checkFastDraw()) {
+            fastDrawTextLine(view, canvas)
+        } else {
+            for (i in columns.indices) {
+                columns[i].draw(view, canvas)
+            }
         }
         if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) {
             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() {
         invalidateSelf()
         textPage.invalidate()
@@ -166,6 +234,7 @@ data class TextLine(
 
     companion object {
         val emptyTextLine = TextLine()
+        private val atLeastApi29 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
     }
 
 }

+ 18 - 0
app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt

@@ -1,11 +1,13 @@
 package io.legado.app.ui.book.read.page.entities
 
 import android.graphics.Canvas
+import android.graphics.Paint
 import android.text.Layout
 import android.text.StaticLayout
 import androidx.annotation.Keep
 import androidx.core.graphics.withTranslation
 import io.legado.app.R
+import io.legado.app.help.PaintPool
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.ui.book.read.page.ContentTextView
 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.utils.canvasrecorder.CanvasRecorderFactory
 import io.legado.app.utils.canvasrecorder.recordIfNeeded
+import io.legado.app.utils.dpToPx
 import splitties.init.appCtx
 import java.text.DecimalFormat
 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) {
         for (i in lines.indices) {
             val line = lines[i]

+ 5 - 0
app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt

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

+ 15 - 5
app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt

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