Просмотр исходного кода

Merge pull request #1292 from woruteyqi/fix/cross-paragraph-regex

支持regex全文匹配
Kudomaga 5 месяцев назад
Родитель
Сommit
c5fcaa985f
1 измененных файлов с 129 добавлено и 30 удалено
  1. 129 30
      app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt

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

@@ -134,6 +134,8 @@ class TextChapterLayout(
 
 
     var channel = Channel<TextPage>(Channel.UNLIMITED)
     var channel = Channel<TextPage>(Channel.UNLIMITED)
 
 
+    private var globalRegexResult: RegexMatchResult? = null
+
 
 
     init {
     init {
         job = Coroutine.async(
         job = Coroutine.async(
@@ -241,8 +243,8 @@ class TextChapterLayout(
         val isSingleImageStyle = imageStyle.equals(Book.imgStyleSingle, true)
         val isSingleImageStyle = imageStyle.equals(Book.imgStyleSingle, true)
         val isTextImageStyle = imageStyle.equals(Book.imgStyleText, true)
         val isTextImageStyle = imageStyle.equals(Book.imgStyleText, true)
 
 
-        if (titleMode != 2 || bookChapter.isVolume || contents.isEmpty()) {
-            val allTitleSegments = displayTitle.splitNotBlank("\n").flatMap { rawTitle ->
+        val allTitleSegments = if (titleMode != 2 || bookChapter.isVolume || contents.isEmpty()) {
+            displayTitle.splitNotBlank("\n").flatMap { rawTitle ->
                 TitleStyleParser.getSegments(
                 TitleStyleParser.getSegments(
                     rawTitle,
                     rawTitle,
                     titleSegType,
                     titleSegType,
@@ -251,7 +253,66 @@ class TextChapterLayout(
                     titleSegScaling
                     titleSegScaling
                 )
                 )
             }
             }
+        } else null
+
+        if (ReadBookConfig.regexColorRules.isNotEmpty()) {
+            val fullTextBuilder = StringBuilder()
+            allTitleSegments?.forEachIndexed { index, segment ->
+                val reviewImg = bookChapter.reviewImg
+                var reviewTxt = ""
+                if (index == allTitleSegments.lastIndex && reviewImg != null) {
+                    reviewTxt = if (reviewImg.contains("TEXT")) reviewChar else srcReplaceChar
+                }
+                fullTextBuilder.append(segment.text).append(reviewTxt).append("\n")
+            }
+            contents.forEach { content ->
+                if (adaptSpecialStyle) {
+                    val t = content.trim()
+                    if (t == "[newpage]" || t.startsWith("<usehtml>")) {
+                        fullTextBuilder.append(content).append("\n")
+                        return@forEach
+                    }
+                }
+                val text = content.replace(srcReplaceCharC, srcReplaceCharD)
+                if (isTextImageStyle) {
+                    val matcher = AppPattern.imgPattern.matcher(text)
+                    val ssb = StringBuffer()
+                    while (matcher.find()) {
+                        if (matcher.group(1) != null) {
+                            matcher.appendReplacement(ssb, srcReplaceChar)
+                        }
+                    }
+                    matcher.appendTail(ssb)
+                    fullTextBuilder.append(ssb.toString())
+                } else {
+                    val matcher = AppPattern.imgPattern.matcher(text)
+                    var start = 0
+                    while (matcher.find()) {
+                        val imgSrc = matcher.group(1) ?: continue
+                        val iStyle = if (imgSrc.contains("TEXT")) "text" else imageStyle
+                        if (start < matcher.start()) {
+                            fullTextBuilder.append(text.substring(start, matcher.start()))
+                        }
+                        if (iStyle == "text" || iStyle == "TEXT") {
+                            fullTextBuilder.append(if (iStyle == "TEXT") reviewChar else srcReplaceChar)
+                        } else {
+                            fullTextBuilder.append(" ")
+                        }
+                        start = matcher.end()
+                    }
+                    if (start < text.length) {
+                        fullTextBuilder.append(text.substring(start))
+                    }
+                    if (AppConfig.enableReview) fullTextBuilder.append(reviewChar)
+                }
+                fullTextBuilder.append("\n")
+            }
+            preApplyRegexColorRules(fullTextBuilder.toString())
+        }
 
 
+        var currentOffset = 0
+
+        if (allTitleSegments != null) {
             allTitleSegments.forEachIndexed { index, segment ->
             allTitleSegments.forEachIndexed { index, segment ->
                 val currentPaint: TextPaint
                 val currentPaint: TextPaint
                 val currentHeight: Float
                 val currentHeight: Float
@@ -277,9 +338,10 @@ class TextChapterLayout(
                     reviewTxt = if (reviewImg.contains("TEXT")) reviewChar else srcReplaceChar
                     reviewTxt = if (reviewImg.contains("TEXT")) reviewChar else srcReplaceChar
                 }
                 }
 
 
+                val text = segment.text + reviewTxt
                 setTypeText(
                 setTypeText(
                     book = book,
                     book = book,
-                    text = segment.text + reviewTxt,
+                    text = text,
                     textPaint = currentPaint,
                     textPaint = currentPaint,
                     textHeight = currentHeight,
                     textHeight = currentHeight,
                     fontMetrics = currentMetrics,
                     fontMetrics = currentMetrics,
@@ -287,8 +349,10 @@ class TextChapterLayout(
                     srcList = srcList.ifEmpty { null },
                     srcList = srcList.ifEmpty { null },
                     isTitle = true,
                     isTitle = true,
                     emptyContent = contents.isEmpty(),
                     emptyContent = contents.isEmpty(),
-                    isVolumeTitle = bookChapter.isVolume
+                    isVolumeTitle = bookChapter.isVolume,
+                    offset = currentOffset
                 )
                 )
+                currentOffset += text.length + 1
 
 
                 if (segment.scale != 1.0f) {
                 if (segment.scale != 1.0f) {
                     val currentLines = pendingTextPage.lines
                     val currentLines = pendingTextPage.lines
@@ -319,9 +383,11 @@ class TextChapterLayout(
                 val text = content.trim()
                 val text = content.trim()
                 if (text == "[newpage]") {
                 if (text == "[newpage]") {
                     prepareNextPageIfNeed()
                     prepareNextPageIfNeed()
+                    currentOffset += content.length + 1
                     return@forEach
                     return@forEach
                 } else if (text.startsWith("<usehtml>")) {
                 } else if (text.startsWith("<usehtml>")) {
                     setTypeHtml(imageStyle, book, text.substring(9, text.lastIndexOf("<")))
                     setTypeHtml(imageStyle, book, text.substring(9, text.lastIndexOf("<")))
+                    currentOffset += content.length + 1
                     return@forEach
                     return@forEach
                 }
                 }
             }
             }
@@ -347,8 +413,10 @@ class TextChapterLayout(
                     contentPaintTextHeight,
                     contentPaintTextHeight,
                     contentPaintFontMetrics,
                     contentPaintFontMetrics,
                     imageStyle,
                     imageStyle,
-                    srcList = srcList
+                    srcList = srcList,
+                    offset = currentOffset
                 )
                 )
+                currentOffset += text.length
             } else {
             } else {
                 if (isSingleImageStyle && isSetTypedImage) {
                 if (isSingleImageStyle && isSetTypedImage) {
                     isSetTypedImage = false
                     isSetTypedImage = false
@@ -402,10 +470,12 @@ class TextChapterLayout(
                         }
                         }
 
 
                         if (start < matcher.start()) {
                         if (start < matcher.start()) {
-                            sb.append(text.substring(start, matcher.start()))
+                            val textPart = text.substring(start, matcher.start())
+                            sb.append(textPart)
                         }
                         }
                         if (iStyle == "text" || iStyle == "TEXT") {
                         if (iStyle == "text" || iStyle == "TEXT") {
-                            sb.append(if (iStyle == "TEXT") reviewChar else srcReplaceChar)
+                            val charPart = if (iStyle == "TEXT") reviewChar else srcReplaceChar
+                            sb.append(charPart)
                             srcList.add(imgSrc)
                             srcList.add(imgSrc)
                             clickList.add(click)
                             clickList.add(click)
                         } else {
                         } else {
@@ -413,10 +483,12 @@ class TextChapterLayout(
                             if (textBefore.isNotBlank()) {
                             if (textBefore.isNotBlank()) {
                                 wordCount += textBefore.replace(noWordCountRegex,"").length
                                 wordCount += textBefore.replace(noWordCountRegex,"").length
                                 setTypeText(
                                 setTypeText(
-                                    book, sb.toString(), contentPaint, contentPaintTextHeight,
+                                    book, textBefore, contentPaint, contentPaintTextHeight,
                                     contentPaintFontMetrics, "TEXT", isFirstLine = isFirstLine,
                                     contentPaintFontMetrics, "TEXT", isFirstLine = isFirstLine,
-                                    srcList = srcList, clickList = clickList
+                                    srcList = srcList, clickList = clickList,
+                                    offset = currentOffset
                                 )
                                 )
+                                currentOffset += textBefore.length
                                 sb.setLength(0)
                                 sb.setLength(0)
                                 isFirstLine = false
                                 isFirstLine = false
                             }
                             }
@@ -428,6 +500,7 @@ class TextChapterLayout(
                                 imgSize,
                                 imgSize,
                                 click
                                 click
                             ) // 传递点击信息
                             ) // 传递点击信息
+                            currentOffset += 1
                             isSetTypedImage = true
                             isSetTypedImage = true
                         }
                         }
                         start = matcher.end()
                         start = matcher.end()
@@ -444,21 +517,25 @@ class TextChapterLayout(
                 text = sb.toString()
                 text = sb.toString()
                 if (text.isNotBlank()) {
                 if (text.isNotBlank()) {
                     wordCount += text.replace(noWordCountRegex,"").length
                     wordCount += text.replace(noWordCountRegex,"").length
+                    val textToType = if (AppConfig.enableReview) text + reviewChar else text
                     setTypeText(
                     setTypeText(
                         book,
                         book,
-                        if (AppConfig.enableReview) text + reviewChar else text,
+                        textToType,
                         contentPaint,
                         contentPaint,
                         contentPaintTextHeight,
                         contentPaintTextHeight,
                         contentPaintFontMetrics,
                         contentPaintFontMetrics,
                         "TEXT",
                         "TEXT",
                         isFirstLine = isFirstLine,
                         isFirstLine = isFirstLine,
                         srcList = srcList.ifEmpty { null },
                         srcList = srcList.ifEmpty { null },
-                        clickList = clickList.ifEmpty { null }
+                        clickList = clickList.ifEmpty { null },
+                        offset = currentOffset
                     )
                     )
+                    currentOffset += textToType.length
                 }
                 }
             }
             }
             pendingTextPage.lines.last().isParagraphEnd = true
             pendingTextPage.lines.last().isParagraphEnd = true
             stringBuilder.append("\n")
             stringBuilder.append("\n")
+            currentOffset += 1
         }
         }
         val chapterWordCount = StringUtils.wordCountFormat(wordCount.toString())
         val chapterWordCount = StringUtils.wordCountFormat(wordCount.toString())
         bookChapter.wordCount = chapterWordCount
         bookChapter.wordCount = chapterWordCount
@@ -868,11 +945,12 @@ class TextChapterLayout(
         emptyContent: Boolean = false,
         emptyContent: Boolean = false,
         isVolumeTitle: Boolean = false,
         isVolumeTitle: Boolean = false,
         srcList: LinkedList<String>? = null,
         srcList: LinkedList<String>? = null,
-        clickList: LinkedList<String?>? = null
+        clickList: LinkedList<String?>? = null,
+        offset: Int = -1
     ) {
     ) {
         val widthsArray = allocateFloatArray(text.length)
         val widthsArray = allocateFloatArray(text.length)
         textPaint.getTextWidthsCompat(text, widthsArray)
         textPaint.getTextWidthsCompat(text, widthsArray)
-        val colorMap = applyRegexColorRules(text)
+        val colorMap = applyRegexColorRules(text, offset)
         val layout = if (useZhLayout) {
         val layout = if (useZhLayout) {
             val (words, widths) = measureTextSplit(text, widthsArray)
             val (words, widths) = measureTextSplit(text, widthsArray)
             val indentSize = if (isFirstLine) paragraphIndent.length else 0
             val indentSize = if (isFirstLine) paragraphIndent.length else 0
@@ -1310,28 +1388,21 @@ class TextChapterLayout(
         val fontPathArray: Array<String?>
         val fontPathArray: Array<String?>
     )
     )
 
 
-    private fun applyRegexColorRules(text: String): RegexMatchResult? {
+    private fun preApplyRegexColorRules(fullText: String) {
         val rules = ReadBookConfig.regexColorRules
         val rules = ReadBookConfig.regexColorRules
-        if (rules.isEmpty()) return null
+        if (rules.isEmpty()) return
+        val colorArray = IntArray(fullText.length) { -1 }
+        val fontPathArray = arrayOfNulls<String>(fullText.length)
         var hasMatch = false
         var hasMatch = false
         for (rule in rules) {
         for (rule in rules) {
             try {
             try {
-                val regex = regexCache.getOrPut(rule.pattern) { Regex(rule.pattern) }
-                if (regex.containsMatchIn(text)) {
-                    hasMatch = true
-                    break
+                val regex = synchronized(regexCache) {
+                    regexCache.getOrPut(rule.pattern) {
+                        Regex(rule.pattern, RegexOption.DOT_MATCHES_ALL)
+                    }
                 }
                 }
-            } catch (_: Exception) {
-            }
-        }
-        if (!hasMatch) return null
-        val colorArray = IntArray(text.length) { -1 }
-        val fontPathArray = arrayOfNulls<String>(text.length)
-        for (rule in rules) {
-            try {
-                val regex = regexCache.getOrPut(rule.pattern) { Regex(rule.pattern) }
-                val matches = regex.findAll(text)
-                for (match in matches) {
+                regex.findAll(fullText).forEach { match ->
+                    hasMatch = true
                     for (i in match.range) {
                     for (i in match.range) {
                         colorArray[i] = rule.color
                         colorArray[i] = rule.color
                         if (rule.fontPath.isNotEmpty()) {
                         if (rule.fontPath.isNotEmpty()) {
@@ -1342,6 +1413,34 @@ class TextChapterLayout(
             } catch (_: Exception) {
             } catch (_: Exception) {
             }
             }
         }
         }
+        if (hasMatch) globalRegexResult = RegexMatchResult(colorArray, fontPathArray)
+    }
+
+    private fun applyRegexColorRules(text: String, offset: Int): RegexMatchResult? {
+        val globalResult = globalRegexResult ?: return null
+        if (offset < 0) return null
+        val endIdx = minOf(offset + text.length, globalResult.colorArray.size)
+        var hasMatch = false
+        for (globalIdx in offset until endIdx) {
+            if (globalResult.colorArray[globalIdx] != -1 || globalResult.fontPathArray[globalIdx] != null) {
+                hasMatch = true
+                break
+            }
+        }
+        if (!hasMatch) return null
+        val colorArray = IntArray(text.length) { -1 }
+        val fontPathArray = arrayOfNulls<String>(text.length)
+        for (i in text.indices) {
+            val globalIdx = offset + i
+            if (globalIdx >= 0 && globalIdx < globalResult.colorArray.size) {
+                if (globalResult.colorArray[globalIdx] != -1) {
+                    colorArray[i] = globalResult.colorArray[globalIdx]
+                }
+                if (globalResult.fontPathArray[globalIdx] != null) {
+                    fontPathArray[i] = globalResult.fontPathArray[globalIdx]
+                }
+            }
+        }
         return RegexMatchResult(colorArray, fontPathArray)
         return RegexMatchResult(colorArray, fontPathArray)
     }
     }