parent
							
								
									38911b6d93
								
							
						
					
					
						commit
						db33516ac5
					
				@ -1,106 +1,270 @@ | 
				
			||||
package io.legado.app.ui.book.read.page | 
				
			||||
 | 
				
			||||
import android.text.Spannable | 
				
			||||
import android.text.SpannableStringBuilder | 
				
			||||
import android.text.style.ForegroundColorSpan | 
				
			||||
import android.text.style.RelativeSizeSpan | 
				
			||||
import androidx.core.text.HtmlCompat | 
				
			||||
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT | 
				
			||||
import io.legado.app.App | 
				
			||||
import android.graphics.Point | 
				
			||||
import android.text.Layout | 
				
			||||
import android.text.StaticLayout | 
				
			||||
import android.text.TextPaint | 
				
			||||
import io.legado.app.data.entities.BookChapter | 
				
			||||
import io.legado.app.lib.theme.accentColor | 
				
			||||
import io.legado.app.help.BookHelp | 
				
			||||
import io.legado.app.help.ReadBookConfig | 
				
			||||
import io.legado.app.ui.book.read.page.entities.TextChapter | 
				
			||||
import io.legado.app.ui.book.read.page.entities.TextChar | 
				
			||||
import io.legado.app.ui.book.read.page.entities.TextLine | 
				
			||||
import io.legado.app.ui.book.read.page.entities.TextPage | 
				
			||||
import io.legado.app.utils.dp | 
				
			||||
 | 
				
			||||
 | 
				
			||||
object ChapterProvider { | 
				
			||||
    var readAloudSpan = ForegroundColorSpan(App.INSTANCE.accentColor) | 
				
			||||
    private val titleSpan = RelativeSizeSpan(1.2f) | 
				
			||||
    var viewWidth = 0 | 
				
			||||
    var viewHeight = 0 | 
				
			||||
    private var visibleWidth = 0 | 
				
			||||
    private var visibleHeight = 0 | 
				
			||||
    private var paddingLeft = 0 | 
				
			||||
    private var paddingTop = 0 | 
				
			||||
    private var lineSpacingExtra = 0 | 
				
			||||
    private var paragraphSpacing = 0 | 
				
			||||
    var titlePaint = TextPaint() | 
				
			||||
    var contentPaint = TextPaint() | 
				
			||||
 | 
				
			||||
    var textView: ContentTextView? = null | 
				
