中文字体的FontMetrics解析

中文字体的FontMetrics解析

因行业对字体大小要求严格参考相关规范,因此对通过渲染引擎绘制的文本字体把控严格。

而在Skia/openGL/Qt等主流渲染引擎中,所有设置字体大小(FontSize),都是以英文字体大小作为标准的,准确来说,对应的应该是FontMetrics中的CapHeight作为英文字体高度。

笔者发现在网络上相关资料中,很少有关于中文字符的FontMetrics解析。通过多日研究后,在此详细解析一下中文字体的FontMetrics。

一、FontMetrics标准解析

研究发现,基本所有的渲染引擎都采用以下的字体绘制结构,以下简称标准
在这里插入图片描述
这里各个属性都已经描述的很清楚了,网络上其余各个版本的,都不太正确。

为了对比,我使用Skia渲染引擎绘制了同样的文字来做对比,效果如下:

在这里插入图片描述
实际上实测的效果还是与网上流传的示意图有一定差异的。

1、大部分字体,字符的顶部触碰不到AscentLine。AscentLine用于带上标的字符、拉丁文字符等
在这里插入图片描述
2、对于输入的字符不同,计算的Top、Bottom位置并非不变的。(例如斜体单个字符h的Top、Bottom就要比非斜体jEh要窄些)

大体上都参考标准,研究得出几条结论:

1、英文正常高度为Baseline到CapHeight线
2、最低位置到Descent线处
3、带上标的字符最高到Ascent线处
4、斜体并不改变字体高度

二、中文字符的FontMetrics

绘制引擎就是按照英文字符构造来设计的几条线,压根没考虑中文字符的感受。

假设设置字体高度为100像素,绘制出来的英文字体为100个像素,对于中文来说,往往是会放大。所有的字体的中文的字符都不会在CapHeight内

以宋体和等线体为例:
在这里插入图片描述
注:与上图英文字符的绘制线一样,绿色的Ascent与Descent和Top与Bottom几条线重合了,因此绿色看不出来是绿色了。

可以清楚的看到,无论哪种字体,中文字符都不在CapHeight和BaseLine之间。而且并不与任一条线接触。

理论上来说,我们需要将中文字符的高度改成我们设置的高度,就需要缩放。

公式为:
在这里插入图片描述

因此,想要知道需要设置的字体高度,公式为:

在这里插入图片描述
其中,中文字符应有高度为100像素,当前设置字体高度为100像素,所以只需要量算出中文字符当前高度,就能计算出这个应设置的字体高度。

但是目测量算的都不准确,应该使用FontMetrics中有的属性来确定真实值。

经过多测量算、推演,得出以下规律:

1、以宋体、仿宋体等为代表的标准中文字体类型:
在这里插入图片描述
上下两条紫色线为中文字符上下限高度

中文字符下限为UnderLinePos,上限为 Capheight + DescentLine
是的没有看错,上线是DescentLine(即BaseLine到DescentLine的距离),而不是AscentLine。
实际上AscentLine与Top重合了,是要比DescentLine要大一点

至于为什么是DescentLine实际上我也不明白,但能肯定的是这不是偶然。无论如何缩放这个值始终是能和中文字符上线重合,一定存在规律。

2、以等线体为代表的的中英文字体类型:

在这里插入图片描述
这类字体非常奇怪,首先英文字符的下限并没有到Descent,甚至没有到UnderLine,与纯英文字体如Times NewRoman等规律就不同。

中文字符也是不能符合上面宋体的规律。

但经过多次推算(不同尺度下),发现一个规律:

中文字符高度 = CapHeight + UnderLinePos

能得出这个结论我也是比较疑惑的,没有任何API、文献资料能证明这条规律,但事实上它就是如此。

得出中文字符高度后,再带入公式,求出应该设置的FontSize,再设置新FontSize,绘制出来的中文字符,就是我们想要的字体高度了。

至此,中文字符高度的计算工作完毕。

期间使用到的代码:

1.绘制字体基准线代码

	paint.setStyle(SkPaint::kStroke_Style);
	paint.setStrokeWidth(3);
	paint.setColor(0xffff0000);//红色为fTop\fBottom\BaseLine
	double y = p.y();
	canvas->drawLine(p.x(), y, p.x() + length, y, paint);
	y = p.y() - font_m.fTop;
	canvas->drawLine(p.x(), y, p.x() + length, y, paint);
	y = p.y() - font_m.fBottom;
	canvas->drawLine(p.x(), y, p.x() + length, y, paint);

	paint.setStrokeWidth(0);
	paint.setColor(0xff00ff00);//绿色为Ascent\Descent
	y = p.y() - font_m.fAscent;
	canvas->drawLine(p.x(), y, p.x() + length, y, paint);
	y = p.y() - font_m.fDescent;
	canvas->drawLine(p.x(), y, p.x() + length, y, paint);

	paint.setColor(0xffffff00);//黄色为fCapHeight
	y = p.y() + font_m.fCapHeight;
	canvas->drawLine(p.x(), y, p.x() + length, y, paint);

	paint.setColor(0xffff00ff);//紫色为fUnderlinePosition、宋体、仿宋体等中文字符顶部
	y = p.y() - font_m.fUnderlinePosition;
	canvas->drawLine(p.x(), y, p.x() + length, y, paint);
	y = p.y() + font_m.fCapHeight + std::fabs(font_m.fDescent);
	canvas->drawLine(p.x(), y, p.x() + length, y, paint);
	//paint.setColor(0xffffffff);//白色为fStrikeoutPosition
	//y = p.y() + font_m.fCapHeight + font_m.fDescent;
	//canvas->drawLine(p.x(), y, p.x() + length, y, paint);

2.判断是否中文类字体(目前只用字体名判断,希望能有其他方法)

	bool _isSpecialZhFamily(const dan::SGString& fontName)
	{
		if(fontName == "宋体" || fontName == "仿宋")
		{
			return true;
		}

		return false;
	}

3.按照中文标准高度换算应设置的字体高度

	auto text_height = std::fabs(font_m.fCapHeight);//英文标高,所有字体的英文标高都是CapHeight

	if(this->isFontSizeOnZhChar())  //中标高为准
	{
	    //对于部分中文字体,需要用其他修正方式
	    text_height = std::fabs(font_m.fCapHeight) + std::fabs(font_m.fUnderlinePosition);

	    if(_isSpecialZhFamily(_paint->font().family()))
	    {
	        text_height += std::fabs(font_m.fDescent);
	    }

	    length /= TEXT_LENGTH_RESIZE_CONST;//长度修正,中文标高不需要修正
	}

	double scale = font_size / text_height;
	auto scaled_fontsize = font_size * scale;//缩放后的文本大小
	skFont.setSize(scaled_fontsize);

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

话与山鬼听

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值