引言
本周在写代码的时候,涉及到一部分跑马灯的实现,其中涉及到使用Canvas
绘制字体、获取与调整字体大小等功能的实现。中途还遇到过字体绘制时出现字体超出view高度或低于view底部导致字体绘制不全的问题。故本文将将自己在编码时搜集到的资料进行记录。
推荐fft字体查看软件:FontForge
字体度量相关属性
计算机字体度量系统初认知
上图是来自于网络的字体度量展示图。其中各部分的描述如下所示
核心基准与范围
单词 | 描述 |
---|---|
Baseline(基线) | 字符排列的基准线,类似书写时的 “底线”,多数字母(如E h )底部对齐此线,j 的下伸部分会突破。 |
Origin(原点) | 字体度量的参考原点,通常位于基线左侧起始位置,是计算字符位置的基准点。 |
Bounding rectangle(边界矩形) | 包围字符的最小矩形,定义单个字符的视觉范围,辅助排版时确定字符间距、碰撞检测。 |
Line height | 两行文字基线之间的垂直距离,包含字体本身高度(ascent + descent )和行间距(leading ),控制文本行与行的间隔。 |
字符垂直度量
单词 | 描述 |
---|---|
Ascent(上伸高度) | 基线到字体中最高字符(如E h 的顶部)的距离,体现字符向上延伸的最大范围。 |
Descent(下伸高度) | 基线到字体中最低字符(如j 的下伸部分)的距离,体现字符向下延伸的最大范围。 |
Cap height(大写字母高度) | 基线到大写字母(如E )顶部的距离,反映大写字母的视觉高度,影响字体风格(如衬线体、无衬线体的差异)。 |
X-height(x 字高) | 基线到小写字母x (无上下伸的字母)顶部的距离,决定字体 “紧凑感”,是影响可读性的关键参数。 |
字符间距与倾斜
单词 | 描述 |
---|---|
Advancement(字宽推进) | 一个字符在基线方向上的宽度(含空白间隔),决定字符间的水平间距,排版时控制文字排列密度 |
Italic angle(倾斜角度) | 斜体字的倾斜角度,体现字体风格(如意大利体的倾斜度),影响文字的视觉流动感 |
Android Font Metrics
/**
* 描述特定文本大小下字体各种度量标准的类。
* 请记住,Y 值随向下方向递增,因此这些值将为正,
* 而测量向上距离的值将为负。此类由 getFontMetrics () 方法返回。
*/
public static class FontMetrics {
/**
* 对于给定文本大小的字体,最高字符超出 baseline 的长度。
*/
public float top;
/**
* 对于单行间距文本,推荐的 baseline 以上距离。
*/
public float ascent;
/**
* 对于单行间距文本,推荐的 baseline 以下距离。
*/
public float descent;
/**
* 对于给定文本大小的字体,最低字符超出 baseline 的长度。
*/
public float bottom;
/**
* 建议添加在文本行之间的额外间距。(可理解为行间距)。
*/
public float leading;
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
@Override
public String toString() { ... }
}
安卓提供的属性与上图相似,但有点不同。上面的属性中,比较常用的是ascent
与descent
。(主要,根据我做项目查到的资料来看,top与bottom没看到有用的)。top与bottom则是Android加入的,主要是为了兼容一些特殊字体。安卓实际的字体渲染高度由top与bottom决定。
编码操作
计算字体高度
如果你需要计算字体的高度,可以使用
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
// 字体高度 = 上伸部分(ascent 为负) + 下伸部分(descent 为正)
float fontHeight = fontMetrics.descent - fontMetrics.ascent;
虽然安卓实际中的字体高度由 descent 与 ascent 决定,但实际渲染时则是使用 top 与 bottom,留出了空白,来兼容少数特殊字体。
为什么上面的坐标为负?
因为在度量系统中,并不以元素的左上角为坐标原点。其中 X 依然为元素最左侧,而 Y 则是 baseline 的位置。
计算行间距
行间距表示相邻两行的基线之间的距离。默认行距的实际值等于字体设置中的|Descent| + |Aescent|(leading = 0)。根据上节的图可知,如果你需要计算当前的间距,可以使用
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
// 行高 = 上伸绝对值 + 下伸 + 行间距(leading),leading 是字体建议的行与行之间额外间距
float lineHeight = fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading;
Canvas绘制文字
Canvas绘制文字的APIcanvas.drawText(str, X, Y, paint)
。该API中(X, Y)
为绘制起点,同时也是字体的原点的位置。这里的字体原点指的是 baseline 的位置。
假设字体所在的View
位于(0, 0)
。我们希望将字体绘制在view
的左上角。则
错误的绘制方式,下面的绘制方式会导致字体只有baseline
下的内容可被展示。baseline
上面的内容会因为超出view
而无法显示
canvas.drawText(str, 0, 0, paint)
正确的绘制方式
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
canvas.drawText(str, 0, -fontMetrics.ascent, paint)