本文记录我探索 Skia 做竖排文本渲染过程中的字符高度计算方法
如果只考虑字符和字符之间密集的进行排列,那只需使用 GetGlyphWidths 方法获取字符的字墨尺寸范围即可。如以下代码所示,从 SKFont 获取字墨尺寸范围
using var paint = new SKPaint();
paint.Color = SKColors.Blue;
paint.Style = SKPaintStyle.Fill;
paint.IsAntialias = true;
using var typeface = SKFontManager.Default.MatchFamily("微软雅黑");
using var skFont = new SKFont(typeface, 30);
var text = "pi一二一中文雅黑对齐";
var glyphList = new ushort[text.Length];
skFont.GetGlyphs(text, glyphList);
var widthList = new float[glyphList.Length];
var boundsList = new SKRect[glyphList.Length];
skFont.GetGlyphWidths(glyphList, widthList, boundsList, paint);
以上获取的 boundsList
就是对应的每个字符的在 WPF 概念里面的 字墨尺寸范围 接近的值。这里需要说明的是 WPF 采用的是 DirectWrite 进行渲染,和 Skia 获取的数值上有所偏差,好在对于微软雅黑来说,只是小数点之后的误差而已
在 Skia 里面,传入 DrawText 里面的点坐标指的是字符的 baseline 基线坐标,而不是左上角坐标
默认情况下渲染的字符高度,直接等于字高度是可以的,能够实现一个字紧接一个字的效果。尽管这样会让字过于紧凑,如以下示意图
对应的代码如下
var positionList = new SKPoint[text.Length];
var y = 0f;
for (int i = 0; i < text.Length; i++)
{
var height = boundsList[i].Height;
var charHeight = height;
var top = boundsList[i].Top;
paint.Color = SKColors.Blue.WithAlpha(0xC5);
paint.Style = SKPaintStyle.Stroke;
positionList[i] = new SKPoint(boundsList[i].Left, y + -top);
y += charHeight;
}
SKTextBlob skTextBlob = SKTextBlob.CreatePositioned(text, skFont, positionList.AsSpan());
skCanvas.DrawText(skTextBlob, 0, 0, paint);
实现的效果十分紧凑的字符排版效果,如此的直排竖排垂直分布排版界面效果如下
以上代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin d77e52470f55cc00d556dea1c873a1ec5ce61f22
以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin d77e52470f55cc00d556dea1c873a1ec5ce61f22
获取代码之后,进入 SkiaSharp/DihealereniRakallfairko 文件夹,即可获取到源代码
以上的竖排效果只能说是有效果,但整体效果不好。为了实现更好的竖排效果,自然可以想到的就是加上字符边距,如 https://gitee.com/lindexi/lindexi_gd/blob/0d200a22b3829188a9abb6f967f96a7341f75b5c/LightTextEditorPlus/LightTextEditorPlus.Skia/Platform/SkiaCharInfoMeasurer.cs#L275-277 或 https://github.com/lindexi/lindexi_gd/blob/0d200a22b3829188a9abb6f967f96a7341f75b5c/LightTextEditorPlus/LightTextEditorPlus.Skia/Platform/SkiaCharInfoMeasurer.cs#L275-L277 文本库的补丁代码所示
if (!isHorizontal)
{
// 竖排情况下,不要让字间距过大
const int margin = 6;
height = renderBounds.Height + margin;
}
自然来说,这个间距是好的。但不足之处至于这是一个魔法值,没有什么理由
一个自然的方法是引入 baseline(Ascent)来作为间距,如下图所示
引入 baseline(Ascent)来作为间距,即以上代码的 margin 换成了 space 值
space = Ascent - Top
于是此时的渲染高度计算公式如下
RenderHeight = height + space
= 字高度+空隙
为了让字不紧凑,就需要使用至少为 baseline(Ascent) 的间距距离。尽管理论上使用 Top 就足够了,但 Top 过于紧凑。引入 baseline 之后,就会因为 Top 和 baseline 之间的差距,引入了 space 高度的空隙。于是引入 baseline 之后的渲染高度就应该是 height 字高加上空隙
更改代码,引入 baseline 作为 margin 的代码如下
var positionList = new SKPoint[text.Length];
var y = 0f;
for (int i = 0; i < text.Length; i++)
{
var height = boundsList[i].Height;
var charHeight = height;
var top = boundsList[i].Top;
var space = baseline + top;
charHeight = height + space; // 字高度加空隙等于渲染高度
paint.Color = SKColors.Blue.WithAlpha(0xC5);
paint.Style = SKPaintStyle.Stroke;
positionList[i] = new SKPoint(boundsList[i].Left, y + baseline);
y += charHeight;
}
SKTextBlob skTextBlob = SKTextBlob.CreatePositioned(text, skFont, positionList.AsSpan());
paint.Style = SKPaintStyle.Fill;
paint.Color = SKColors.Blue;
skCanvas.DrawText(skTextBlob, 0, 0, paint);
运行之后的界面效果如下
是不是看起来有些奇怪?试试将每个外接边框也输出出来,代码如下
var height = boundsList[i].Height;
var charHeight = height;
var top = boundsList[i].Top;
var space = baseline + top;
charHeight = height + space; // 字高度加空隙等于渲染高度
paint.Color = SKColors.Blue.WithAlpha(0xC5);
paint.Style = SKPaintStyle.Stroke;
skCanvas.DrawRect(boundsList[i].Left, y, boundsList[i].Width, charHeight, paint);
positionList[i] = new SKPoint(boundsList[i].Left, y + baseline);
y += charHeight;
运行的效果如下图,从下图一下就可以看出来,汉字“一”明显过于偏下
只引入 baseline 依然还是不够的,为了能够在竖排过程中,让一些汉字,如“一”进行垂直方向的居中,这里不会直接从 baseline 开始画,而是取 (baseline-space/2)
开始画,确保画出来的效果如下所示
核心实现代码如下
var positionList = new SKPoint[text.Length];
var y = 0f;
for (int i = 0; i < text.Length; i++)
{
var height = boundsList[i].Height;
var charHeight = height;
var top = boundsList[i].Top;
var space = baseline + top;
charHeight = height + space; // 字高度加空隙等于渲染高度
paint.Color = SKColors.Blue.WithAlpha(0xC5);
paint.Style = SKPaintStyle.Stroke;
skCanvas.DrawRect(boundsList[i].Left, y, boundsList[i].Width, charHeight, paint);
positionList[i] = new SKPoint(boundsList[i].Left, y + baseline - space / 2);
y += charHeight;
}
画出来的界面效果如下
去掉调试边框之后的效果如下
如此看起来垂直方向就十分正常了。以上优化修改的代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 0223eda53dae64ded4aa99a0a4506b6c89af207e
以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 0223eda53dae64ded4aa99a0a4506b6c89af207e
获取代码之后,进入 SkiaSharp/RebeduryaiNarjargeka 文件夹,即可获取到源代码
更多技术博客,请参阅 博客导航
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/WPF-%E6%8E%A2%E7%B4%A2-Skia-%E7%9A%84%E7%AB%96%E6%8E%92%E6%96%87%E6%9C%AC%E6%B8%B2%E6%9F%93%E7%9A%84%E5%AD%97%E7%AC%A6%E9%AB%98%E5%BA%A6.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航
本作品采用
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:
https://blog.lindexi.com
),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请
与我联系
。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利