diff --git a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt index c07bb3157..074a40363 100644 --- a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt @@ -273,6 +273,12 @@ object ReadBookConfig { config.lineSpacingExtra = value } + var titleLineSpacingExtra: Int + get() = config.titleLineSpacingExtra + set(value) { + config.titleLineSpacingExtra = value + } + var paragraphSpacing: Int get() = config.paragraphSpacing set(value) { @@ -293,6 +299,30 @@ object ReadBookConfig { config.titleSize = value } + var titleSegType: Int + get() = config.titleSegType + set(value) { + config.titleSegType = value + } + + var titleSegScaling: Float + get() = config.titleSegScaling + set(value) { + config.titleSegScaling = value + } + + var titleSegDistance: Int + get() = config.titleSegDistance + set(value) { + config.titleSegDistance = value + } + + var titleSegFlag: String + get() = config.titleSegFlag + set(value) { + config.titleSegFlag = value + } + /** * 是否标题居中 */ @@ -497,6 +527,11 @@ object ReadBookConfig { var titleTopSpacing: Int = 0, var titleBottomSpacing: Int = 0, var titleBold: Int = 500, + var titleLineSpacingExtra: Int = 4, + var titleSegType: Int = 0,//分段模式 + var titleSegScaling: Float = 1.2f,//分段缩放,第二段与第一段的字体大小比例 + var titleSegDistance: Int = 4,//分段判断,第几个字符开始分段 + var titleSegFlag: String = "",//分段判断,碰到指定值时分段 var paragraphIndent: String = "  ",//段落缩进 var underline: Boolean = false, //下划线 var paddingBottom: Int = 6, diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt index 011ca1fbf..ab6589f1b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt @@ -1,12 +1,16 @@ package io.legado.app.ui.book.read.config import android.os.Bundle +import android.text.InputType import android.view.View +import android.widget.EditText +import androidx.appcompat.app.AlertDialog import com.google.android.material.chip.Chip import com.jaredrummler.android.colorpicker.ColorPickerDialog import io.legado.app.R import io.legado.app.base.BaseBottomSheetDialogFragment import io.legado.app.constant.EventBus +import io.legado.app.databinding.DialogEditTextBinding import io.legado.app.databinding.DialogTipConfigBinding import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadTipConfig @@ -15,6 +19,8 @@ import io.legado.app.lib.dialogs.selector import io.legado.app.utils.hexString import io.legado.app.utils.observeEvent import io.legado.app.utils.postEvent +import io.legado.app.utils.requestInputMethod +import io.legado.app.utils.toastOnUi import io.legado.app.utils.viewbindingdelegate.viewBinding @@ -74,10 +80,82 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config addOnChangeListener { _, newValue, _ -> binding.textFontWeightConverter.text = "自定义" ReadBookConfig.titleBold = newValue.toInt() - postEvent(EventBus.UP_CONFIG, arrayListOf(8, 9, 6)) + postEvent(EventBus.UP_CONFIG, arrayListOf(5)) } } + binding.btnTitleSegType.setOnClickListener { + val types = arrayOf("不分段", "按字符数分段", "按标志字符串分段") + val current = ReadBookConfig.titleSegType + + alert(title = "选择标题分段模式") { + singleChoiceItems(types, current) { _, which -> + ReadBookConfig.titleSegType = which + } + positiveButton("确定") { + toastOnUi("分段模式已设置为:${types[ReadBookConfig.titleSegType]}") + postEvent(EventBus.UP_CONFIG, arrayListOf(5)) + } + negativeButton("取消") + }.show() + } + + binding.btnTitleSegConfig.setOnClickListener { + when (ReadBookConfig.titleSegType) { + 1 -> { // 按字符数分段 + alert(title = "设置分段字符数") { + val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply { + editView.inputType = InputType.TYPE_CLASS_NUMBER + editView.setText(ReadBookConfig.titleSegDistance.toString()) + editView.hint = "输入分段字符数" + } + customView { alertBinding.root } + + okButton { + val value = alertBinding.editView.text?.toString()?.toIntOrNull() + if (value != null && value > 0) { + ReadBookConfig.titleSegDistance = value + toastOnUi("分段字符数设置为 $value") + postEvent(EventBus.UP_CONFIG, arrayListOf(5)) + } else { + toastOnUi("请输入有效数字") + } + } + cancelButton() + }.requestInputMethod() + } + + 2 -> { // 按标志字符串分段 + alert(title = "设置分段标志") { + val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply { + editView.inputType = InputType.TYPE_CLASS_TEXT + editView.setText(ReadBookConfig.titleSegFlag) + editLayout.hint = "输入多个标志,用英文逗号分隔,例如:章,回,篇" + } + customView { alertBinding.root } + + okButton { + val value = alertBinding.editView.text?.toString()?.trim() + if (!value.isNullOrEmpty()) { + ReadBookConfig.titleSegFlag = value + toastOnUi("分段标志设置为 \"$value\"") + postEvent(EventBus.UP_CONFIG, arrayListOf(5)) + } else { + toastOnUi("标志不能为空") + } + } + cancelButton() + }.requestInputMethod() + } + + else -> { + toastOnUi("当前分段模式无需配置参数") + } + } + } + + binding.dsbTitleSegScaling.progress = ReadBookConfig.titleSegScaling.toInt() * 10 + binding.dsbTitleLineSpacingExtra.progress = ReadBookConfig.titleLineSpacingExtra binding.dsbTitleSize.progress = ReadBookConfig.titleSize binding.dsbTitleTop.progress = ReadBookConfig.titleTopSpacing binding.dsbTitleBottom.progress = ReadBookConfig.titleBottomSpacing @@ -166,6 +244,14 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config postEvent(EventBus.UP_CONFIG, arrayListOf(5)) } } + binding.dsbTitleSegScaling.onChanged = { + ReadBookConfig.titleSegScaling = it / 10f + postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) + } + binding.dsbTitleLineSpacingExtra.onChanged = { + ReadBookConfig.titleLineSpacingExtra = it + postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) + } dsbTitleSize.onChanged = { ReadBookConfig.titleSize = it postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 7b7f5788d..c7232aa5d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -97,6 +97,9 @@ object ChapterProvider { var lineSpacingExtra = 0f private set + var titleLineSpacingExtra = 0f + private set + @JvmStatic var paragraphSpacing = 0 private set @@ -861,6 +864,7 @@ object ChapterProvider { } //间距 lineSpacingExtra = ReadBookConfig.lineSpacingExtra / 10f + titleLineSpacingExtra = ReadBookConfig.titleLineSpacingExtra / 10f paragraphSpacing = ReadBookConfig.paragraphSpacing titleTopSpacing = ReadBookConfig.titleTopSpacing.dpToPx() titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dpToPx() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt index bb6a4101a..410597c09 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt @@ -65,6 +65,8 @@ class TextChapterLayout( private val titleTopSpacing = ChapterProvider.titleTopSpacing private val titleBottomSpacing = ChapterProvider.titleBottomSpacing private val lineSpacingExtra = ChapterProvider.lineSpacingExtra + private val titleLineSpacingExtra = ChapterProvider.titleLineSpacingExtra + private val paragraphSpacing = ChapterProvider.paragraphSpacing private val visibleHeight = ChapterProvider.visibleHeight @@ -80,6 +82,11 @@ class TextChapterLayout( private val useZhLayout = ReadBookConfig.useZhLayout private val isMiddleTitle = ReadBookConfig.isMiddleTitle private val textFullJustify = ReadBookConfig.textFullJustify + private val titleSegType = ReadBookConfig.titleSegType + private val titleSegDistance = ReadBookConfig.titleSegDistance + private val titleSegFlag = ReadBookConfig.titleSegFlag + private val titleSegScaling = ReadBookConfig.titleSegScaling + private var pendingTextPage = TextPage() @@ -189,6 +196,48 @@ class TextChapterLayout( } } + private fun parseTitleSegments( + rawTitle: String, + segType: Int, + segDistance: Int, + segFlag: String + ): List { + if (rawTitle.isBlank()) return emptyList() + + return when (segType) { + // 0:不分段 + 0 -> listOf(rawTitle) + + // 1:按字符数分段 + 1 -> { + if (segDistance <= 0 || segDistance >= rawTitle.length) + listOf(rawTitle) + else + listOf( + rawTitle.substring(0, segDistance), + rawTitle.substring(segDistance) + ) + } + + // 2:按标志字符串分段 + 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) + } + } + + /** * 获取拆分完的章节数据 */ @@ -204,37 +253,48 @@ class TextChapterLayout( val isTextImageStyle = imageStyle.equals(Book.imgStyleText, true) if (titleMode != 2 || bookChapter.isVolume || contents.isEmpty()) { - //标题非隐藏 - displayTitle.splitNotBlank("\n").forEach { text -> - val srcList = LinkedList() - val reviewImg = bookChapter.reviewImg - var reviewTxt = "" - if (reviewImg != null) { - srcList.add(reviewImg) - reviewTxt = if (reviewImg.contains("TEXT")) { - ChapterProvider.reviewChar - } else { - ChapterProvider.srcReplaceChar - } - } - setTypeText( - book, - text + reviewTxt, - titlePaint, - titlePaintTextHeight, - titlePaintFontMetrics, - imageStyle, - srcList = srcList.ifEmpty { null }, - isTitle = true, - emptyContent = contents.isEmpty(), - isVolumeTitle = bookChapter.isVolume + val baseTitles = displayTitle.splitNotBlank("\n") + baseTitles.forEach { rawTitle -> + val titleSegments = parseTitleSegments( + rawTitle, + titleSegType, + titleSegDistance, + titleSegFlag ) - pendingTextPage.lines.last().isParagraphEnd = true - stringBuilder.append("\n") + titleSegments.forEachIndexed { index, segment -> + val srcList = LinkedList() + val reviewImg = bookChapter.reviewImg + var reviewTxt = "" + if (reviewImg != null) { + srcList.add(reviewImg) + reviewTxt = if (reviewImg.contains("TEXT")) { + ChapterProvider.reviewChar + } else { + ChapterProvider.srcReplaceChar + } + } + val paint = if (index == 0) titlePaint else { + TextPaint(titlePaint).apply { + textSize = titlePaint.textSize * titleSegScaling + } + } + 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") + } } durY += titleBottomSpacing - - // 如果是单图模式且当前页有内容,强制分页 if (isSingleImageStyle && pendingTextPage.lines.isNotEmpty() && contents.isNotEmpty()) { prepareNextPageIfNeed() } @@ -598,7 +658,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 } diff --git a/app/src/main/res/layout/dialog_tip_config.xml b/app/src/main/res/layout/dialog_tip_config.xml index e2ac8d1a6..2b603d764 100644 --- a/app/src/main/res/layout/dialog_tip_config.xml +++ b/app/src/main/res/layout/dialog_tip_config.xml @@ -75,11 +75,58 @@ + + + + + + + + + + + + + + + + + +