			||||
    init { | 
				
			||||
        upStyle(ReadBookConfig.durConfig) | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    fun upStyle(config: ReadBookConfig.Config) { | 
				
			||||
        titlePaint.color = config.textColor() | 
				
			||||
        titlePaint.letterSpacing = config.letterSpacing | 
				
			||||
        contentPaint.color = config.textColor() | 
				
			||||
        contentPaint.letterSpacing = config.letterSpacing | 
				
			||||
        lineSpacingExtra = config.lineSpacingExtra | 
				
			||||
        paragraphSpacing = config.paragraphSpacing | 
				
			||||
        titlePaint.textSize = (config.textSize + 2).dp.toFloat() | 
				
			||||
        contentPaint.textSize = config.textSize.dp.toFloat() | 
				
			||||
        upSize(config) | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    fun upSize(config: ReadBookConfig.Config) { | 
				
			||||
        paddingLeft = config.paddingLeft.dp | 
				
			||||
        paddingTop = config.paddingTop.dp | 
				
			||||
        visibleWidth = viewWidth - paddingLeft - config.paddingRight.dp | 
				
			||||
        visibleHeight = viewHeight - paddingTop - config.paddingBottom.dp | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    @Suppress("DEPRECATION") | 
				
			||||
    fun getTextChapter( | 
				
			||||
        bookChapter: BookChapter, | 
				
			||||
        content: String, | 
				
			||||
        chapterSize: Int, | 
				
			||||
        isHtml: Boolean = false | 
				
			||||
    ): TextChapter { | 
				
			||||
        textView?.let { | 
				
			||||
            val textPages = arrayListOf<TextPage>() | 
				
			||||
            val pageLines = arrayListOf<Int>() | 
				
			||||
            val pageLengths = arrayListOf<Int>() | 
				
			||||
            var surplusText = content | 
				
			||||
            var pageIndex = 0 | 
				
			||||
            while (surplusText.isNotEmpty()) { | 
				
			||||
                val spannableStringBuilder = | 
				
			||||
                    if (isHtml) { | 
				
			||||
                        HtmlCompat.fromHtml( | 
				
			||||
                            surplusText, | 
				
			||||
                            FROM_HTML_MODE_COMPACT | 
				
			||||
                        ) as SpannableStringBuilder | 
				
			||||
                    } else { | 
				
			||||
                        SpannableStringBuilder(surplusText) | 
				
			||||
                    } | 
				
			||||
                if (pageIndex == 0) { | 
				
			||||
                    val end = surplusText.indexOf("\n") | 
				
			||||
                    if (end > 0) { | 
				
			||||
                        spannableStringBuilder.setSpan( | 
				
			||||
                            titleSpan, | 
				
			||||
                            0, | 
				
			||||
                            end, | 
				
			||||
                            Spannable.SPAN_INCLUSIVE_EXCLUSIVE | 
				
			||||
        val bodyIndent = BookHelp.bodyIndent | 
				
			||||
        val textPages = arrayListOf<TextPage>() | 
				
			||||
        val pageLines = arrayListOf<Int>() | 
				
			||||
        val pageLengths = arrayListOf<Int>() | 
				
			||||
        var surplusText = content | 
				
			||||
        var durY = 0 | 
				
			||||
        textPages.add(TextPage()) | 
				
			||||
        while (surplusText.isNotEmpty()) { | 
				
			||||
            if (textPages.first().textLines.isEmpty()) { | 
				
			||||
                //title | 
				
			||||
                val end = surplusText.indexOf("\n") | 
				
			||||
                if (end > 0) { | 
				
			||||
                    val title = surplusText.substring(0, end) | 
				
			||||
                    surplusText = surplusText.substring(end + 1) | 
				
			||||
                    val layout = StaticLayout( | 
				
			||||
                        title, titlePaint, visibleWidth, | 
				
			||||
                        Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra.toFloat(), false | 
				
			||||
                    ) | 
				
			||||
                    for (lineIndex in 0 until layout.lineCount) { | 
				
			||||
                        durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) | 
				
			||||
                        val textLine = TextLine(isTitle = true) | 
				
			||||
                        if (durY < visibleHeight) { | 
				
			||||
                            textPages.last().textLines.add(textLine) | 
				
			||||
                        } else { | 
				
			||||
                            durY = 0 | 
				
			||||
                            textPages.add(TextPage()) | 
				
			||||
                            textPages.last().textLines.add(textLine) | 
				
			||||
                        } | 
				
			||||
                        textLine.lineBottom = layout.getLineBottom(lineIndex) | 
				
			||||
                        textLine.lineTop = layout.getLineTop(lineIndex) | 
				
			||||
                        val words = title.substring( | 
				
			||||
                            layout.getLineStart(lineIndex), | 
				
			||||
                            layout.getLineEnd(lineIndex) | 
				
			||||
                        ) | 
				
			||||
                        val desiredWidth = layout.getLineMax(lineIndex) | 
				
			||||
                        if (lineIndex != layout.lineCount - 1) { | 
				
			||||
                            val gapCount: Int = words.length - 1 | 
				
			||||
                            val d = (visibleWidth - desiredWidth) / gapCount | 
				
			||||
                            var x = 0 | 
				
			||||
                            for (i in words.indices) { | 
				
			||||
                                val char = words[i].toString() | 
				
			||||
                                val cw = StaticLayout.getDesiredWidth(char, titlePaint) | 
				
			||||
                                val x1 = if (i != words.lastIndex) { | 
				
			||||
                                    (x + cw + d).toInt() | 
				
			||||
                                } else { | 
				
			||||
                                    (x + cw).toInt() | 
				
			||||
                                } | 
				
			||||
                                val textChar = TextChar( | 
				
			||||
                                    charData = char, | 
				
			||||
                                    leftBottomPosition = Point(paddingLeft + x, paddingTop + durY), | 
				
			||||
                                    rightTopPosition = Point( | 
				
			||||
                                        paddingLeft + x1, | 
				
			||||
                                        paddingTop + durY - (textLine.lineBottom - textLine.lineTop) | 
				
			||||
                                    ) | 
				
			||||
                                ) | 
				
			||||
                                textLine.textChars.add(textChar) | 
				
			||||
                                x = x1 | 
				
			||||
                            } | 
				
			||||
                        } else { | 
				
			||||
                            //最后一行 | 
				
			||||
                            var x = 0 | 
				
			||||
                            for (i in words.indices) { | 
				
			||||
                                val char = words[i].toString() | 
				
			||||
                                val cw = StaticLayout.getDesiredWidth(char, titlePaint) | 
				
			||||
                                val x1 = (x + cw).toInt() | 
				
			||||
                                val textChar = TextChar( | 
				
			||||
                                    charData = char, | 
				
			||||
                                    leftBottomPosition = Point(paddingLeft + x, paddingTop + durY), | 
				
			||||
                                    rightTopPosition = Point( | 
				
			||||
                                        paddingLeft + x1, | 
				
			||||
                                        paddingTop + durY - (textLine.lineBottom - textLine.lineTop) | 
				
			||||
                                    ) | 
				
			||||
                                ) | 
				
			||||
                                textLine.textChars.add(textChar) | 
				
			||||
                                x = x1 | 
				
			||||
                            } | 
				
			||||
                        } | 
				
			||||
                    } | 
				
			||||
                    durY += paragraphSpacing | 
				
			||||
                } | 
				
			||||
                it.text = spannableStringBuilder | 
				
			||||
                val lastLine = it.getLineNum() | 
				
			||||
                val lastCharNum = it.getCharNum(lastLine) | 
				
			||||
                if (lastCharNum == 0) { | 
				
			||||
                    break | 
				
			||||
            } else { | 
				
			||||
                //正文 | 
				
			||||
                val end = surplusText.indexOf("\n") | 
				
			||||
                val text: String | 
				
			||||
                if (end >= 0) { | 
				
			||||
                    text = surplusText.substring(0, end) | 
				
			||||
                    surplusText = surplusText.substring(end + 1) | 
				
			||||
                } else { | 
				
			||||
                    pageLines.add(lastLine) | 
				
			||||
                    pageLengths.add(lastCharNum) | 
				
			||||
                    textPages.add( | 
				
			||||
                        TextPage( | 
				
			||||
                            index = pageIndex, | 
				
			||||
                            text = spannableStringBuilder.delete( | 
				
			||||
                                lastCharNum, | 
				
			||||
                                spannableStringBuilder.length | 
				
			||||
                            ), | 
				
			||||
                            title = bookChapter.title, | 
				
			||||
                            chapterSize = chapterSize, | 
				
			||||
                            chapterIndex = bookChapter.index | 
				
			||||
                    text = surplusText | 
				
			||||
                    surplusText = "" | 
				
			||||
                } | 
				
			||||
                val layout = StaticLayout( | 
				
			||||
                    text, contentPaint, visibleWidth, | 
				
			||||
                    Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra.toFloat(), false | 
				
			||||
                ) | 
				
			||||
                for (lineIndex in 0 until layout.lineCount) { | 
				
			||||
                    val textLine = TextLine(isTitle = false) | 
				
			||||
                    durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) | 
				
			||||
                    if (durY < visibleHeight) { | 
				
			||||
                        textPages.last().textLines.add(textLine) | 
				
			||||
                    } else { | 
				
			||||
                        durY = 0 | 
				
			||||
                        textPages.add(TextPage()) | 
				
			||||
                        textPages.last().textLines.add(textLine) | 
				
			||||
                    } | 
				
			||||
                    textLine.lineBottom = layout.getLineBottom(lineIndex) | 
				
			||||
                    textLine.lineTop = layout.getLineTop(lineIndex) | 
				
			||||
                    var words = | 
				
			||||
                        text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) | 
				
			||||
                    val desiredWidth = layout.getLineMax(lineIndex) | 
				
			||||
                    if (lineIndex == 0 && layout.lineCount > 1) { | 
				
			||||
                        //第一行 | 
				
			||||
                        var x = 0 | 
				
			||||
                        val icw = StaticLayout.getDesiredWidth(bodyIndent, contentPaint) | 
				
			||||
                        var x1 = (x + icw).toInt() | 
				
			||||
                        val textChar = TextChar( | 
				
			||||
                            charData = bodyIndent, | 
				
			||||
                            leftBottomPosition = Point(paddingLeft + x, paddingTop + durY), | 
				
			||||
                            rightTopPosition = Point( | 
				
			||||
                                paddingLeft + x1, | 
				
			||||
                                paddingTop + durY - (textLine.lineBottom - textLine.lineTop) | 
				
			||||
                            ) | 
				
			||||
                        ) | 
				
			||||
                    ) | 
				
			||||
                    surplusText = surplusText.substring(lastCharNum) | 
				
			||||
                    pageIndex++ | 
				
			||||
                        textLine.textChars.add(textChar) | 
				
			||||
                        x = x1 | 
				
			||||
                        words = words.replaceFirst(bodyIndent, "") | 
				
			||||
                        val gapCount: Int = words.length - 1 | 
				
			||||
                        val d = (visibleWidth - desiredWidth) / gapCount | 
				
			||||
                        for (i in words.indices) { | 
				
			||||
                            val char = words[i].toString() | 
				
			||||
                            val cw = StaticLayout.getDesiredWidth(char, contentPaint) | 
				
			||||
                            x1 = if (i != words.lastIndex) { | 
				
			||||
                                (x + cw + d).toInt() | 
				
			||||
                            } else { | 
				
			||||
                                (x + cw).toInt() | 
				
			||||
                            } | 
				
			||||
                            val textChar1 = TextChar( | 
				
			||||
                                charData = char, | 
				
			||||
                                leftBottomPosition = Point(paddingLeft + x, paddingTop + durY), | 
				
			||||
                                rightTopPosition = Point( | 
				
			||||
                                    paddingLeft + x1, | 
				
			||||
                                    paddingTop + durY - (textLine.lineBottom - textLine.lineTop) | 
				
			||||
                                ) | 
				
			||||
                            ) | 
				
			||||
                            textLine.textChars.add(textChar1) | 
				
			||||
                            x = x1 | 
				
			||||
                        } | 
				
			||||
                    } else if (lineIndex == layout.lineCount - 1) { | 
				
			||||
                        //最后一行 | 
				
			||||
                        var x = 0 | 
				
			||||
                        for (i in words.indices) { | 
				
			||||
                            val char = words[i].toString() | 
				
			||||
                            val cw = StaticLayout.getDesiredWidth(char, contentPaint) | 
				
			||||
                            val x1 = (x + cw).toInt() | 
				
			||||
                            val textChar = TextChar( | 
				
			||||
                                charData = char, | 
				
			||||
                                leftBottomPosition = Point(paddingLeft + x, paddingTop + durY), | 
				
			||||
                                rightTopPosition = Point( | 
				
			||||
                                    paddingLeft + x1, | 
				
			||||
                                    paddingTop + durY - (textLine.lineBottom - textLine.lineTop) | 
				
			||||
                                ) | 
				
			||||
                            ) | 
				
			||||
                            textLine.textChars.add(textChar) | 
				
			||||
                            x = x1 | 
				
			||||
                        } | 
				
			||||
                    } else { | 
				
			||||
                        //中间行 | 
				
			||||
                        val gapCount: Int = words.length - 1 | 
				
			||||
                        val d = (visibleWidth - desiredWidth) / gapCount | 
				
			||||
                        var x = 0 | 
				
			||||
                        for (i in words.indices) { | 
				
			||||
                            val char = words[i].toString() | 
				
			||||
                            val cw = StaticLayout.getDesiredWidth(char, contentPaint) | 
				
			||||
                            val x1 = if (i != words.lastIndex) { | 
				
			||||
                                (x + cw + d).toInt() | 
				
			||||
                            } else { | 
				
			||||
                                (x + cw).toInt() | 
				
			||||
                            } | 
				
			||||
                            val textChar = TextChar( | 
				
			||||
                                charData = char, | 
				
			||||
                                leftBottomPosition = Point(paddingLeft + x, paddingTop + durY), | 
				
			||||
                                rightTopPosition = Point( | 
				
			||||
                                    paddingLeft + x1, | 
				
			||||
                                    paddingTop + durY - (textLine.lineBottom - textLine.lineTop) | 
				
			||||
                                ) | 
				
			||||
                            ) | 
				
			||||
                            textLine.textChars.add(textChar) | 
				
			||||
                            x = x1 | 
				
			||||
                        } | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
                durY += paragraphSpacing | 
				
			||||
            } | 
				
			||||
            for (item in textPages) { | 
				
			||||
                item.pageSize = textPages.size | 
				
			||||
            } | 
				
			||||
            return TextChapter( | 
				
			||||
                bookChapter.index, | 
				
			||||
                bookChapter.title, | 
				
			||||
                bookChapter.url, | 
				
			||||
                textPages, | 
				
			||||
                pageLines, | 
				
			||||
                pageLengths, | 
				
			||||
                chapterSize | 
				
			||||
            ) | 
				
			||||
        } ?: return TextChapter( | 
				
			||||
        } | 
				
			||||
        for ((index, item) in textPages.withIndex()) { | 
				
			||||
            item.index = index | 
				
			||||
            item.pageSize = textPages.size | 
				
			||||
            item.chapterIndex = bookChapter.index | 
				
			||||
            item.chapterSize = chapterSize | 
				
			||||
            item.title = bookChapter.title | 
				
			||||
        } | 
				
			||||
        return TextChapter( | 
				
			||||
            bookChapter.index, | 
				
			||||
            bookChapter.title, | 
				
			||||
            bookChapter.url, | 
				
			||||
            arrayListOf(), | 
				
			||||
            arrayListOf(), | 
				
			||||
            arrayListOf(), | 
				
			||||
            textPages, | 
				
			||||
            pageLines, | 
				
			||||
            pageLengths, | 
				
			||||
            chapterSize | 
				
			||||
        ) | 
				
			||||
 | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    fun upReadAloudSpan() { | 
				
			||||
        readAloudSpan = ForegroundColorSpan(App.INSTANCE.accentColor) | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -1,6 +1,9 @@ | 
				
			||||
package io.legado.app.ui.book.read.page.entities | 
				
			||||
 | 
				
			||||
data class TextLine( | 
				
			||||
    val textChars: List<TextChar>, | 
				
			||||
    val lineBottom: Int | 
				
			||||
    val textChars: ArrayList<TextChar> = arrayListOf(), | 
				
			||||
    var lineTop: Int = 0, | 
				
			||||
    var lineBottom: Int = 0, | 
				
			||||
    val isTitle: Boolean = false, | 
				
			||||
    var isReadAloud: Boolean = false | 
				
			||||
) | 
				
			||||
@ -1,40 +1,33 @@ | 
				
			||||
package io.legado.app.ui.book.read.page.entities | 
				
			||||
 | 
				
			||||
import android.text.Spannable | 
				
			||||
import android.text.SpannableStringBuilder | 
				
			||||
import io.legado.app.App | 
				
			||||
import io.legado.app.R | 
				
			||||
import io.legado.app.ui.book.read.page.ChapterProvider | 
				
			||||
 | 
				
			||||
data class TextPage( | 
				
			||||
    val index: Int, | 
				
			||||
    val text: CharSequence = App.INSTANCE.getString(R.string.data_loading), | 
				
			||||
    val title: String, | 
				
			||||
    var index: Int = 0, | 
				
			||||
    var text: CharSequence = App.INSTANCE.getString(R.string.data_loading), | 
				
			||||
    var title: String = "", | 
				
			||||
    val textLines: ArrayList<TextLine> = arrayListOf(), | 
				
			||||
    var pageSize: Int = 0, | 
				
			||||
    var chapterSize: Int = 0, | 
				
			||||
    var chapterIndex: Int = 0 | 
				
			||||
) { | 
				
			||||
 | 
				
			||||
    fun removePageAloudSpan(): TextPage { | 
				
			||||
        if (text is SpannableStringBuilder) { | 
				
			||||
            text.removeSpan(ChapterProvider.readAloudSpan) | 
				
			||||
        textLines.forEach { textLine -> | 
				
			||||
            textLine.isReadAloud = false | 
				
			||||
        } | 
				
			||||
        return this | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    fun upPageAloudSpan(pageStart: Int) { | 
				
			||||
        if (text is SpannableStringBuilder) { | 
				
			||||
            text.removeSpan(ChapterProvider.readAloudSpan) | 
				
			||||
            removePageAloudSpan() | 
				
			||||
            var end = text.indexOf("\n", pageStart) | 
				
			||||
            if (end == -1) end = text.length | 
				
			||||
            var start = text.lastIndexOf("\n", pageStart) | 
				
			||||
            if (start == -1) start = 0 | 
				
			||||
            text.setSpan( | 
				
			||||
                ChapterProvider.readAloudSpan, | 
				
			||||
                start, | 
				
			||||
                end, | 
				
			||||
                Spannable.SPAN_INCLUSIVE_EXCLUSIVE | 
				
			||||
            ) | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue