WPF 探索 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/
如圖片看不見,請在瀏覽器開啟不安全http內容兼容

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名[林德熙](http://www.rzrgm.cn/lindexi)(包含鏈接:http://www.rzrgm.cn/lindexi ),不得用于商業目的,基于本文修改后的作品務必以相同的許可發布。如有任何疑問,請與我[聯系](mailto:lindexi_gd@163.com)。

浙公網安備 33010602011771號