mirror of
https://github.com/gedoor/legado.git
synced 2024-08-30 09:23:26 +08:00
优化
This commit is contained in:
parent
abdc23967c
commit
946cd1ab94
@ -282,6 +282,10 @@ data class Book(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBookSource(): BookSource? {
|
||||||
|
return appDb.bookSourceDao.getBookSource(origin)
|
||||||
|
}
|
||||||
|
|
||||||
fun toSearchBook() = SearchBook(
|
fun toSearchBook() = SearchBook(
|
||||||
name = name,
|
name = name,
|
||||||
author = author,
|
author = author,
|
||||||
|
@ -124,7 +124,7 @@ object BookHelp {
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
saveText(book, bookChapter, content)
|
saveText(book, bookChapter, content)
|
||||||
saveImages(bookSource, book, bookChapter, content)
|
//saveImages(bookSource, book, bookChapter, content)
|
||||||
postEvent(EventBus.SAVE_CONTENT, Pair(book, bookChapter))
|
postEvent(EventBus.SAVE_CONTENT, Pair(book, bookChapter))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
@ -145,6 +145,9 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearTextChapter() {
|
fun clearTextChapter() {
|
||||||
|
prevTextChapter?.cancelLayout()
|
||||||
|
curTextChapter?.cancelLayout()
|
||||||
|
nextTextChapter?.cancelLayout()
|
||||||
prevTextChapter = null
|
prevTextChapter = null
|
||||||
curTextChapter = null
|
curTextChapter = null
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@ -210,6 +213,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex < chapterSize - 1) {
|
if (durChapterIndex < chapterSize - 1) {
|
||||||
durChapterPos = 0
|
durChapterPos = 0
|
||||||
durChapterIndex++
|
durChapterIndex++
|
||||||
|
prevTextChapter?.cancelLayout()
|
||||||
prevTextChapter = curTextChapter
|
prevTextChapter = curTextChapter
|
||||||
curTextChapter = nextTextChapter
|
curTextChapter = nextTextChapter
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@ -226,6 +230,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
AppLog.putDebug("moveToNextChapter-curPageChanged()")
|
AppLog.putDebug("moveToNextChapter-curPageChanged()")
|
||||||
curPageChanged()
|
curPageChanged()
|
||||||
|
curTextChapter?.let { callBack?.onCurrentTextChapterChanged(it) }
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
AppLog.putDebug("跳转下一章失败,没有下一章")
|
AppLog.putDebug("跳转下一章失败,没有下一章")
|
||||||
@ -241,6 +246,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex > 0) {
|
if (durChapterIndex > 0) {
|
||||||
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
||||||
durChapterIndex--
|
durChapterIndex--
|
||||||
|
nextTextChapter?.cancelLayout()
|
||||||
nextTextChapter = curTextChapter
|
nextTextChapter = curTextChapter
|
||||||
curTextChapter = prevTextChapter
|
curTextChapter = prevTextChapter
|
||||||
prevTextChapter = null
|
prevTextChapter = null
|
||||||
@ -254,6 +260,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
saveRead()
|
saveRead()
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
curPageChanged()
|
curPageChanged()
|
||||||
|
curTextChapter?.let { callBack?.onCurrentTextChapterChanged(it) }
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -477,23 +484,33 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
)
|
)
|
||||||
val contents = contentProcessor
|
val contents = contentProcessor
|
||||||
.getContent(book, chapter, content, includeTitle = false)
|
.getContent(book, chapter, content, includeTitle = false)
|
||||||
val textChapter = ChapterProvider
|
val textChapter = ChapterProvider.getTextChapterAsync(
|
||||||
.getTextChapter(book, chapter, displayTitle, contents, chapterSize)
|
book,
|
||||||
|
chapter,
|
||||||
|
displayTitle,
|
||||||
|
contents,
|
||||||
|
chapterSize,
|
||||||
|
this@ReadBook
|
||||||
|
)
|
||||||
when (val offset = chapter.index - durChapterIndex) {
|
when (val offset = chapter.index - durChapterIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
|
curTextChapter?.cancelLayout()
|
||||||
curTextChapter = textChapter
|
curTextChapter = textChapter
|
||||||
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
curPageChanged()
|
curPageChanged()
|
||||||
callBack?.contentLoadFinish()
|
callBack?.contentLoadFinish()
|
||||||
|
callBack?.onCurrentTextChapterChanged(textChapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
-1 -> {
|
-1 -> {
|
||||||
|
prevTextChapter?.cancelLayout()
|
||||||
prevTextChapter = textChapter
|
prevTextChapter = textChapter
|
||||||
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
|
nextTextChapter?.cancelLayout()
|
||||||
nextTextChapter = textChapter
|
nextTextChapter = textChapter
|
||||||
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
}
|
}
|
||||||
@ -632,6 +649,8 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
fun upPageAnim()
|
fun upPageAnim()
|
||||||
|
|
||||||
fun notifyBookChanged()
|
fun notifyBookChanged()
|
||||||
|
|
||||||
|
fun onCurrentTextChapterChanged(textChapter: TextChapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,10 @@ import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_C
|
|||||||
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.ReadView
|
import io.legado.app.ui.book.read.page.ReadView
|
||||||
import io.legado.app.ui.book.read.page.entities.PageDirection
|
import io.legado.app.ui.book.read.page.entities.PageDirection
|
||||||
|
import io.legado.app.ui.book.read.page.entities.TextChapter
|
||||||
|
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||||
|
import io.legado.app.ui.book.read.page.provider.LayoutProgressListener
|
||||||
import io.legado.app.ui.book.searchContent.SearchContentActivity
|
import io.legado.app.ui.book.searchContent.SearchContentActivity
|
||||||
import io.legado.app.ui.book.searchContent.SearchResult
|
import io.legado.app.ui.book.searchContent.SearchResult
|
||||||
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
||||||
@ -112,8 +115,10 @@ import io.legado.app.utils.observeEvent
|
|||||||
import io.legado.app.utils.observeEventSticky
|
import io.legado.app.utils.observeEventSticky
|
||||||
import io.legado.app.utils.postEvent
|
import io.legado.app.utils.postEvent
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
|
import io.legado.app.utils.stackTraceStr
|
||||||
import io.legado.app.utils.startActivity
|
import io.legado.app.utils.startActivity
|
||||||
import io.legado.app.utils.sysScreenOffTime
|
import io.legado.app.utils.sysScreenOffTime
|
||||||
|
import io.legado.app.utils.throttle
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import io.legado.app.utils.visible
|
import io.legado.app.utils.visible
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
@ -139,7 +144,8 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
ReadBook.CallBack,
|
ReadBook.CallBack,
|
||||||
AutoReadDialog.CallBack,
|
AutoReadDialog.CallBack,
|
||||||
TxtTocRuleDialog.CallBack,
|
TxtTocRuleDialog.CallBack,
|
||||||
ColorPickerDialogListener {
|
ColorPickerDialogListener,
|
||||||
|
LayoutProgressListener {
|
||||||
|
|
||||||
private val tocActivity =
|
private val tocActivity =
|
||||||
registerForActivityResult(TocActivityResult()) {
|
registerForActivityResult(TocActivityResult()) {
|
||||||
@ -221,6 +227,12 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
private val handler by lazy { buildMainHandler() }
|
private val handler by lazy { buildMainHandler() }
|
||||||
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
|
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
|
||||||
private val executor = ReadBook.executor
|
private val executor = ReadBook.executor
|
||||||
|
private val upSeekBarThrotle = throttle(200) {
|
||||||
|
runOnUiThread {
|
||||||
|
upSeekBarProgress()
|
||||||
|
binding.readMenu.upSeekBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//恢复跳转前进度对话框的交互结果
|
//恢复跳转前进度对话框的交互结果
|
||||||
private var confirmRestoreProcess: Boolean? = null
|
private var confirmRestoreProcess: Boolean? = null
|
||||||
@ -1357,6 +1369,25 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
binding.readView.autoPager.resume()
|
binding.readView.autoPager.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCurrentTextChapterChanged(textChapter: TextChapter) {
|
||||||
|
textChapter.setProgressListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutPageCompleted(index: Int, page: TextPage) {
|
||||||
|
upSeekBarThrotle.invoke()
|
||||||
|
binding.readView.onLayoutPageCompleted(index, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutCompleted() {
|
||||||
|
binding.readView.onLayoutCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutException(e: Throwable) {
|
||||||
|
AppLog.put("ChapterProvider ERROR", e)
|
||||||
|
toastOnUi("ChapterProvider ERROR:\n${e.stackTraceStr}")
|
||||||
|
binding.readView.onLayoutException(e)
|
||||||
|
}
|
||||||
|
|
||||||
/* 全文搜索跳转 */
|
/* 全文搜索跳转 */
|
||||||
private fun skipToSearch(searchResult: SearchResult) {
|
private fun skipToSearch(searchResult: SearchResult) {
|
||||||
val previousResult = binding.searchMenu.previousSearchResult
|
val previousResult = binding.searchMenu.previousSearchResult
|
||||||
|
@ -655,7 +655,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
|
|
||||||
fun createBookmark(): Bookmark? {
|
fun createBookmark(): Bookmark? {
|
||||||
val page = relativePage(selectStart.relativePagePos)
|
val page = relativePage(selectStart.relativePagePos)
|
||||||
page.getTextChapter()?.let { chapter ->
|
page.getTextChapter().let { chapter ->
|
||||||
ReadBook.book?.let { book ->
|
ReadBook.book?.let { book ->
|
||||||
return book.createBookMark().apply {
|
return book.createBookMark().apply {
|
||||||
chapterIndex = page.chapterIndex
|
chapterIndex = page.chapterIndex
|
||||||
|
@ -309,11 +309,18 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
fun setProgress(textPage: TextPage) = textPage.apply {
|
fun setProgress(textPage: TextPage) = textPage.apply {
|
||||||
tvBookName?.setTextIfNotEqual(ReadBook.book?.name)
|
tvBookName?.setTextIfNotEqual(ReadBook.book?.name)
|
||||||
tvTitle?.setTextIfNotEqual(textPage.title)
|
tvTitle?.setTextIfNotEqual(textPage.title)
|
||||||
tvPage?.setTextIfNotEqual("${index.plus(1)}/$pageSize")
|
|
||||||
val readProgress = readProgress
|
val readProgress = readProgress
|
||||||
tvTotalProgress?.setTextIfNotEqual(readProgress)
|
tvTotalProgress?.setTextIfNotEqual(readProgress)
|
||||||
tvTotalProgress1?.setTextIfNotEqual("${chapterIndex.plus(1)}/${chapterSize}")
|
tvTotalProgress1?.setTextIfNotEqual("${chapterIndex.plus(1)}/${chapterSize}")
|
||||||
|
if (textChapter.isCompleted) {
|
||||||
tvPageAndTotal?.setTextIfNotEqual("${index.plus(1)}/$pageSize $readProgress")
|
tvPageAndTotal?.setTextIfNotEqual("${index.plus(1)}/$pageSize $readProgress")
|
||||||
|
tvPage?.setTextIfNotEqual("${index.plus(1)}/$pageSize")
|
||||||
|
} else {
|
||||||
|
val pageSizeInt = pageSize - 1
|
||||||
|
val pageSize = if (pageSizeInt <= 0) "-" else "~$pageSizeInt"
|
||||||
|
tvPageAndTotal?.setTextIfNotEqual("${index.plus(1)}/$pageSize $readProgress")
|
||||||
|
tvPage?.setTextIfNotEqual("${index.plus(1)}/$pageSize")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAutoPager(autoPager: AutoPager?) {
|
fun setAutoPager(autoPager: AutoPager?) {
|
||||||
|
@ -28,10 +28,12 @@ import io.legado.app.ui.book.read.page.entities.TextChapter
|
|||||||
import io.legado.app.ui.book.read.page.entities.TextPage
|
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||||
import io.legado.app.ui.book.read.page.entities.TextPos
|
import io.legado.app.ui.book.read.page.entities.TextPos
|
||||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||||
|
import io.legado.app.ui.book.read.page.provider.LayoutProgressListener
|
||||||
import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
||||||
import io.legado.app.utils.activity
|
import io.legado.app.utils.activity
|
||||||
import io.legado.app.utils.invisible
|
import io.legado.app.utils.invisible
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
|
import io.legado.app.utils.throttle
|
||||||
import java.text.BreakIterator
|
import java.text.BreakIterator
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@ -41,7 +43,7 @@ import kotlin.math.abs
|
|||||||
*/
|
*/
|
||||||
class ReadView(context: Context, attrs: AttributeSet) :
|
class ReadView(context: Context, attrs: AttributeSet) :
|
||||||
FrameLayout(context, attrs),
|
FrameLayout(context, attrs),
|
||||||
DataSource {
|
DataSource, LayoutProgressListener {
|
||||||
|
|
||||||
val callBack: CallBack get() = activity as CallBack
|
val callBack: CallBack get() = activity as CallBack
|
||||||
var pageFactory: TextPageFactory = TextPageFactory(this)
|
var pageFactory: TextPageFactory = TextPageFactory(this)
|
||||||
@ -99,6 +101,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
private val bcRect = RectF()
|
private val bcRect = RectF()
|
||||||
private val brRect = RectF()
|
private val brRect = RectF()
|
||||||
private val boundary by lazy { BreakIterator.getWordInstance(Locale.getDefault()) }
|
private val boundary by lazy { BreakIterator.getWordInstance(Locale.getDefault()) }
|
||||||
|
private val upProgressThrottle = throttle(200) { post { upProgress() } }
|
||||||
val autoPager = AutoPager(this)
|
val autoPager = AutoPager(this)
|
||||||
val isAutoPage get() = autoPager.isRunning
|
val isAutoPage get() = autoPager.isRunning
|
||||||
|
|
||||||
@ -539,6 +542,10 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
callBack.screenOffTimerStart()
|
callBack.screenOffTimerStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun upProgress() {
|
||||||
|
curPage.setProgress(pageFactory.curPage)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新滑动距离
|
* 更新滑动距离
|
||||||
*/
|
*/
|
||||||
@ -654,6 +661,37 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
curPage.submitRenderTask()
|
curPage.submitRenderTask()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLayoutPageCompleted(index: Int, page: TextPage) {
|
||||||
|
val line = page.lines.first()
|
||||||
|
val durChapterPos = ReadBook.durChapterPos
|
||||||
|
val startPos = line.chapterPosition
|
||||||
|
val endPos = line.chapterPosition + line.charSize
|
||||||
|
if (durChapterPos in startPos..<endPos) {
|
||||||
|
post {
|
||||||
|
upContent(resetPageOffset = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isScroll) {
|
||||||
|
val pageIndex = ReadBook.durPageIndex
|
||||||
|
if (index - 3 < pageIndex) {
|
||||||
|
post {
|
||||||
|
upContent(resetPageOffset = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
upProgressThrottle.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutCompleted() {
|
||||||
|
post {
|
||||||
|
upContent(resetPageOffset = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutException(e: Throwable) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
override val currentChapter: TextChapter?
|
override val currentChapter: TextChapter?
|
||||||
get() {
|
get() {
|
||||||
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
|
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
|
||||||
|
@ -2,9 +2,14 @@ package io.legado.app.ui.book.read.page.entities
|
|||||||
|
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import io.legado.app.data.entities.Book
|
||||||
import io.legado.app.data.entities.BookChapter
|
import io.legado.app.data.entities.BookChapter
|
||||||
import io.legado.app.data.entities.ReplaceRule
|
import io.legado.app.data.entities.ReplaceRule
|
||||||
|
import io.legado.app.help.book.BookContent
|
||||||
|
import io.legado.app.ui.book.read.page.provider.LayoutProgressListener
|
||||||
|
import io.legado.app.ui.book.read.page.provider.TextChapterLayout
|
||||||
import io.legado.app.utils.fastBinarySearchBy
|
import io.legado.app.utils.fastBinarySearchBy
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@ -17,14 +22,18 @@ data class TextChapter(
|
|||||||
val chapter: BookChapter,
|
val chapter: BookChapter,
|
||||||
val position: Int,
|
val position: Int,
|
||||||
val title: String,
|
val title: String,
|
||||||
val pages: List<TextPage>,
|
|
||||||
val chaptersSize: Int,
|
val chaptersSize: Int,
|
||||||
val sameTitleRemoved: Boolean,
|
val sameTitleRemoved: Boolean,
|
||||||
val isVip: Boolean,
|
val isVip: Boolean,
|
||||||
val isPay: Boolean,
|
val isPay: Boolean,
|
||||||
//起效的替换规则
|
//起效的替换规则
|
||||||
val effectiveReplaceRules: List<ReplaceRule>?
|
val effectiveReplaceRules: List<ReplaceRule>?
|
||||||
) {
|
) : LayoutProgressListener {
|
||||||
|
|
||||||
|
private val textPages = arrayListOf<TextPage>()
|
||||||
|
val pages: List<TextPage> get() = textPages
|
||||||
|
|
||||||
|
var layout: TextChapterLayout? = null
|
||||||
|
|
||||||
fun getPage(index: Int): TextPage? {
|
fun getPage(index: Int): TextPage? {
|
||||||
return pages.getOrNull(index)
|
return pages.getOrNull(index)
|
||||||
@ -42,6 +51,10 @@ data class TextChapter(
|
|||||||
|
|
||||||
val pageSize: Int get() = pages.size
|
val pageSize: Int get() = pages.size
|
||||||
|
|
||||||
|
var listener: LayoutProgressListener? = null
|
||||||
|
|
||||||
|
var isCompleted = false
|
||||||
|
|
||||||
val paragraphs by lazy {
|
val paragraphs by lazy {
|
||||||
paragraphsInternal
|
paragraphsInternal
|
||||||
}
|
}
|
||||||
@ -79,7 +92,12 @@ data class TextChapter(
|
|||||||
* @return 是否是最后一页
|
* @return 是否是最后一页
|
||||||
*/
|
*/
|
||||||
fun isLastIndex(index: Int): Boolean {
|
fun isLastIndex(index: Int): Boolean {
|
||||||
return index >= pages.size - 1
|
return isCompleted && index >= pages.size - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isLastIndexCurrent(index: Int): Boolean {
|
||||||
|
// 未完成排版时,最后一页是正在排版中的,需要去掉
|
||||||
|
return index >= if (isCompleted) pages.size - 1 else pages.size - 2
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -190,15 +208,28 @@ data class TextChapter(
|
|||||||
* @return 根据索引位置获取所在页
|
* @return 根据索引位置获取所在页
|
||||||
*/
|
*/
|
||||||
fun getPageIndexByCharIndex(charIndex: Int): Int {
|
fun getPageIndexByCharIndex(charIndex: Int): Int {
|
||||||
val index = pages.fastBinarySearchBy(charIndex) {
|
val pageSize = pages.size
|
||||||
|
if (pageSize == 0) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
val size = if (isCompleted) pageSize else pageSize - 1
|
||||||
|
val bIndex = pages.fastBinarySearchBy(charIndex, 0, size) {
|
||||||
it.lines.first().chapterPosition
|
it.lines.first().chapterPosition
|
||||||
}
|
}
|
||||||
return if (index >= 0) {
|
val index = abs(bIndex + 1) - 1
|
||||||
index
|
if (index == -1) {
|
||||||
} else {
|
return -1
|
||||||
abs(index + 1) - 1
|
|
||||||
}
|
}
|
||||||
/* 相当于以下实现
|
// 判断是否已经排版到 charIndex ,没有则返回 -1
|
||||||
|
if (!isCompleted && index == size - 1) {
|
||||||
|
val line = pages[index].lines.first()
|
||||||
|
val pageEndPos = line.chapterPosition + line.charSize
|
||||||
|
if (charIndex > pageEndPos) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
/*
|
||||||
var length = 0
|
var length = 0
|
||||||
for (i in pages.indices) {
|
for (i in pages.indices) {
|
||||||
val page = pages[i]
|
val page = pages[i]
|
||||||
@ -222,4 +253,52 @@ data class TextChapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createLayout(scope: CoroutineScope, book: Book, bookContent: BookContent) {
|
||||||
|
textPages.clear()
|
||||||
|
layout = TextChapterLayout(
|
||||||
|
scope,
|
||||||
|
this,
|
||||||
|
textPages,
|
||||||
|
book,
|
||||||
|
bookContent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setProgressListener(l: LayoutProgressListener) {
|
||||||
|
if (isCompleted) {
|
||||||
|
l.onLayoutCompleted()
|
||||||
|
} else {
|
||||||
|
listener = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutPageCompleted(index: Int, page: TextPage) {
|
||||||
|
listener?.onLayoutPageCompleted(index, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutCompleted() {
|
||||||
|
isCompleted = true
|
||||||
|
listener?.onLayoutCompleted()
|
||||||
|
listener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutException(e: Throwable) {
|
||||||
|
listener?.onLayoutException(e)
|
||||||
|
listener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelLayout() {
|
||||||
|
layout?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val emptyTextChapter = TextChapter(
|
||||||
|
BookChapter(), -1, "emptyTextChapter", -1,
|
||||||
|
sameTitleRemoved = false,
|
||||||
|
isVip = false,
|
||||||
|
isPay = false,
|
||||||
|
null
|
||||||
|
).apply { isCompleted = true }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@ 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.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
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.TextChapter.Companion.emptyTextChapter
|
||||||
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.canvasrecorder.CanvasRecorderFactory
|
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
|
||||||
@ -27,7 +27,6 @@ data class TextPage(
|
|||||||
var text: String = appCtx.getString(R.string.data_loading),
|
var text: String = appCtx.getString(R.string.data_loading),
|
||||||
var title: String = appCtx.getString(R.string.data_loading),
|
var title: String = appCtx.getString(R.string.data_loading),
|
||||||
private val textLines: ArrayList<TextLine> = arrayListOf(),
|
private val textLines: ArrayList<TextLine> = arrayListOf(),
|
||||||
var pageSize: Int = 0,
|
|
||||||
var chapterSize: Int = 0,
|
var chapterSize: Int = 0,
|
||||||
var chapterIndex: Int = 0,
|
var chapterIndex: Int = 0,
|
||||||
var height: Float = 0f,
|
var height: Float = 0f,
|
||||||
@ -47,6 +46,10 @@ data class TextPage(
|
|||||||
var canvasRecorder = CanvasRecorderFactory.create(true)
|
var canvasRecorder = CanvasRecorderFactory.create(true)
|
||||||
var doublePage = false
|
var doublePage = false
|
||||||
var paddingTop = 0
|
var paddingTop = 0
|
||||||
|
var isCompleted = false
|
||||||
|
@JvmField
|
||||||
|
var textChapter = emptyTextChapter
|
||||||
|
val pageSize get() = textChapter.pageSize
|
||||||
|
|
||||||
val paragraphs by lazy {
|
val paragraphs by lazy {
|
||||||
paragraphsInternal
|
paragraphsInternal
|
||||||
@ -162,6 +165,7 @@ data class TextPage(
|
|||||||
}
|
}
|
||||||
height = ChapterProvider.visibleHeight.toFloat()
|
height = ChapterProvider.visibleHeight.toFloat()
|
||||||
invalidate()
|
invalidate()
|
||||||
|
isCompleted = true
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@ -248,23 +252,8 @@ data class TextPage(
|
|||||||
/**
|
/**
|
||||||
* @return 页面所在章节
|
* @return 页面所在章节
|
||||||
*/
|
*/
|
||||||
fun getTextChapter(): TextChapter? {
|
fun getTextChapter(): TextChapter {
|
||||||
ReadBook.curTextChapter?.let {
|
return textChapter
|
||||||
if (it.position == chapterIndex) {
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReadBook.nextTextChapter?.let {
|
|
||||||
if (it.position == chapterIndex) {
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReadBook.prevTextChapter?.let {
|
|
||||||
if (it.position == chapterIndex) {
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {
|
fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {
|
||||||
@ -284,6 +273,7 @@ data class TextPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun render(view: ContentTextView): Boolean {
|
fun render(view: ContentTextView): Boolean {
|
||||||
|
if (!isCompleted) return false
|
||||||
return canvasRecorder.recordIfNeeded(view.width, height.toInt()) {
|
return canvasRecorder.recordIfNeeded(view.width, height.toInt()) {
|
||||||
drawPage(view, this)
|
drawPage(view, this)
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import io.legado.app.utils.postEvent
|
|||||||
import io.legado.app.utils.spToPx
|
import io.legado.app.utils.spToPx
|
||||||
import io.legado.app.utils.splitNotBlank
|
import io.legado.app.utils.splitNotBlank
|
||||||
import io.legado.app.utils.textHeight
|
import io.legado.app.utils.textHeight
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -42,12 +43,12 @@ import java.util.Locale
|
|||||||
@Suppress("DEPRECATION", "ConstPropertyName")
|
@Suppress("DEPRECATION", "ConstPropertyName")
|
||||||
object ChapterProvider {
|
object ChapterProvider {
|
||||||
//用于图片字的替换
|
//用于图片字的替换
|
||||||
private const val srcReplaceChar = "▩"
|
const val srcReplaceChar = "▩"
|
||||||
|
|
||||||
//用于评论按钮的替换
|
//用于评论按钮的替换
|
||||||
private const val reviewChar = "▨"
|
const val reviewChar = "▨"
|
||||||
|
|
||||||
private const val indentChar = " "
|
const val indentChar = " "
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
var viewWidth = 0
|
var viewWidth = 0
|
||||||
@ -94,25 +95,31 @@ object ChapterProvider {
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private var paragraphSpacing = 0
|
var paragraphSpacing = 0
|
||||||
|
private set
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private var titleTopSpacing = 0
|
var titleTopSpacing = 0
|
||||||
|
private set
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private var titleBottomSpacing = 0
|
var titleBottomSpacing = 0
|
||||||
|
private set
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private var indentCharWidth = 0f
|
var indentCharWidth = 0f
|
||||||
|
private set
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
var titlePaintTextHeight = 0f
|
var titlePaintTextHeight = 0f
|
||||||
|
private set
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
var contentPaintTextHeight = 0f
|
var contentPaintTextHeight = 0f
|
||||||
|
private set
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private var titlePaintFontMetrics = FontMetrics()
|
var titlePaintFontMetrics = FontMetrics()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
var contentPaintFontMetrics = FontMetrics()
|
var contentPaintFontMetrics = FontMetrics()
|
||||||
@ -278,7 +285,7 @@ object ChapterProvider {
|
|||||||
textPage.text = stringBuilder.toString()
|
textPage.text = stringBuilder.toString()
|
||||||
textPages.forEachIndexed { index, item ->
|
textPages.forEachIndexed { index, item ->
|
||||||
item.index = index
|
item.index = index
|
||||||
item.pageSize = textPages.size
|
//item.pageSize = textPages.size
|
||||||
item.chapterIndex = bookChapter.index
|
item.chapterIndex = bookChapter.index
|
||||||
item.chapterSize = chapterSize
|
item.chapterSize = chapterSize
|
||||||
item.title = displayTitle
|
item.title = displayTitle
|
||||||
@ -290,7 +297,8 @@ object ChapterProvider {
|
|||||||
return TextChapter(
|
return TextChapter(
|
||||||
bookChapter,
|
bookChapter,
|
||||||
bookChapter.index, displayTitle,
|
bookChapter.index, displayTitle,
|
||||||
textPages, chapterSize,
|
//textPages,
|
||||||
|
chapterSize,
|
||||||
bookContent.sameTitleRemoved,
|
bookContent.sameTitleRemoved,
|
||||||
bookChapter.isVip,
|
bookChapter.isVip,
|
||||||
bookChapter.isPay,
|
bookChapter.isPay,
|
||||||
@ -298,6 +306,30 @@ object ChapterProvider {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTextChapterAsync(
|
||||||
|
book: Book,
|
||||||
|
bookChapter: BookChapter,
|
||||||
|
displayTitle: String,
|
||||||
|
bookContent: BookContent,
|
||||||
|
chapterSize: Int,
|
||||||
|
scope: CoroutineScope
|
||||||
|
): TextChapter {
|
||||||
|
|
||||||
|
val textChapter = TextChapter(
|
||||||
|
bookChapter,
|
||||||
|
bookChapter.index, displayTitle,
|
||||||
|
chapterSize,
|
||||||
|
bookContent.sameTitleRemoved,
|
||||||
|
bookChapter.isVip,
|
||||||
|
bookChapter.isPay,
|
||||||
|
bookContent.effectiveReplaceRules
|
||||||
|
).apply {
|
||||||
|
createLayout(scope, book, bookContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return textChapter
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 排版图片
|
* 排版图片
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package io.legado.app.ui.book.read.page.provider
|
||||||
|
|
||||||
|
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||||
|
|
||||||
|
interface LayoutProgressListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单页排版完成
|
||||||
|
*/
|
||||||
|
fun onLayoutPageCompleted(index: Int, page: TextPage)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全部排版完成
|
||||||
|
*/
|
||||||
|
fun onLayoutCompleted()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排版出现异常
|
||||||
|
*/
|
||||||
|
fun onLayoutException(e: Throwable)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,752 @@
|
|||||||
|
package io.legado.app.ui.book.read.page.provider
|
||||||
|
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextPaint
|
||||||
|
import io.legado.app.constant.AppLog
|
||||||
|
import io.legado.app.constant.AppPattern
|
||||||
|
import io.legado.app.data.entities.Book
|
||||||
|
import io.legado.app.data.entities.BookChapter
|
||||||
|
import io.legado.app.help.book.BookContent
|
||||||
|
import io.legado.app.help.book.BookHelp
|
||||||
|
import io.legado.app.help.config.AppConfig
|
||||||
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
|
import io.legado.app.model.ImageProvider
|
||||||
|
import io.legado.app.model.ReadBook
|
||||||
|
import io.legado.app.ui.book.read.page.entities.TextChapter
|
||||||
|
import io.legado.app.ui.book.read.page.entities.TextLine
|
||||||
|
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||||
|
import io.legado.app.ui.book.read.page.entities.column.ImageColumn
|
||||||
|
import io.legado.app.ui.book.read.page.entities.column.ReviewColumn
|
||||||
|
import io.legado.app.ui.book.read.page.entities.column.TextColumn
|
||||||
|
import io.legado.app.utils.dpToPx
|
||||||
|
import io.legado.app.utils.fastSum
|
||||||
|
import io.legado.app.utils.splitNotBlank
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class TextChapterLayout(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
private val textChapter: TextChapter,
|
||||||
|
private val textPages: ArrayList<TextPage>,
|
||||||
|
private val book: Book,
|
||||||
|
private val bookContent: BookContent,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var listener: LayoutProgressListener? = textChapter
|
||||||
|
|
||||||
|
private val paddingLeft = ChapterProvider.paddingLeft
|
||||||
|
private val paddingTop = ChapterProvider.paddingTop
|
||||||
|
|
||||||
|
private val titlePaint = ChapterProvider.titlePaint
|
||||||
|
private val titlePaintTextHeight = ChapterProvider.titlePaintTextHeight
|
||||||
|
private val titlePaintFontMetrics = ChapterProvider.titlePaintFontMetrics
|
||||||
|
|
||||||
|
private val contentPaint = ChapterProvider.contentPaint
|
||||||
|
private val contentPaintTextHeight = ChapterProvider.contentPaintTextHeight
|
||||||
|
private val contentPaintFontMetrics = ChapterProvider.contentPaintFontMetrics
|
||||||
|
|
||||||
|
private val titleTopSpacing = ChapterProvider.titleTopSpacing
|
||||||
|
private val titleBottomSpacing = ChapterProvider.titleBottomSpacing
|
||||||
|
private val lineSpacingExtra = ChapterProvider.lineSpacingExtra
|
||||||
|
private val paragraphSpacing = ChapterProvider.paragraphSpacing
|
||||||
|
|
||||||
|
private val visibleHeight = ChapterProvider.visibleHeight
|
||||||
|
private val visibleWidth = ChapterProvider.visibleWidth
|
||||||
|
|
||||||
|
private val viewWidth = ChapterProvider.viewWidth
|
||||||
|
private val doublePage = ChapterProvider.doublePage
|
||||||
|
private val indentCharWidth = ChapterProvider.indentCharWidth
|
||||||
|
private val stringBuilder = StringBuilder()
|
||||||
|
|
||||||
|
private var isCompleted = false
|
||||||
|
private var exception: Throwable? = null
|
||||||
|
private val job: Coroutine<*>
|
||||||
|
private val bookChapter inline get() = textChapter.chapter
|
||||||
|
private val displayTitle inline get() = textChapter.title
|
||||||
|
private val chaptersSize inline get() = textChapter.chaptersSize
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
job = Coroutine.async(scope) {
|
||||||
|
launch {
|
||||||
|
val bookSource = book.getBookSource() ?: return@launch
|
||||||
|
BookHelp.saveImages(bookSource, book, bookChapter, bookContent.toString())
|
||||||
|
}
|
||||||
|
getTextChapter(book, bookChapter, displayTitle, bookContent)
|
||||||
|
}.onError {
|
||||||
|
exception = it
|
||||||
|
onException(it)
|
||||||
|
}.onFinally {
|
||||||
|
isCompleted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setProgressListener(l: LayoutProgressListener) {
|
||||||
|
try {
|
||||||
|
if (isCompleted) {
|
||||||
|
l.onLayoutCompleted()
|
||||||
|
} else if (exception != null) {
|
||||||
|
l.onLayoutException(exception!!)
|
||||||
|
} else {
|
||||||
|
listener = l
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
AppLog.put("调用布局进度监听回调出错\n${e.localizedMessage}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onPageCompleted() {
|
||||||
|
val textPage = textPages.last()
|
||||||
|
textPage.index = textPages.lastIndex
|
||||||
|
textPage.chapterIndex = bookChapter.index
|
||||||
|
textPage.chapterSize = chaptersSize
|
||||||
|
textPage.title = displayTitle
|
||||||
|
textPage.doublePage = doublePage
|
||||||
|
textPage.paddingTop = paddingTop
|
||||||
|
textPage.isCompleted = true
|
||||||
|
textPage.textChapter = textChapter
|
||||||
|
textPage.upLinesPosition()
|
||||||
|
try {
|
||||||
|
listener?.onLayoutPageCompleted(textPages.lastIndex, textPage)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
AppLog.put("调用布局进度监听回调出错\n${e.localizedMessage}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCompleted() {
|
||||||
|
try {
|
||||||
|
listener?.onLayoutCompleted()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
AppLog.put("调用布局进度监听回调出错\n${e.localizedMessage}", e)
|
||||||
|
} finally {
|
||||||
|
listener = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onException(e: Throwable) {
|
||||||
|
if (e is CancellationException) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
listener?.onLayoutException(e)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
AppLog.put("调用布局进度监听回调出错\n${e.localizedMessage}", e)
|
||||||
|
} finally {
|
||||||
|
listener = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取拆分完的章节数据
|
||||||
|
*/
|
||||||
|
private suspend fun getTextChapter(
|
||||||
|
book: Book,
|
||||||
|
bookChapter: BookChapter,
|
||||||
|
displayTitle: String,
|
||||||
|
bookContent: BookContent,
|
||||||
|
) {
|
||||||
|
val contents = bookContent.textList
|
||||||
|
var absStartX = paddingLeft
|
||||||
|
var durY = 0f
|
||||||
|
textPages.add(TextPage())
|
||||||
|
if (ReadBookConfig.titleMode != 2 || bookChapter.isVolume) {
|
||||||
|
//标题非隐藏
|
||||||
|
displayTitle.splitNotBlank("\n").forEach { text ->
|
||||||
|
setTypeText(
|
||||||
|
book, absStartX, durY,
|
||||||
|
if (AppConfig.enableReview) text + ChapterProvider.reviewChar else text,
|
||||||
|
titlePaint,
|
||||||
|
titlePaintTextHeight,
|
||||||
|
titlePaintFontMetrics,
|
||||||
|
isTitle = true,
|
||||||
|
emptyContent = contents.isEmpty(),
|
||||||
|
isVolumeTitle = bookChapter.isVolume
|
||||||
|
).let {
|
||||||
|
absStartX = it.first
|
||||||
|
durY = it.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textPages.last().lines.last().isParagraphEnd = true
|
||||||
|
stringBuilder.append("\n")
|
||||||
|
durY += titleBottomSpacing
|
||||||
|
}
|
||||||
|
val sb = StringBuffer()
|
||||||
|
contents.forEach { content ->
|
||||||
|
if (book.getImageStyle().equals(Book.imgStyleText, true)) {
|
||||||
|
//图片样式为文字嵌入类型
|
||||||
|
var text = content.replace(ChapterProvider.srcReplaceChar, "▣")
|
||||||
|
val srcList = LinkedList<String>()
|
||||||
|
sb.setLength(0)
|
||||||
|
val matcher = AppPattern.imgPattern.matcher(text)
|
||||||
|
while (matcher.find()) {
|
||||||
|
matcher.group(1)?.let { src ->
|
||||||
|
srcList.add(src)
|
||||||
|
matcher.appendReplacement(sb, ChapterProvider.srcReplaceChar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher.appendTail(sb)
|
||||||
|
text = sb.toString()
|
||||||
|
setTypeText(
|
||||||
|
book,
|
||||||
|
absStartX,
|
||||||
|
durY,
|
||||||
|
text,
|
||||||
|
contentPaint,
|
||||||
|
contentPaintTextHeight,
|
||||||
|
contentPaintFontMetrics,
|
||||||
|
srcList = srcList
|
||||||
|
).let {
|
||||||
|
absStartX = it.first
|
||||||
|
durY = it.second
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val matcher = AppPattern.imgPattern.matcher(content)
|
||||||
|
var start = 0
|
||||||
|
while (matcher.find()) {
|
||||||
|
val text = content.substring(start, matcher.start())
|
||||||
|
if (text.isNotBlank()) {
|
||||||
|
setTypeText(
|
||||||
|
book,
|
||||||
|
absStartX,
|
||||||
|
durY,
|
||||||
|
text,
|
||||||
|
contentPaint,
|
||||||
|
contentPaintTextHeight,
|
||||||
|
contentPaintFontMetrics
|
||||||
|
).let {
|
||||||
|
absStartX = it.first
|
||||||
|
durY = it.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTypeImage(
|
||||||
|
book,
|
||||||
|
matcher.group(1)!!,
|
||||||
|
absStartX,
|
||||||
|
durY,
|
||||||
|
textPages,
|
||||||
|
contentPaintTextHeight,
|
||||||
|
stringBuilder,
|
||||||
|
book.getImageStyle()
|
||||||
|
).let {
|
||||||
|
absStartX = it.first
|
||||||
|
durY = it.second
|
||||||
|
}
|
||||||
|
start = matcher.end()
|
||||||
|
}
|
||||||
|
if (start < content.length) {
|
||||||
|
val text = content.substring(start, content.length)
|
||||||
|
if (text.isNotBlank()) {
|
||||||
|
setTypeText(
|
||||||
|
book, absStartX, durY,
|
||||||
|
if (AppConfig.enableReview) text + ChapterProvider.reviewChar else text,
|
||||||
|
contentPaint,
|
||||||
|
contentPaintTextHeight,
|
||||||
|
contentPaintFontMetrics
|
||||||
|
).let {
|
||||||
|
absStartX = it.first
|
||||||
|
durY = it.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textPages.last().lines.last().isParagraphEnd = true
|
||||||
|
stringBuilder.append("\n")
|
||||||
|
}
|
||||||
|
val textPage = textPages.last()
|
||||||
|
val endPadding = 20.dpToPx()
|
||||||
|
val durYPadding = durY + endPadding
|
||||||
|
if (textPage.height < durYPadding) {
|
||||||
|
textPage.height = durYPadding
|
||||||
|
} else {
|
||||||
|
textPage.height += endPadding
|
||||||
|
}
|
||||||
|
textPage.text = stringBuilder.toString()
|
||||||
|
onPageCompleted()
|
||||||
|
onCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排版图片
|
||||||
|
*/
|
||||||
|
private suspend fun setTypeImage(
|
||||||
|
book: Book,
|
||||||
|
src: String,
|
||||||
|
x: Int,
|
||||||
|
y: Float,
|
||||||
|
textPages: ArrayList<TextPage>,
|
||||||
|
textHeight: Float,
|
||||||
|
stringBuilder: StringBuilder,
|
||||||
|
imageStyle: String?,
|
||||||
|
): Pair<Int, Float> {
|
||||||
|
var absStartX = x
|
||||||
|
var durY = y
|
||||||
|
val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)
|
||||||
|
if (size.width > 0 && size.height > 0) {
|
||||||
|
if (durY > visibleHeight) {
|
||||||
|
val textPage = textPages.last()
|
||||||
|
if (textPage.height < durY) {
|
||||||
|
textPage.height = durY
|
||||||
|
}
|
||||||
|
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
||||||
|
stringBuilder.clear()
|
||||||
|
onPageCompleted()
|
||||||
|
textPages.add(TextPage())
|
||||||
|
durY = 0f
|
||||||
|
}
|
||||||
|
var height = size.height
|
||||||
|
var width = size.width
|
||||||
|
when (imageStyle?.uppercase(Locale.ROOT)) {
|
||||||
|
Book.imgStyleFull -> {
|
||||||
|
width = visibleWidth
|
||||||
|
height = size.height * visibleWidth / size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (size.width > visibleWidth) {
|
||||||
|
height = size.height * visibleWidth / size.width
|
||||||
|
width = visibleWidth
|
||||||
|
}
|
||||||
|
if (height > visibleHeight) {
|
||||||
|
width = width * visibleHeight / height
|
||||||
|
height = visibleHeight
|
||||||
|
}
|
||||||
|
if (durY + height > visibleHeight) {
|
||||||
|
val textPage = textPages.last()
|
||||||
|
if (doublePage && absStartX < viewWidth / 2) {
|
||||||
|
//当前页面左列结束
|
||||||
|
textPage.leftLineSize = textPage.lineSize
|
||||||
|
absStartX = viewWidth / 2 + paddingLeft
|
||||||
|
} else {
|
||||||
|
//当前页面结束
|
||||||
|
if (textPage.leftLineSize == 0) {
|
||||||
|
textPage.leftLineSize = textPage.lineSize
|
||||||
|
}
|
||||||
|
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
||||||
|
stringBuilder.clear()
|
||||||
|
onPageCompleted()
|
||||||
|
textPages.add(TextPage())
|
||||||
|
}
|
||||||
|
// 双页的 durY 不正确,可能会小于实际高度
|
||||||
|
if (textPage.height < durY) {
|
||||||
|
textPage.height = durY
|
||||||
|
}
|
||||||
|
durY = 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val textLine = TextLine(isImage = true)
|
||||||
|
textLine.lineTop = durY + paddingTop
|
||||||
|
durY += height
|
||||||
|
textLine.lineBottom = durY + paddingTop
|
||||||
|
val (start, end) = if (visibleWidth > width) {
|
||||||
|
val adjustWidth = (visibleWidth - width) / 2f
|
||||||
|
Pair(adjustWidth, adjustWidth + width)
|
||||||
|
} else {
|
||||||
|
Pair(0f, width.toFloat())
|
||||||
|
}
|
||||||
|
textLine.addColumn(
|
||||||
|
ImageColumn(start = x + start, end = x + end, src = src)
|
||||||
|
)
|
||||||
|
calcTextLinePosition(textPages, textLine, stringBuilder.length)
|
||||||
|
stringBuilder.append(" ") // 确保翻页时索引计算正确
|
||||||
|
textPages.last().addLine(textLine)
|
||||||
|
}
|
||||||
|
return absStartX to durY + textHeight * paragraphSpacing / 10f
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排版文字
|
||||||
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
private suspend fun setTypeText(
|
||||||
|
book: Book,
|
||||||
|
x: Int,
|
||||||
|
y: Float,
|
||||||
|
text: String,
|
||||||
|
textPaint: TextPaint,
|
||||||
|
textHeight: Float,
|
||||||
|
fontMetrics: Paint.FontMetrics,
|
||||||
|
isTitle: Boolean = false,
|
||||||
|
emptyContent: Boolean = false,
|
||||||
|
isVolumeTitle: Boolean = false,
|
||||||
|
srcList: LinkedList<String>? = null
|
||||||
|
): Pair<Int, Float> {
|
||||||
|
var absStartX = x
|
||||||
|
val layout = if (ReadBookConfig.useZhLayout) {
|
||||||
|
ZhLayout(text, textPaint, visibleWidth)
|
||||||
|
} else {
|
||||||
|
StaticLayout(text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true)
|
||||||
|
}
|
||||||
|
var durY = when {
|
||||||
|
//标题y轴居中
|
||||||
|
emptyContent && textPages.size == 1 -> {
|
||||||
|
val textPage = textPages.last()
|
||||||
|
if (textPage.lineSize == 0) {
|
||||||
|
val ty = (visibleHeight - layout.lineCount * textHeight) / 2
|
||||||
|
if (ty > titleTopSpacing) ty else titleTopSpacing.toFloat()
|
||||||
|
} else {
|
||||||
|
var textLayoutHeight = layout.lineCount * textHeight
|
||||||
|
val fistLine = textPage.getLine(0)
|
||||||
|
if (fistLine.lineTop < textLayoutHeight + titleTopSpacing) {
|
||||||
|
textLayoutHeight = fistLine.lineTop - titleTopSpacing
|
||||||
|
}
|
||||||
|
textPage.lines.forEach {
|
||||||
|
it.lineTop -= textLayoutHeight
|
||||||
|
it.lineBase -= textLayoutHeight
|
||||||
|
it.lineBottom -= textLayoutHeight
|
||||||
|
}
|
||||||
|
y - textLayoutHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isTitle && textPages.size == 1 && textPages.last().lines.isEmpty() ->
|
||||||
|
y + titleTopSpacing
|
||||||
|
|
||||||
|
else -> y
|
||||||
|
}
|
||||||
|
for (lineIndex in 0 until layout.lineCount) {
|
||||||
|
val textLine = TextLine(isTitle = isTitle)
|
||||||
|
if (durY + textHeight > visibleHeight) {
|
||||||
|
val textPage = textPages.last()
|
||||||
|
if (doublePage && absStartX < viewWidth / 2) {
|
||||||
|
//当前页面左列结束
|
||||||
|
textPage.leftLineSize = textPage.lineSize
|
||||||
|
absStartX = viewWidth / 2 + paddingLeft
|
||||||
|
} else {
|
||||||
|
//当前页面结束,设置各种值
|
||||||
|
if (textPage.leftLineSize == 0) {
|
||||||
|
textPage.leftLineSize = textPage.lineSize
|
||||||
|
}
|
||||||
|
textPage.text = stringBuilder.toString()
|
||||||
|
onPageCompleted()
|
||||||
|
//新建页面
|
||||||
|
textPages.add(TextPage())
|
||||||
|
stringBuilder.clear()
|
||||||
|
absStartX = paddingLeft
|
||||||
|
}
|
||||||
|
if (textPage.height < durY) {
|
||||||
|
textPage.height = durY
|
||||||
|
}
|
||||||
|
durY = 0f
|
||||||
|
}
|
||||||
|
val lineStart = layout.getLineStart(lineIndex)
|
||||||
|
val lineEnd = layout.getLineEnd(lineIndex)
|
||||||
|
val lineText = text.substring(lineStart, lineEnd)
|
||||||
|
val (words, widths) = measureTextSplit(lineText, textPaint)
|
||||||
|
val desiredWidth = widths.fastSum()
|
||||||
|
when {
|
||||||
|
lineIndex == 0 && layout.lineCount > 1 && !isTitle -> {
|
||||||
|
//第一行 非标题
|
||||||
|
textLine.text = lineText
|
||||||
|
addCharsToLineFirst(
|
||||||
|
book, absStartX, textLine, words,
|
||||||
|
desiredWidth, widths, srcList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
lineIndex == layout.lineCount - 1 -> {
|
||||||
|
//最后一行
|
||||||
|
textLine.text = lineText
|
||||||
|
//标题x轴居中
|
||||||
|
val startX = if (
|
||||||
|
isTitle &&
|
||||||
|
(ReadBookConfig.isMiddleTitle || emptyContent || isVolumeTitle)
|
||||||
|
) {
|
||||||
|
(visibleWidth - desiredWidth) / 2
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
addCharsToLineNatural(
|
||||||
|
book, absStartX, textLine, words,
|
||||||
|
startX, !isTitle && lineIndex == 0, widths, srcList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (
|
||||||
|
isTitle &&
|
||||||
|
(ReadBookConfig.isMiddleTitle || emptyContent || isVolumeTitle)
|
||||||
|
) {
|
||||||
|
//标题居中
|
||||||
|
val startX = (visibleWidth - desiredWidth) / 2
|
||||||
|
addCharsToLineNatural(
|
||||||
|
book, absStartX, textLine, words,
|
||||||
|
startX, false, widths, srcList
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
//中间行
|
||||||
|
textLine.text = lineText
|
||||||
|
addCharsToLineMiddle(
|
||||||
|
book, absStartX, textLine, words,
|
||||||
|
desiredWidth, 0f, widths, srcList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (doublePage) {
|
||||||
|
textLine.isLeftLine = absStartX < viewWidth / 2
|
||||||
|
}
|
||||||
|
calcTextLinePosition(textPages, textLine, stringBuilder.length)
|
||||||
|
stringBuilder.append(lineText)
|
||||||
|
textLine.upTopBottom(durY, textHeight, fontMetrics)
|
||||||
|
val textPage = textPages.last()
|
||||||
|
textPage.addLine(textLine)
|
||||||
|
durY += textHeight * lineSpacingExtra
|
||||||
|
if (textPage.height < durY) {
|
||||||
|
textPage.height = durY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
durY += textHeight * paragraphSpacing / 10f
|
||||||
|
return Pair(absStartX, durY)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calcTextLinePosition(
|
||||||
|
textPages: ArrayList<TextPage>,
|
||||||
|
textLine: TextLine,
|
||||||
|
sbLength: Int
|
||||||
|
) {
|
||||||
|
val lastLine = textPages.last().lines.lastOrNull { it.paragraphNum > 0 }
|
||||||
|
?: textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull { it.paragraphNum > 0 }
|
||||||
|
val paragraphNum = when {
|
||||||
|
lastLine == null -> 1
|
||||||
|
lastLine.isParagraphEnd -> lastLine.paragraphNum + 1
|
||||||
|
else -> lastLine.paragraphNum
|
||||||
|
}
|
||||||
|
textLine.paragraphNum = paragraphNum
|
||||||
|
textLine.chapterPosition =
|
||||||
|
(textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull()?.run {
|
||||||
|
chapterPosition + charSize + if (isParagraphEnd) 1 else 0
|
||||||
|
} ?: 0) + sbLength
|
||||||
|
textLine.pagePosition = sbLength
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有缩进,两端对齐
|
||||||
|
*/
|
||||||
|
private suspend fun addCharsToLineFirst(
|
||||||
|
book: Book,
|
||||||
|
absStartX: Int,
|
||||||
|
textLine: TextLine,
|
||||||
|
words: List<String>,
|
||||||
|
/**自然排版长度**/
|
||||||
|
desiredWidth: Float,
|
||||||
|
textWidths: List<Float>,
|
||||||
|
srcList: LinkedList<String>?
|
||||||
|
) {
|
||||||
|
var x = 0f
|
||||||
|
if (!ReadBookConfig.textFullJustify) {
|
||||||
|
addCharsToLineNatural(
|
||||||
|
book, absStartX, textLine, words,
|
||||||
|
x, true, textWidths, srcList
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val bodyIndent = ReadBookConfig.paragraphIndent
|
||||||
|
for (i in bodyIndent.indices) {
|
||||||
|
val x1 = x + indentCharWidth
|
||||||
|
textLine.addColumn(
|
||||||
|
TextColumn(
|
||||||
|
charData = ChapterProvider.indentChar,
|
||||||
|
start = absStartX + x,
|
||||||
|
end = absStartX + x1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
x = x1
|
||||||
|
textLine.indentWidth = x
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
desiredWidth, x, textWidths1, srcList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无缩进,两端对齐
|
||||||
|
*/
|
||||||
|
private suspend fun addCharsToLineMiddle(
|
||||||
|
book: Book,
|
||||||
|
absStartX: Int,
|
||||||
|
textLine: TextLine,
|
||||||
|
words: List<String>,
|
||||||
|
/**自然排版长度**/
|
||||||
|
desiredWidth: Float,
|
||||||
|
/**起始x坐标**/
|
||||||
|
startX: Float,
|
||||||
|
textWidths: List<Float>,
|
||||||
|
srcList: LinkedList<String>?
|
||||||
|
) {
|
||||||
|
if (!ReadBookConfig.textFullJustify) {
|
||||||
|
addCharsToLineNatural(
|
||||||
|
book, absStartX, textLine, words,
|
||||||
|
startX, false, textWidths, srcList
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val residualWidth = visibleWidth - desiredWidth
|
||||||
|
val spaceSize = words.count { it == " " }
|
||||||
|
if (spaceSize > 1) {
|
||||||
|
val d = residualWidth / spaceSize
|
||||||
|
var x = startX
|
||||||
|
for (index in words.indices) {
|
||||||
|
val char = words[index]
|
||||||
|
val cw = textWidths[index]
|
||||||
|
val x1 = if (char == " ") {
|
||||||
|
if (index != words.lastIndex) (x + cw + d) else (x + cw)
|
||||||
|
} else {
|
||||||
|
(x + cw)
|
||||||
|
}
|
||||||
|
addCharToLine(
|
||||||
|
book, absStartX, textLine, char,
|
||||||
|
x, x1, index + 1 == words.size, srcList
|
||||||
|
)
|
||||||
|
x = x1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val gapCount: Int = words.lastIndex
|
||||||
|
val d = residualWidth / gapCount
|
||||||
|
var x = startX
|
||||||
|
for (index in words.indices) {
|
||||||
|
val char = words[index]
|
||||||
|
val cw = textWidths[index]
|
||||||
|
val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)
|
||||||
|
addCharToLine(
|
||||||
|
book, absStartX, textLine, char,
|
||||||
|
x, x1, index + 1 == words.size, srcList
|
||||||
|
)
|
||||||
|
x = x1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exceed(absStartX, textLine, words)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自然排列
|
||||||
|
*/
|
||||||
|
private suspend fun addCharsToLineNatural(
|
||||||
|
book: Book,
|
||||||
|
absStartX: Int,
|
||||||
|
textLine: TextLine,
|
||||||
|
words: List<String>,
|
||||||
|
startX: Float,
|
||||||
|
hasIndent: Boolean,
|
||||||
|
textWidths: List<Float>,
|
||||||
|
srcList: LinkedList<String>?
|
||||||
|
) {
|
||||||
|
val indentLength = ReadBookConfig.paragraphIndent.length
|
||||||
|
var x = startX
|
||||||
|
for (index in words.indices) {
|
||||||
|
val char = words[index]
|
||||||
|
val cw = textWidths[index]
|
||||||
|
val x1 = x + cw
|
||||||
|
addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)
|
||||||
|
x = x1
|
||||||
|
if (hasIndent && index == indentLength - 1) {
|
||||||
|
textLine.indentWidth = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exceed(absStartX, textLine, words)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加字符
|
||||||
|
*/
|
||||||
|
private suspend fun addCharToLine(
|
||||||
|
book: Book,
|
||||||
|
absStartX: Int,
|
||||||
|
textLine: TextLine,
|
||||||
|
char: String,
|
||||||
|
xStart: Float,
|
||||||
|
xEnd: Float,
|
||||||
|
isLineEnd: Boolean,
|
||||||
|
srcList: LinkedList<String>?
|
||||||
|
) {
|
||||||
|
val column = when {
|
||||||
|
srcList != null && char == ChapterProvider.srcReplaceChar -> {
|
||||||
|
val src = srcList.removeFirst()
|
||||||
|
ImageProvider.cacheImage(book, src, ReadBook.bookSource)
|
||||||
|
ImageColumn(
|
||||||
|
start = absStartX + xStart,
|
||||||
|
end = absStartX + xEnd,
|
||||||
|
src = src
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLineEnd && char == ChapterProvider.reviewChar -> {
|
||||||
|
ReviewColumn(
|
||||||
|
start = absStartX + xStart,
|
||||||
|
end = absStartX + xEnd,
|
||||||
|
count = 100
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
TextColumn(
|
||||||
|
start = absStartX + xStart,
|
||||||
|
end = absStartX + xEnd,
|
||||||
|
charData = char
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textLine.addColumn(column)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超出边界处理
|
||||||
|
*/
|
||||||
|
private fun exceed(absStartX: Int, textLine: TextLine, words: List<String>) {
|
||||||
|
val visibleEnd = absStartX + visibleWidth
|
||||||
|
val endX = textLine.columns.lastOrNull()?.end ?: return
|
||||||
|
if (endX > visibleEnd) {
|
||||||
|
val cc = (endX - visibleEnd) / words.size
|
||||||
|
for (i in 0..words.lastIndex) {
|
||||||
|
textLine.getColumnReverseAt(i).let {
|
||||||
|
val py = cc * (words.size - i)
|
||||||
|
it.start -= py
|
||||||
|
it.end -= py
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun measureTextSplit(
|
||||||
|
text: String,
|
||||||
|
paint: TextPaint
|
||||||
|
): Pair<ArrayList<String>, ArrayList<Float>> {
|
||||||
|
val length = text.length
|
||||||
|
val widthsArray = FloatArray(length)
|
||||||
|
paint.getTextWidths(text, widthsArray)
|
||||||
|
val clusterCount = widthsArray.count { it > 0f }
|
||||||
|
val widths = ArrayList<Float>(clusterCount)
|
||||||
|
val stringList = ArrayList<String>(clusterCount)
|
||||||
|
var i = 0
|
||||||
|
while (i < length) {
|
||||||
|
val clusterBaseIndex = i++
|
||||||
|
widths.add(widthsArray[clusterBaseIndex])
|
||||||
|
while (i < length && widthsArray[i] == 0f) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
stringList.add(text.substring(clusterBaseIndex, i))
|
||||||
|
}
|
||||||
|
return stringList to widths
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -35,12 +35,16 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
|||||||
|
|
||||||
override fun moveToNext(upContent: Boolean): Boolean = with(dataSource) {
|
override fun moveToNext(upContent: Boolean): Boolean = with(dataSource) {
|
||||||
return if (hasNext()) {
|
return if (hasNext()) {
|
||||||
|
val pageIndex = pageIndex
|
||||||
if (currentChapter == null || currentChapter?.isLastIndex(pageIndex) == true) {
|
if (currentChapter == null || currentChapter?.isLastIndex(pageIndex) == true) {
|
||||||
if ((currentChapter == null || isScroll) && nextChapter == null) {
|
if ((currentChapter == null || isScroll) && nextChapter == null) {
|
||||||
return@with false
|
return@with false
|
||||||
}
|
}
|
||||||
ReadBook.moveToNextChapter(upContent, false)
|
ReadBook.moveToNextChapter(upContent, false)
|
||||||
} else {
|
} else {
|
||||||
|
if (pageIndex < 0 || currentChapter?.isLastIndexCurrent(pageIndex) == true) {
|
||||||
|
return@with false
|
||||||
|
}
|
||||||
ReadBook.setPageIndex(pageIndex.plus(1))
|
ReadBook.setPageIndex(pageIndex.plus(1))
|
||||||
}
|
}
|
||||||
if (upContent) upContent(resetPageOffset = false)
|
if (upContent) upContent(resetPageOffset = false)
|
||||||
@ -55,6 +59,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
|||||||
if (currentChapter == null && prevChapter == null) {
|
if (currentChapter == null && prevChapter == null) {
|
||||||
return@with false
|
return@with false
|
||||||
}
|
}
|
||||||
|
if (prevChapter != null && prevChapter?.isCompleted == false) {
|
||||||
|
return@with false
|
||||||
|
}
|
||||||
ReadBook.moveToPrevChapter(upContent, upContentInPlace = false)
|
ReadBook.moveToPrevChapter(upContent, upContentInPlace = false)
|
||||||
} else {
|
} else {
|
||||||
if (currentChapter == null) {
|
if (currentChapter == null) {
|
||||||
@ -74,7 +81,8 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
|||||||
return@with TextPage(text = it).format()
|
return@with TextPage(text = it).format()
|
||||||
}
|
}
|
||||||
currentChapter?.let {
|
currentChapter?.let {
|
||||||
return@with it.getPage(pageIndex) ?: TextPage(title = it.title).format()
|
return@with it.getPage(pageIndex)
|
||||||
|
?: TextPage(title = it.title).apply { textChapter = it }.format()
|
||||||
}
|
}
|
||||||
return TextPage().format()
|
return TextPage().format()
|
||||||
}
|
}
|
||||||
@ -90,6 +98,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
|||||||
return@with it.getPage(pageIndex + 1)?.removePageAloudSpan()
|
return@with it.getPage(pageIndex + 1)?.removePageAloudSpan()
|
||||||
?: TextPage(title = it.title).format()
|
?: TextPage(title = it.title).format()
|
||||||
}
|
}
|
||||||
|
if (!it.isCompleted) {
|
||||||
|
return@with TextPage(title = it.title).format()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nextChapter?.let {
|
nextChapter?.let {
|
||||||
return@with it.getPage(0)?.removePageAloudSpan()
|
return@with it.getPage(0)?.removePageAloudSpan()
|
||||||
@ -109,6 +120,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
|||||||
return@with it.getPage(pageIndex - 1)?.removePageAloudSpan()
|
return@with it.getPage(pageIndex - 1)?.removePageAloudSpan()
|
||||||
?: TextPage(title = it.title).format()
|
?: TextPage(title = it.title).format()
|
||||||
}
|
}
|
||||||
|
if (!it.isCompleted) {
|
||||||
|
return@with TextPage(title = it.title).format()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
prevChapter?.let {
|
prevChapter?.let {
|
||||||
return@with it.lastPage?.removePageAloudSpan()
|
return@with it.lastPage?.removePageAloudSpan()
|
||||||
@ -125,6 +139,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
|||||||
return@with it.getPage(pageIndex + 2)?.removePageAloudSpan()
|
return@with it.getPage(pageIndex + 2)?.removePageAloudSpan()
|
||||||
?: TextPage(title = it.title).format()
|
?: TextPage(title = it.title).format()
|
||||||
}
|
}
|
||||||
|
if (!it.isCompleted) {
|
||||||
|
return@with TextPage(title = it.title).format()
|
||||||
|
}
|
||||||
nextChapter?.let { nc ->
|
nextChapter?.let { nc ->
|
||||||
if (pageIndex < it.pageSize - 1) {
|
if (pageIndex < it.pageSize - 1) {
|
||||||
return@with nc.getPage(0)?.removePageAloudSpan()
|
return@with nc.getPage(0)?.removePageAloudSpan()
|
||||||
|
@ -13,6 +13,12 @@ inline fun <T> List<T>.fastBinarySearch(
|
|||||||
toIndex: Int = size,
|
toIndex: Int = size,
|
||||||
comparison: (T) -> Int
|
comparison: (T) -> Int
|
||||||
): Int {
|
): Int {
|
||||||
|
when {
|
||||||
|
fromIndex > toIndex -> throw IllegalArgumentException("fromIndex ($fromIndex) is greater than toIndex ($toIndex).")
|
||||||
|
fromIndex < 0 -> throw IndexOutOfBoundsException("fromIndex ($fromIndex) is less than zero.")
|
||||||
|
toIndex > size -> throw IndexOutOfBoundsException("toIndex ($toIndex) is greater than size ($size).")
|
||||||
|
}
|
||||||
|
|
||||||
var low = fromIndex
|
var low = fromIndex
|
||||||
var high = toIndex - 1
|
var high = toIndex - 1
|
||||||
|
|
||||||
|
@ -119,3 +119,11 @@ open class Debounce<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> debounce(
|
||||||
|
wait: Long = 0L,
|
||||||
|
maxWait: Long = -1L,
|
||||||
|
leading: Boolean = false,
|
||||||
|
trailing: Boolean = true,
|
||||||
|
func: () -> T
|
||||||
|
) = Debounce(wait, maxWait, leading, trailing, func)
|
||||||
|
@ -7,3 +7,10 @@ class Throttle<T>(
|
|||||||
trailing: Boolean = true,
|
trailing: Boolean = true,
|
||||||
func: () -> T
|
func: () -> T
|
||||||
) : Debounce<T>(wait, wait, leading, trailing, func)
|
) : Debounce<T>(wait, wait, leading, trailing, func)
|
||||||
|
|
||||||
|
fun <T> throttle(
|
||||||
|
wait: Long = 0L,
|
||||||
|
leading: Boolean = true,
|
||||||
|
trailing: Boolean = true,
|
||||||
|
func: () -> T
|
||||||
|
) = Throttle(wait, leading, trailing, func)
|
||||||
|
Loading…
Reference in New Issue
Block a user