|
@@ -49,14 +49,16 @@ object ChapterProvider {
|
|
|
|
|
|
|
|
const val indentChar = " "
|
|
const val indentChar = " "
|
|
|
|
|
|
|
|
- val linePaint: Paint by lazy {
|
|
|
|
|
- Paint(contentPaint).apply {
|
|
|
|
|
- clearShadowLayer()
|
|
|
|
|
- isAntiAlias = true
|
|
|
|
|
- strokeWidth = ReadBookConfig.underlineHeight.toFloat()
|
|
|
|
|
- style = Paint.Style.STROKE
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 朗读高亮 / 搜索命中那条线的画笔。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 曾经是 `by lazy`:首次取用时按当时的 [contentPaint] 与 `underlineHeight` 定型,之后
|
|
|
|
|
+ * 再改下划线粗细、字体、字号都不会反映到这条线上——`upThemeColors` 只就地改了它的
|
|
|
|
|
+ * `color`,粗细和字形一直是首帧那份。现在与其它画笔一样由 [upStyle] 重建。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Volatile
|
|
|
|
|
+ var linePaint: Paint = Paint()
|
|
|
|
|
+ private set
|
|
|
|
|
|
|
|
var dashEffect = DashPathEffect(floatArrayOf(dottedBase, dottedRatio), 0f)
|
|
var dashEffect = DashPathEffect(floatArrayOf(dottedBase, dottedRatio), 0f)
|
|
|
|
|
|
|
@@ -246,167 +248,6 @@ object ChapterProvider {
|
|
|
upStyle()
|
|
upStyle()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /*
|
|
|
|
|
- /**
|
|
|
|
|
- * 获取拆分完的章节数据
|
|
|
|
|
- */
|
|
|
|
|
- suspend fun getTextChapter(
|
|
|
|
|
- book: Book,
|
|
|
|
|
- bookChapter: BookChapter,
|
|
|
|
|
- displayTitle: String,
|
|
|
|
|
- bookContent: BookContent,
|
|
|
|
|
- chapterSize: Int,
|
|
|
|
|
- ): TextChapter {
|
|
|
|
|
- val contents = bookContent.textList
|
|
|
|
|
- val textPages = arrayListOf<TextPage>()
|
|
|
|
|
- val stringBuilder = StringBuilder()
|
|
|
|
|
- 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 (ReadConfig.enableReview) text + reviewChar else text,
|
|
|
|
|
- textPages,
|
|
|
|
|
- stringBuilder,
|
|
|
|
|
- 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
|
|
|
|
|
- }
|
|
|
|
|
- contents.forEach { content ->
|
|
|
|
|
- if (book.getImageStyle().equals(Book.imgStyleText, true)) {
|
|
|
|
|
- //图片样式为文字嵌入类型
|
|
|
|
|
- var text = content.replace(srcReplaceChar, "▣")
|
|
|
|
|
- val srcList = LinkedList<String>()
|
|
|
|
|
- val sb = StringBuffer()
|
|
|
|
|
- val matcher = AppPattern.imgPattern.matcher(text)
|
|
|
|
|
- while (matcher.find()) {
|
|
|
|
|
- matcher.group(1)?.let { src ->
|
|
|
|
|
- srcList.add(src)
|
|
|
|
|
- matcher.appendReplacement(sb, srcReplaceChar)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- matcher.appendTail(sb)
|
|
|
|
|
- text = sb.toString()
|
|
|
|
|
- setTypeText(
|
|
|
|
|
- book,
|
|
|
|
|
- absStartX,
|
|
|
|
|
- durY,
|
|
|
|
|
- text,
|
|
|
|
|
- textPages,
|
|
|
|
|
- stringBuilder,
|
|
|
|
|
- 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,
|
|
|
|
|
- textPages,
|
|
|
|
|
- stringBuilder,
|
|
|
|
|
- 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 (ReadConfig.enableReview) text + reviewChar else text,
|
|
|
|
|
- textPages,
|
|
|
|
|
- stringBuilder,
|
|
|
|
|
- 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()
|
|
|
|
|
- textPages.forEachIndexed { index, item ->
|
|
|
|
|
- item.index = index
|
|
|
|
|
- //item.pageSize = textPages.size
|
|
|
|
|
- item.chapterIndex = bookChapter.index
|
|
|
|
|
- item.chapterSize = chapterSize
|
|
|
|
|
- item.title = displayTitle
|
|
|
|
|
- item.doublePage = doublePage
|
|
|
|
|
- item.paddingTop = paddingTop
|
|
|
|
|
- item.upLinesPosition()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return TextChapter(
|
|
|
|
|
- bookChapter,
|
|
|
|
|
- bookChapter.index, displayTitle,
|
|
|
|
|
- //textPages,
|
|
|
|
|
- chapterSize,
|
|
|
|
|
- bookContent.sameTitleRemoved,
|
|
|
|
|
- bookChapter.isVip,
|
|
|
|
|
- bookChapter.isPay,
|
|
|
|
|
- bookContent.effectiveReplaceRules,
|
|
|
|
|
- bookContent.effectiveContentProcesses,
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
fun getTextChapterAsync(
|
|
fun getTextChapterAsync(
|
|
|
scope: CoroutineScope,
|
|
scope: CoroutineScope,
|
|
|
book: Book,
|
|
book: Book,
|
|
@@ -432,516 +273,6 @@ object ChapterProvider {
|
|
|
return textChapter
|
|
return textChapter
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /*
|
|
|
|
|
- /**
|
|
|
|
|
- * 排版图片
|
|
|
|
|
- */
|
|
|
|
|
- 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()
|
|
|
|
|
- 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
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Book.imgStyleSingle -> {
|
|
|
|
|
- width = visibleWidth
|
|
|
|
|
- height = size.height * visibleWidth / size.width
|
|
|
|
|
- if (height > visibleHeight) {
|
|
|
|
|
- width = width * visibleHeight / height
|
|
|
|
|
- height = visibleHeight
|
|
|
|
|
- }
|
|
|
|
|
- if (durY > 0f) {
|
|
|
|
|
- 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()
|
|
|
|
|
- textPages.add(TextPage())
|
|
|
|
|
- }
|
|
|
|
|
- // 双页的 durY 不正确,可能会小于实际高度
|
|
|
|
|
- if (textPage.height < durY) {
|
|
|
|
|
- textPage.height = durY
|
|
|
|
|
- }
|
|
|
|
|
- durY = 0f
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 图片竖直方向居中:调整 Y 坐标
|
|
|
|
|
- if (height < visibleHeight) {
|
|
|
|
|
- val adjustHeight = (visibleHeight - height) / 2f
|
|
|
|
|
- durY = adjustHeight // 将 Y 坐标设置为居中位置
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- 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()
|
|
|
|
|
- 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
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 排版文字
|
|
|
|
|
- */
|
|
|
|
|
- private suspend fun setTypeText(
|
|
|
|
|
- book: Book,
|
|
|
|
|
- x: Int,
|
|
|
|
|
- y: Float,
|
|
|
|
|
- text: String,
|
|
|
|
|
- textPages: ArrayList<TextPage>,
|
|
|
|
|
- stringBuilder: StringBuilder,
|
|
|
|
|
- textPaint: TextPaint,
|
|
|
|
|
- textHeight: Float,
|
|
|
|
|
- fontMetrics: 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, emptyList(), emptyList(),
|
|
|
|
|
- ReadBookConfig.paragraphIndent.length
|
|
|
|
|
- )
|
|
|
|
|
- } 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()
|
|
|
|
|
- //新建页面
|
|
|
|
|
- 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 = 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 == srcReplaceChar -> {
|
|
|
|
|
- val src = srcList.removeFirst()
|
|
|
|
|
- ImageProvider.cacheImage(book, src, ReadBook.bookSource)
|
|
|
|
|
- ImageColumn(
|
|
|
|
|
- start = absStartX + xStart,
|
|
|
|
|
- end = absStartX + xEnd,
|
|
|
|
|
- src = src
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- isLineEnd && char == 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
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- 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
|
|
|
|
|
- }
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 更新样式
|
|
* 更新样式
|
|
|
*/
|
|
*/
|
|
@@ -949,6 +280,12 @@ object ChapterProvider {
|
|
|
upRenderStyle()
|
|
upRenderStyle()
|
|
|
val typeface = getTypeface(ReadBookConfig.textFont)
|
|
val typeface = getTypeface(ReadBookConfig.textFont)
|
|
|
val (titlePaint, contentPaint) = getPaints(typeface)
|
|
val (titlePaint, contentPaint) = getPaints(typeface)
|
|
|
|
|
+ linePaint = Paint(contentPaint).apply {
|
|
|
|
|
+ clearShadowLayer()
|
|
|
|
|
+ isAntiAlias = true
|
|
|
|
|
+ strokeWidth = renderStyle.underlineHeight.toFloat()
|
|
|
|
|
+ style = Paint.Style.STROKE
|
|
|
|
|
+ }
|
|
|
dashEffect = DashPathEffect(
|
|
dashEffect = DashPathEffect(
|
|
|
floatArrayOf(ReadBookConfig.durConfig.dottedBase, ReadBookConfig.durConfig.dottedRatio),
|
|
floatArrayOf(ReadBookConfig.durConfig.dottedBase, ReadBookConfig.durConfig.dottedRatio),
|
|
|
0f
|
|
0f
|