|
|
@@ -2202,55 +2202,102 @@ class ReadBookViewModel(
|
|
|
): Array<Int> {
|
|
|
val pages = textChapter.pages
|
|
|
val content = textChapter.getContent()
|
|
|
- val queryLength = query.length
|
|
|
+ if (pages.isEmpty()) return arrayOf(0, 0, 0, 0, 0)
|
|
|
|
|
|
- var count = 0
|
|
|
- var index = content.indexOf(query)
|
|
|
- while (count != searchResult.resultCountWithinChapter) {
|
|
|
- index = content.indexOf(query, index + queryLength)
|
|
|
- count += 1
|
|
|
- }
|
|
|
- val contentPosition = index
|
|
|
+ val contentPosition = searchResult.queryIndexInChapter
|
|
|
+ .takeIf { it in content.indices && searchResultMatchesAt(content, it, searchResult, query) }
|
|
|
+ ?: findSearchResultPosition(content, searchResult, query)
|
|
|
if (contentPosition < 0) return arrayOf(0, 0, 0, 0, 0)
|
|
|
- var pageIndex = 0
|
|
|
- var length = pages[pageIndex].text.length
|
|
|
- while (length < contentPosition && pageIndex + 1 < pages.size) {
|
|
|
- pageIndex += 1
|
|
|
- length += pages[pageIndex].text.length
|
|
|
- }
|
|
|
|
|
|
+ val pageIndex = textChapter.getPageIndexByCharIndex(contentPosition)
|
|
|
+ .coerceIn(0, pages.lastIndex)
|
|
|
val currentPage = pages[pageIndex]
|
|
|
- val curTextLines = currentPage.lines
|
|
|
- var lineIndex = 0
|
|
|
- var curLine = curTextLines[lineIndex]
|
|
|
- length = length - currentPage.text.length + curLine.text.length
|
|
|
- if (curLine.isParagraphEnd) length++
|
|
|
- while (length < contentPosition && lineIndex + 1 < curTextLines.size) {
|
|
|
- lineIndex += 1
|
|
|
- curLine = curTextLines[lineIndex]
|
|
|
- length += curLine.text.length
|
|
|
- if (curLine.isParagraphEnd) length++
|
|
|
- }
|
|
|
+ val matchLength = searchResult.matchLength.takeIf { it > 0 } ?: query.length
|
|
|
+ val endPosition = (contentPosition + matchLength).coerceAtMost(content.length)
|
|
|
+ val start = linePositionFor(currentPage, contentPosition, preferPreviousAtBoundary = false)
|
|
|
+ val end = linePositionFor(currentPage, endPosition, preferPreviousAtBoundary = true)
|
|
|
+ return arrayOf(pageIndex, start.lineIndex, start.charIndex, end.lineIndex, end.charIndex)
|
|
|
+ }
|
|
|
|
|
|
- val currentLine = currentPage.lines[lineIndex]
|
|
|
- var curLineLength = currentLine.text.length
|
|
|
- if (currentLine.isParagraphEnd) curLineLength++
|
|
|
- length -= curLineLength
|
|
|
+ private fun findSearchResultPosition(
|
|
|
+ content: String,
|
|
|
+ searchResult: SearchResult,
|
|
|
+ query: String,
|
|
|
+ ): Int {
|
|
|
+ if (query.isEmpty()) return -1
|
|
|
+ if (searchResult.isRegex) {
|
|
|
+ return runCatching {
|
|
|
+ Regex(query).findAll(content)
|
|
|
+ .drop(searchResult.resultCountWithinChapter)
|
|
|
+ .firstOrNull()
|
|
|
+ ?.range
|
|
|
+ ?.first
|
|
|
+ }.getOrNull() ?: -1
|
|
|
+ }
|
|
|
|
|
|
- val charIndex = contentPosition - length
|
|
|
- var addLine = 0
|
|
|
- var charIndex2 = 0
|
|
|
- if ((charIndex + queryLength) > curLineLength) {
|
|
|
- addLine = 1
|
|
|
- charIndex2 = charIndex + queryLength - curLineLength - 1
|
|
|
+ var count = 0
|
|
|
+ var index = content.indexOf(query, ignoreCase = true)
|
|
|
+ while (count != searchResult.resultCountWithinChapter && index >= 0) {
|
|
|
+ index = content.indexOf(query, index + query.length, ignoreCase = true)
|
|
|
+ count += 1
|
|
|
}
|
|
|
- if ((lineIndex + addLine + 1) > currentPage.lines.size) {
|
|
|
- addLine = -1
|
|
|
- charIndex2 = charIndex + queryLength - curLineLength - 1
|
|
|
+ return index
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun searchResultMatchesAt(
|
|
|
+ content: String,
|
|
|
+ position: Int,
|
|
|
+ searchResult: SearchResult,
|
|
|
+ query: String,
|
|
|
+ ): Boolean {
|
|
|
+ if (query.isEmpty()) return false
|
|
|
+ if (searchResult.isRegex) {
|
|
|
+ return runCatching {
|
|
|
+ Regex(query).find(content, position)?.range?.first == position
|
|
|
+ }.getOrDefault(false)
|
|
|
+ }
|
|
|
+ return position + query.length <= content.length &&
|
|
|
+ content.regionMatches(position, query, 0, query.length, ignoreCase = true)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun linePositionFor(
|
|
|
+ page: TextPage,
|
|
|
+ chapterPosition: Int,
|
|
|
+ preferPreviousAtBoundary: Boolean,
|
|
|
+ ): SearchLinePosition {
|
|
|
+ if (page.lines.isEmpty()) return SearchLinePosition(0, 0)
|
|
|
+ var fallbackIndex = 0
|
|
|
+ page.lines.forEachIndexed { index, line ->
|
|
|
+ val lineEnd = line.chapterPosition + line.text.length +
|
|
|
+ if (line.isParagraphEnd) 1 else 0
|
|
|
+ if (chapterPosition < line.chapterPosition) {
|
|
|
+ return@forEachIndexed
|
|
|
+ }
|
|
|
+ if (preferPreviousAtBoundary && chapterPosition == line.chapterPosition && index > 0) {
|
|
|
+ val previousLine = page.lines[index - 1]
|
|
|
+ return SearchLinePosition(index - 1, previousLine.text.length)
|
|
|
+ }
|
|
|
+ if (chapterPosition <= lineEnd) {
|
|
|
+ return SearchLinePosition(
|
|
|
+ lineIndex = index,
|
|
|
+ charIndex = (chapterPosition - line.chapterPosition).coerceIn(0, line.text.length),
|
|
|
+ )
|
|
|
+ }
|
|
|
+ fallbackIndex = index
|
|
|
}
|
|
|
- return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2)
|
|
|
+ val lineIndex = fallbackIndex
|
|
|
+ val line = page.lines[lineIndex]
|
|
|
+ return SearchLinePosition(
|
|
|
+ lineIndex = lineIndex,
|
|
|
+ charIndex = (chapterPosition - line.chapterPosition).coerceIn(0, line.text.length),
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
+ private data class SearchLinePosition(
|
|
|
+ val lineIndex: Int,
|
|
|
+ val charIndex: Int,
|
|
|
+ )
|
|
|
+
|
|
|
/**
|
|
|
* Compute the search result position and emit [ReadBookEffect.NavigateToSearchResult]
|
|
|
* so the Controller can navigate and highlight.
|
|
|
@@ -2264,15 +2311,8 @@ class ReadBookViewModel(
|
|
|
val pos = searchResultPositions(textChapter, result, query)
|
|
|
val lineIndex = pos[1]
|
|
|
val charIndex = pos[2]
|
|
|
- val addLine = pos[3]
|
|
|
- val charIndex2 = pos[4]
|
|
|
- val queryLength = query.length
|
|
|
- val endLineIndex = lineIndex + addLine.coerceAtLeast(0)
|
|
|
- val endCharIndex = if (addLine == 0) {
|
|
|
- charIndex + queryLength
|
|
|
- } else {
|
|
|
- charIndex2 + 1
|
|
|
- }
|
|
|
+ val endLineIndex = pos[3]
|
|
|
+ val endCharIndex = pos[4]
|
|
|
_effects.tryEmit(
|
|
|
ReadBookEffect.NavigateToSearchResult(
|
|
|
result = result,
|