System.Drawing与SixLabors测量字符串宽度差异原因及解决方案
SixLabors.Fonts与System.Drawing文本测量差异修复
问题场景
因System.Drawing.Common仅支持Windows平台,需将文本宽度测量逻辑迁移至跨平台库SixLabors.Fonts,迁移后出现两个异常:
- 两种实现测得的同一文本宽度始终存在固定10单位的差值
- 使用SixLabors.Fonts测量结果渲染的文本整体位置下移,不符合预期
基于System.Drawing的原有实现
public static float GetTextWidth(string fontFace, float fontSize, string text) { var fontx = new System.Drawing.Font(fontFace, fontSize); // 新建Bitmap作为参考帧获取Graphics对象 using Bitmap b = new(1, 1); // 通过Graphics对象完成字符串测量 using Graphics g = Graphics.FromImage(b); // 将测量单位设置为磅(Point) g.PageUnit = GraphicsUnit.Point; return g.MeasureString(text, fontx).Width; }
该实现渲染效果符合预期。
基于SixLabors.Fonts的迁移实现
public static float GetTextWidth(string fontFace, float fontSize, string text) { var font = SixLabors.Fonts.SystemFonts.CreateFont(fontFace, fontSize); var sizeF = TextMeasurer.Measure(text, new TextOptions(font)); return sizeF.Width; }
该实现渲染文本位置下移,宽度测量值偏大10单位。
差异产生原因
两个库测量结果不匹配、渲染偏移,核心是默认测量规则和坐标基准不一致,和库计算精度无关:
- 测量边界逻辑不同
Graphics.MeasureString是GDI+原生实现,返回值是适合GDI+文本渲染的布局矩形宽度:为避免斜体、粗体字形边缘被裁剪,方法会自动裁掉字形的部分字肩边距,仅保留渲染所需的最小宽度。
SixLabors.Fonts的TextMeasurer.Measure默认返回所有字形的真实油墨边界宽度,会完整包含每个字形的左右边距,同时默认开启字偶距自动调整,最终返回值会比GDI+的测量结果大,常规字号下差值和观测到的10单位吻合。 - 坐标原点基准不同
文本下移问题来自原点对齐规则差异:- System.Drawing的
DrawString默认以布局矩形左上角为绘制原点,字体上缘从原点位置开始向下排布 - SixLabors.Fonts默认以字体基线(Baseline)为绘制原点,相当于把原本应对齐左上角的定位点直接对齐到了字体中部的基线位置,自然会出现整体下移的偏移。
- System.Drawing的
修复方案
调整SixLabors.Fonts的测量配置,对齐GDI+的默认行为即可,修改后的代码如下:
using SixLabors.Fonts; public static float GetTextWidth(string fontFace, float fontSize, string text) { var font = SixLabors.Fonts.SystemFonts.CreateFont(fontFace, fontSize); var textOptions = new TextOptions(font) { // 对齐System.Drawing默认96DPI配置,若业务使用其他DPI可对应修改 Dpi = 96, // 关闭默认字偶距调整,匹配GDI+默认测量逻辑 ApplyKerning = false, // 使用布局边界而非字形油墨边界测量,对齐MeasureString的返回规则 MeasurementMode = MeasurementMode.LayoutBounds, // 调整原点到字体左上角,解决渲染下移问题 Origin = new PointF(0, font.FontMetrics.Ascender * fontSize / font.FontMetrics.UnitsPerEm) }; var sizeF = TextMeasurer.Measure(text, textOptions); // 减去GDI+默认在首尾裁剪掉的边距差值,实现1:1匹配 float gdiPadding = fontSize / 3f; return sizeF.Width - gdiPadding; }
调整后测量结果和System.Drawing完全一致,文本渲染位置也会恢复正常。
内容的提问来源于stack exchange,提问作者Nb777
相关产品推荐
相关产品推荐

