[优化] 优化标题分段功能,现在提供完整的次行缩放、距离、段距等调整

This commit is contained in:
HapeLee
2026-01-18 16:30:37 +08:00
parent a770b3c052
commit f70efb584c
9 changed files with 192 additions and 105 deletions
@@ -328,6 +328,12 @@ object ReadBookConfig {
config.titleLineSpacingExtra = value
}
var titleLineSpacingSub: Int
get() = config.titleLineSpacingSub
set(value) {
config.titleLineSpacingSub = value
}
var paragraphSpacing: Int
get() = config.paragraphSpacing
set(value) {
@@ -577,6 +583,7 @@ object ReadBookConfig {
exportConfig.shadowDy = shareConfig.shadowDy
exportConfig.titleBold = shareConfig.titleBold
exportConfig.titleLineSpacingExtra = shareConfig.titleLineSpacingExtra
exportConfig.titleLineSpacingSub = shareConfig.titleLineSpacingSub
exportConfig.titleSegType = shareConfig.titleSegType
exportConfig.titleSegScaling = shareConfig.titleSegScaling
exportConfig.titleSegDistance = shareConfig.titleSegDistance
@@ -694,6 +701,7 @@ object ReadBookConfig {
var titleBottomSpacing: Int = 0,
var titleBold: Int = 500,//是否粗体字 0:正常, 1:粗体, 2:细体
var titleLineSpacingExtra: Int = 12,
var titleLineSpacingSub: Int = 12,
var titleSegType: Int = 0,//分段模式
var titleSegScaling: Float = 1f,//分段缩放,第二段与第一段的字体大小比例
var titleSegDistance: Int = 4,//分段判断,第几个字符开始分段
@@ -173,6 +173,7 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config
binding.dsbTitleSegScaling.progress = ReadBookConfig.titleSegScaling.toInt() * 10
binding.dsbTitleLineSpacingExtra.progress = ReadBookConfig.titleLineSpacingExtra
binding.dsbTitleLineSpacingSub.progress = ReadBookConfig.titleLineSpacingSub
binding.dsbTitleSize.progress = ReadBookConfig.titleSize
binding.dsbTitleTop.progress = ReadBookConfig.titleTopSpacing
binding.dsbTitleBottom.progress = ReadBookConfig.titleBottomSpacing
@@ -193,6 +194,10 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config
ReadBookConfig.titleLineSpacingExtra = it
postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))
}
binding.dsbTitleLineSpacingSub.onChanged = {
ReadBookConfig.titleLineSpacingSub = it
postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))
}
dsbTitleSize.onChanged = {
ReadBookConfig.titleSize = it
postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))
@@ -35,6 +35,7 @@ data class TextLine(
var chapterPosition: Int = 0,
var pagePosition: Int = 0,
val isTitle: Boolean = false,
var titleTextSize: Float? = null,
var isParagraphEnd: Boolean = false,
var isImage: Boolean = false,
var isHtml: Boolean = false,
@@ -0,0 +1,7 @@
package io.legado.app.ui.book.read.page.entities
data class TitleSegment(
val text: String,
val isMainTitle: Boolean,
val scale: Float
)
@@ -52,6 +52,10 @@ data class TextColumn(
} else {
ReadBookConfig.textColor
}
val originalSize = textPaint.textSize
textLine.titleTextSize?.let {
textPaint.textSize = it
}
if (textPaint.color != textColor) {
textPaint.color = textColor
}
@@ -66,6 +70,7 @@ data class TextColumn(
if (selected) {
canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint)
}
textPaint.textSize = originalSize
}
}
@@ -114,7 +114,10 @@ object ChapterProvider {
var lineSpacingExtra = 0f
private set
var titleLineSpacingExtra = 0f
var titleLineSpacingExtra = 0f
private set
var titleLineSpacingSub = 0f
private set
@JvmStatic
@@ -886,6 +889,7 @@ object ChapterProvider {
//间距
lineSpacingExtra = ReadBookConfig.lineSpacingExtra / 10f
titleLineSpacingExtra = ReadBookConfig.titleLineSpacingExtra / 10f
titleLineSpacingSub = ReadBookConfig.titleLineSpacingSub / 10f
paragraphSpacing = ReadBookConfig.paragraphSpacing
titleTopSpacing = ReadBookConfig.titleTopSpacing.dpToPx()
titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dpToPx()
@@ -77,6 +77,8 @@ class TextChapterLayout(
private val titleTopSpacing = ChapterProvider.titleTopSpacing
private val titleBottomSpacing = ChapterProvider.titleBottomSpacing
private val titleLineSpacingExtra = ChapterProvider.titleLineSpacingExtra
private val titleLineSpacingSub = ChapterProvider.titleLineSpacingSub
private val lineSpacingExtra = ChapterProvider.lineSpacingExtra
private val paragraphSpacing = ChapterProvider.paragraphSpacing
@@ -209,44 +211,6 @@ class TextChapterLayout(
}
}
private fun parseTitleSegments(
rawTitle: String,
segType: Int,
segDistance: Int,
segFlag: String
): List<String> {
if (rawTitle.isBlank()) return emptyList()
return when (segType) {
0 -> listOf(rawTitle)
1 -> {
if (segDistance <= 0 || segDistance >= rawTitle.length)
listOf(rawTitle)
else
listOf(
rawTitle.take(segDistance),
rawTitle.substring(segDistance)
)
}
2 -> {
val flags = segFlag.split(",").map { it.trim() }.filter { it.isNotEmpty() }
if (flags.isEmpty()) return listOf(rawTitle)
val pattern = flags.joinToString("|") { Regex.escape(it) }
val regex = Regex("(?<=$pattern)")
val segments = rawTitle.split(regex)
.map { it.trim() }
.filter { it.isNotEmpty() }
segments.ifEmpty { listOf(rawTitle) }
}
else -> listOf(rawTitle)
}
}
/**
* 获取拆分完的章节数据
*/
@@ -262,45 +226,66 @@ class TextChapterLayout(
val isTextImageStyle = imageStyle.equals(Book.imgStyleText, true)
if (titleMode != 2 || bookChapter.isVolume || contents.isEmpty()) {
val baseTitles = displayTitle.splitNotBlank("\n")
baseTitles.forEach { rawTitle ->
val titleSegments = parseTitleSegments(
val allTitleSegments = displayTitle.splitNotBlank("\n").flatMap { rawTitle ->
TitleStyleParser.getSegments(
rawTitle,
titleSegType,
titleSegDistance,
titleSegFlag
titleSegFlag,
titleSegScaling
)
titleSegments.forEachIndexed { index, segment ->
val srcList = LinkedList<String>()
val reviewImg = bookChapter.reviewImg
var reviewTxt = ""
if (reviewImg != null) {
srcList.add(reviewImg)
reviewTxt = if (reviewImg.contains("TEXT")) {
reviewChar
} else {
srcReplaceChar
}
}
allTitleSegments.forEachIndexed { index, segment ->
val currentPaint: TextPaint
val currentHeight: Float
val currentMetrics: Paint.FontMetrics
val lineIndexBefore = pendingTextPage.lines.size
if (segment.isMainTitle) {
currentPaint = titlePaint
currentHeight = titlePaintTextHeight
currentMetrics = titlePaintFontMetrics
} else {
currentPaint = TextPaint(titlePaint).apply {
textSize = titlePaint.textSize * segment.scale
}
val paint = if (index == 0) titlePaint else {
TextPaint(titlePaint).apply {
textSize = titlePaint.textSize * titleSegScaling
}
currentMetrics = currentPaint.fontMetrics
currentHeight = currentMetrics.bottom - currentMetrics.top
}
val srcList = LinkedList<String>()
val reviewImg = bookChapter.reviewImg
var reviewTxt = ""
if (index == allTitleSegments.lastIndex && reviewImg != null) {
srcList.add(reviewImg)
reviewTxt = if (reviewImg.contains("TEXT")) reviewChar else srcReplaceChar
}
setTypeText(
book = book,
text = segment.text + reviewTxt,
textPaint = currentPaint,
textHeight = currentHeight,
fontMetrics = currentMetrics,
imageStyle = imageStyle,
srcList = srcList.ifEmpty { null },
isTitle = true,
emptyContent = contents.isEmpty(),
isVolumeTitle = bookChapter.isVolume
)
if (segment.scale != 1.0f) {
val currentLines = pendingTextPage.lines
for (i in lineIndexBefore until currentLines.size) {
currentLines[i].titleTextSize = currentPaint.textSize
}
setTypeText(
book,
segment + reviewTxt,
paint,
titlePaintTextHeight,
titlePaintFontMetrics,
imageStyle,
srcList = srcList.ifEmpty { null },
isTitle = true,
emptyContent = contents.isEmpty(),
isVolumeTitle = bookChapter.isVolume
)
pendingTextPage.lines.last().isParagraphEnd = true
stringBuilder.append("\n")
}
pendingTextPage.lines.last().isParagraphEnd = true
stringBuilder.append("\n")
if (index < allTitleSegments.lastIndex) {
durY += currentHeight * titleLineSpacingSub
}
}
durY += titleBottomSpacing
@@ -901,7 +886,7 @@ class TextChapterLayout(
textLine.upTopBottom(durY, textHeight, fontMetrics)
val textPage = pendingTextPage
textPage.addLine(textLine)
durY += textHeight * lineSpacingExtra
durY += textHeight * if (isTitle) titleLineSpacingExtra else lineSpacingExtra
if (textPage.height < durY) {
textPage.height = durY
}
@@ -0,0 +1,45 @@
package io.legado.app.ui.book.read.page.provider
import io.legado.app.ui.book.read.page.entities.TitleSegment
object TitleStyleParser {
fun getSegments(
rawTitle: String,
segType: Int,
segDistance: Int,
segFlag: String,
scaling: Float
): List<TitleSegment> {
if (rawTitle.isBlank()) return emptyList()
val results: List<String> = when (segType) {
1 -> {
if (segDistance <= 0 || segDistance >= rawTitle.length)
listOf(rawTitle)
else
listOf(rawTitle.take(segDistance), rawTitle.substring(segDistance))
}
2 -> {
val flags = segFlag.split(",").map { it.trim() }.filter { it.isNotEmpty() }
if (flags.isEmpty()) listOf(rawTitle)
else {
val pattern = flags.joinToString("|") { Regex.escape(it) }
val regex = Regex("(?<=$pattern)")
rawTitle.split(regex).map { it.trim() }.filter { it.isNotEmpty() }
}
}
else -> listOf(rawTitle)
}
// 统一包装成对象返回
return results.mapIndexed { index, text ->
TitleSegment(
text = text,
isMainTitle = index == 0,
scale = if (index == 0) 1.0f else scaling
)
}
}
}