C# 计算字体大小,使文本适合特定宽度
我有一个大小不断变化的图像,我想知道我应该使用哪种字体大小来适应动态变化的大小 如您所知,有一个C# 计算字体大小,使文本适合特定宽度,c#,gdi+,C#,Gdi+,我有一个大小不断变化的图像,我想知道我应该使用哪种字体大小来适应动态变化的大小 如您所知,有一个Graphics.MeasureString方法,用于计算字符串的大小。一种可能的方法是测量每个字体大小,直到找到最佳匹配,但由于我需要在一秒钟内渲染很多帧,因此对性能的影响太大 在给定特定图像宽度的情况下,有没有更有效的方法来确定字体大小?首先,我非常确定没有一种方法不使用Graphics.MeasureString()。至少,您必须分析GDI+字体呈现代码的重要部分,以便获得最终字体大小的估计值
Graphics.MeasureString
方法,用于计算字符串的大小。一种可能的方法是测量每个字体大小,直到找到最佳匹配,但由于我需要在一秒钟内渲染很多帧,因此对性能的影响太大
在给定特定图像宽度的情况下,有没有更有效的方法来确定字体大小?首先,我非常确定没有一种方法不使用
Graphics.MeasureString()
。至少,您必须分析GDI+字体呈现代码的重要部分,以便获得最终字体大小的估计值
幸运的是,有很多方法可以大大减少每帧调用MeasureString()
的次数,使之成为一个小常量(甚至为零),具体取决于您使用的字体
等距字体 如果您使用单空格字体,事情很简单:
MeasureString()
:n
次,0
次/帧
比例(可变间距)字体 如果使用具有可变字符大小的比例字体,则内容会变得更加复杂。正如前言中所说,很难避免调用
MeasureString()
;但是,通过计算和细化估计值,您可以将所需的调用次数显著减少到一个小常量
一般算法为:
max
)字体大小max
字体大小测量字符串s
计算估计的字体大小:est=s*max
est
尚未达到最佳值,请检查角盒并稍微调整字体大小public Font GetFont(string str, Graphics g, int imgWidth, int imgHeight)
{
// Measure with maximum sized font
var baseSize = g.MeasureString(str, _fontCache[_maxFontSize]);
// Downsample to actual image size
float widthRatio = imgWidth / baseSize.Width;
float heightRatio = imgHeight / baseSize.Height;
float minRatio = Math.Min(widthRatio, heightRatio);
int estimatedFontSize = (int)(_maxFontSize * minRatio);
// Make sure the precomputed font list is always hit
if(estimatedFontSize > _maxFontSize)
estimatedFontSize = _maxFontSize;
else if(estimatedFontSize < _minFontSize)
estimatedFontSize = _minFontSize;
// Make sure the estimated size is not too large
var estimatedSize = g.MeasureString(str, _fontCache[estimatedFontSize]);
bool estimatedSizeWasReduced = false;
while(estimatedSize.Width > imgWidth || estimatedSize.Height > imgHeight)
{
if(estimatedFontSize == _minFontSize)
break;
--estimatedFontSize;
estimatedSizeWasReduced = true;
estimatedSize = g.MeasureString(str, _fontCache[estimatedFontSize]);
++counter;
}
// Can we increase the size a bit?
if(!estimatedSizeWasReduced)
{
while(estimatedSize.Width < imgWidth && estimatedSize.Height < imgHeight)
{
if(estimatedFontSize == _maxFontSize)
break;
++estimatedFontSize;
estimatedSize = g.MeasureString(str, _fontCache[estimatedFontSize]);
}
// We increase the size until it is larger than the image, so we need to go back one step afterwards
if(estimatedFontSize > _minFontSize)
--estimatedFontSize;
}
return _fontCache[estimatedFontSize];
}
公共字体GetFont(字符串str、图形g、int-imgWidth、int-imgHeight)
{
//用最大字体测量
var baseSize=g.MeasureString(str,_-fontCache[_-maxFontSize]);
//降采样到实际图像大小
浮动宽度比=imgWidth/baseSize.Width;
浮动高度比=imghight/baseSize.高度;
浮动最小比率=数学最小值(宽度比率、高度比率);
int estimatedFontSize=(int)(_maxFontSize*minRatio);
//确保始终点击预计算字体列表
如果(估计字体大小>\u最大字体大小)
estimatedFontSize=_maxFontSize;
else if(估计字体大小<\u minFontSize)
estimatedFontSize=_minFontSize;
//确保估计的大小不是太大
var estimatedSize=g.MeasureString(str,_fontCache[estimatedFontSize]);
bool estimatedSizeWasReduced=假;
而(estimatedSize.Width>imgWidth | | estimatedSize.Height>imgHeight)
{
如果(估计的字体大小==\u最小字体大小)
打破
--估计字体大小;
estimatedSizeWasReduced=真;
estimatedSize=g.MeasureString(str,_fontCache[estimatedFontSize]);
++计数器;
}
//我们能把尺寸再大一点吗?
如果(!估计尺寸减小)
{
while(estimatedSize.WidthMeasureString()
的次数
对MeasureString()
的调用次数:启动期间0
次,每帧2次或3次
因此,(在实践中)该算法具有恒定复杂性,比线性方法效率更高,同时仍能找到最佳整数字体大小
如果特定字体和字符集允许,可以通过添加额外的检查来进一步优化此方法,以保存另一个或两个MeasureString()
调用
注释:
- 示例实现适用于宽度和高度;因为您的问题表明您只对宽度感兴趣,所以可以安全地删除高度部分
- 我只为整数字体大小实现了这个。如果呈现的文本确实应该尽可能大,那么在估计大小和较小(可能恒定)偏移量之间进行额外的二进制搜索应该会产生一个很好的近似值,同时保持
MeasureString()
调用的数量较小
- 在示例实现中,为了方便起见,我预先分配了所有
n
Font
对象,并将它们放入\u fontCache
列表中。从性能角度来看,这可能不是必需的;您可能需要测量分配Font
对象的开销
- 您不必将字体大小限制在
max
。放大也是可能的,但可能会失去精度
- 您可以利用有关渲染文本内容的附加信息。例如,如果有
using (Font font = new Font(new FontFamily("Times New Roman"), 45, 0, GraphicsUnit.Pixel))
{
float width = ClientSize.Width;
float height = ClientSize.Height;
e.Graphics.FillRectangle(Brushes.White, 0, 0, width, height);
SizeF textSize = e.Graphics.MeasureString("Hello World", font);
float scale = Math.Min(width / textSize.Width, height / textSize.Height);
e.Graphics.ScaleTransform(scale, scale);
e.Graphics.DrawString("Hello World", font, Brushes.Black, new PointF(0, 0));
}