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.Width\u最小字体大小) --估计字体大小; } 返回_fontCache[estimatedFontSize]; } 不幸的是,各种C#在线编译器不支持GDI+,但我上传了一个。该程序测试了各种不同的图像大小,同时记录调用
    MeasureString()
    的次数

    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));
              }