Przeglądaj źródła

fix(reader): 行高排除异常 leading,修复部分字体顶部行距过大 (#2221)

部分字体文件(如方正新楷体)把 fontMetrics.leading 声明为异常大的值
(约等于 1em),行盒按 descent - ascent + leading 计算时行高翻倍,
空隙全堆在字形上方。

引入 Paint.validFontLeading:leading 超过字体高度(descent - ascent)
15% 时视为字体文件异常,不计入行盒;正常字体的行间隙保留。应用到
全部三处行盒计算:
- PaintExtensions.textHeight:config 行高、空行与段距
- AndroidReaderTextShaper.fontLineMetrics:正文/标题实际行盒
  (视觉行距的真正来源)
- ReaderAndroidPaintFactory.baselineOffset:行盒基线偏移

实测:汉王 Clear7(系统字体当前选择方正新楷体 FZXKT--GB4-0),
textSize=24sp 时该字体 ascent=-44.18 / descent=7.80 / leading=51.98
(leading≈1em)。修复前实测行距 107px,修复后 52px,正文排版恢复
正常(见 PR 内真机对照截图)。
Mupceet 2 miesięcy temu
rodzic
commit
1d69ec70d2

+ 6 - 3
app/src/main/java/io/legado/app/feature/reader/platform/AndroidReaderTextShaper.kt

@@ -11,6 +11,7 @@ import io.legado.app.feature.reader.core.layout.ReaderFontBounds
 import io.legado.app.feature.reader.core.layout.ReaderFontLineMetrics
 import io.legado.app.feature.reader.core.layout.clusterGlyphs
 import io.legado.app.feature.reader.core.model.ReaderTextStyle
+import io.legado.app.utils.validFontLeading
 import java.io.File
 import splitties.init.appCtx
 
@@ -36,8 +37,8 @@ object ReaderAndroidPaintFactory {
 
     fun createTextPaint(style: ReaderTextStyle): TextPaint = TextPaint(create(style))
 
-    /** The reader's line box uses descent - ascent + leading; baseline = line bottom - descent. */
-    fun baselineOffset(paint: Paint): Float = paint.fontMetrics.let { it.leading - it.ascent }
+    /** 行盒基线偏移:行高已排除异常 leading(见 PaintExtensions.validFontLeading)。 */
+    fun baselineOffset(paint: Paint): Float = paint.fontMetrics.let { paint.validFontLeading - it.ascent }
 
     fun loadTypeface(path: String, weight: Int, italic: Boolean, family: String = "sans-serif"): Typeface {
         val key = "$path|$weight|$italic|$family"
@@ -74,7 +75,9 @@ class AndroidReaderTextShaper(paint: TextPaint) : ReaderTextShaper {
     private val paint = TextPaint(paint).apply { letterSpacing = 0f }
     override val fontBounds = this.paint.fontMetrics.let { ReaderFontBounds(it.top, it.bottom, it.descent) }
     override val fontLineMetrics = this.paint.fontMetrics.let {
-        val height = it.descent - it.ascent + it.leading
+        // 行盒高度与 utils/PaintExtensions.textHeight 同规则:异常 leading 不计入
+        //(如方正新楷体声明 leading≈1em,原样计入会让行高翻倍、空隙全在字形上方)
+        val height = it.descent - it.ascent + this.paint.validFontLeading
         ReaderFontLineMetrics(
             heightPx = height,
             baselineOffsetPx = height - it.descent,

+ 17 - 1
app/src/main/java/io/legado/app/utils/PaintExtensions.kt

@@ -1,10 +1,26 @@
 package io.legado.app.utils
 
+import android.graphics.Paint
 import android.os.Build
 import android.text.TextPaint
 
+/**
+ * 计入行盒的字体 leading。
+ *
+ * 正常情况下 leading 是字体建议的行间隙,应计入行高;但部分字体文件
+ * (如方正新楷体)把 leading 声明为异常大的值(约等于 1em),按原值
+ * 计算会导致行高翻倍且空隙全堆到字形上方。因此当 leading 超过字体
+ * 高度(descent-ascent)的 15% 时,视为字体文件异常,返回 0 不计入。
+ */
+val Paint.validFontLeading: Float
+    get() = fontMetrics.run {
+        val height = descent - ascent
+        if (leading > height * 0.15f) 0f else leading
+    }
+
+/** 文本行高:descent - ascent 加上 [validFontLeading](异常 leading 已排除)。 */
 val TextPaint.textHeight: Float
-    get() = fontMetrics.run { descent - ascent + leading }
+    get() = fontMetrics.run { descent - ascent + validFontLeading }
 
 fun TextPaint.getTextWidthsCompat(text: String, widths: FloatArray) {
     getTextWidths(text, widths)