From ce22b9f20e6ab14232f7a8b04e48af8371df8d71 Mon Sep 17 00:00:00 2001 From: HapeLee <63206378+HapeLee@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:42:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=AB=98=E4=BA=AE=E8=A7=84=E5=88=99=20f?= =?UTF-8?q?ontPath=20=E6=8E=92=E7=89=88=E6=B5=8B=E9=87=8F=E4=B8=8E?= =?UTF-8?q?=E7=BB=98=E5=88=B6=E5=AD=97=E4=BD=93=E4=B8=8D=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E8=A1=8C=E9=A6=96=E8=A1=8C=E5=B0=BE=E6=A0=87?= =?UTF-8?q?=E7=82=B9=E9=94=99=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 排版阶段始终用默认字体测宽和断行,但绘制阶段才切换高亮规则字体, 导致不同字体的标点 advance 差异引起首尾符号错位。 - TextLine.addColumn(): 把 fontPath.isNotEmpty() 纳入 onlyTextColumn 判断, 防止仅改字体的规则被 fastDrawTextLine 跳过 - TextColumn: 新增 internal getTypeface() 让排版阶段复用 typeface 缓存 - TextChapterLayout: 在 applyHighlightRules 后用 remeasureWithHighlightFonts() 按字符/cluster 使用对应 fontPath 的 TextPaint 重新测宽, 确保 ZhLayout 断行、desiredWidth、TextColumn.start/end 全部基于正确字体度量 --- .../ui/book/read/page/entities/TextLine.kt | 2 +- .../read/page/entities/column/TextColumn.kt | 12 ++++--- .../read/page/provider/TextChapterLayout.kt | 33 +++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index 5ae229b1e..ca4e5a428 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -82,7 +82,7 @@ data class TextLine( fun addColumn(column: BaseColumn) { if (column !is TextColumn) { onlyTextColumn = false - } else if (column.textColor != null || column.bgColor != null || column.underlineMode != 0 || column.bgImage.isNotEmpty()) { + } else if (column.textColor != null || column.bgColor != null || column.underlineMode != 0 || column.bgImage.isNotEmpty() || column.fontPath.isNotEmpty()) { onlyTextColumn = false } column.textLine = this diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt index 33acaacf4..6ea1a3019 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt @@ -110,15 +110,19 @@ data class TextColumn( } private fun getCustomTypeface(): Typeface? { - if (fontPath.isEmpty()) return null - return typefaceCache.getOrPut(fontPath) { - loadTypeface(fontPath) - } + return getTypeface(fontPath) } companion object { private val typefaceCache = HashMap() + internal fun getTypeface(fontPath: String): Typeface? { + if (fontPath.isEmpty()) return null + return typefaceCache.getOrPut(fontPath) { + loadTypeface(fontPath) + } + } + private fun loadTypeface(fontPath: String): Typeface? { return runCatching { when { 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 dd2d50a2a..0edfa0eff 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 @@ -924,6 +924,10 @@ class TextChapterLayout( val charStyles = applyHighlightRules(text, isTitle) val widthsArray = allocateFloatArray(text.length) textPaint.getTextWidthsCompat(text, widthsArray) + // 用高亮规则字体重新测量字符宽度,确保排版和绘制使用同一套字体 + if (charStyles != null) { + remeasureWithHighlightFonts(text, charStyles, textPaint, widthsArray) + } val layout = if (useZhLayout) { val (words, widths) = measureTextSplit(text, widthsArray) val indentSize = if (isFirstLine) paragraphIndent.length else 0 @@ -1359,6 +1363,35 @@ class TextChapterLayout( return code == 8203 || code == 8204 || code == 8205 || code == 8288 } + /** + * 对有自定义字体的高亮字符重新测量宽度,确保排版和绘制使用同一套字体度量。 + */ + private fun remeasureWithHighlightFonts( + text: String, + charStyles: Array, + textPaint: TextPaint, + widthsArray: FloatArray + ) { + val originalTypeface = textPaint.typeface + var i = 0 + while (i < text.length) { + val fontPath = charStyles[i]?.fontPath.orEmpty() + if (fontPath.isEmpty()) { i++; continue } + // 找连续使用同一字体的区间 + val segStart = i + i++ + while (i < text.length && charStyles[i]?.fontPath.orEmpty() == fontPath) i++ + val segEnd = i + val typeface = TextColumn.getTypeface(fontPath) ?: continue + textPaint.typeface = typeface + val segment = text.substring(segStart, segEnd) + val segWidths = FloatArray(segEnd - segStart) + textPaint.getTextWidthsCompat(segment, segWidths) + segWidths.copyInto(widthsArray, segStart) + } + textPaint.typeface = originalTypeface + } + /** * 对文本应用高亮规则,返回每字符的样式数组。无匹配时返回 null。 */