c# - 计算字体大小以使文本适合特定宽度

标签 c# gdi+

我有一个改变大小的图像,我想知道我应该使用哪种字体大小来适应动态变化的大小。
如您所知,有一个Graphics.MeasureString方法,它计算字符串的大小。一种可能的方法是测量每种字体大小,直到找到最合适的字体,但是由于我需要在一秒钟内渲染很多帧,因此对性能的影响太大了。
给定特定的图像宽度,是否有更有效的方法来查找字体大小?

最佳答案

首先,我很确定没有不使用 Graphics.MeasureString() 的方法。 .至少,您必须分析 GDI+ 字体渲染代码的重要部分,才能估计最终字体大小 since it uses its own renderer .
幸运的是,有一些方法可以大大减少 MeasureString() 的数量。每帧调用到一个小常数 (甚至为零),具体取决于您使用的字体。

等宽字体
如果你使用等宽字体,事情很简单:

  • 测量任意字符的边界,对于 n不同的字体大小。
  • 将图像宽度除以文本长度,得到字符的最大宽度。
  • 使用预先计算的边界列表来确定字体大小。
  • 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];
    }
    
    可惜网上各种C#编译器都不支持GDI+,不过我上传了self-contained sample program as a Gist .该程序测试了各种不同的图像大小,同时记录了对 MeasureString() 的调用次数。 .MeasureString() 的来电次数: 0启动期间的次数,23每帧次数。
    因此,(在实践中)这个算法有 恒定复杂性 并且比线性方法更有效,同时它仍然可以找到最佳的整数字体大小。
    这种方法可以通过添加额外的检查来进一步优化,以便保存另外一个或两个 MeasureString()调用,如果特定的字体和字符集允许的话。
    备注 :
  • 示例实现适用于宽度和高度;由于您的问题表明您只对宽度感兴趣,因此您可以安全地删除高度部分。
  • 我只为整数字体大小实现了这个。如果渲染的文本确实应该尽可能大,那么在估计的大小和小的(可能是恒定的)偏移之间进行额外的二进制搜索应该会产生一个很好的近似值,同时保持 MeasureString() 的数量。叫小。
  • 在示例实现中,我预先分配了所有 n Font对象并将它们放入 _fontCache列表,为方便起见。从性能的角度来看,这可能没有必要;您可能想要测量分配 Font 的开销目的。
  • 您不必将字体大小限制在 max .升级也是可能的,但您可能会失去精度。
  • 您可以利用有关呈现文本内容的其他信息。例如,如果只有 m可能的字符串,缓存其计算的字体大小可能会有所帮助。
  • 关于c# - 计算字体大小以使文本适合特定宽度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62608763/

    相关文章:

    c# - 为分布式事务处理协调器 (msdtc.exe) 添加防火墙规则

    c# - ServerPool 中的 MySqlClient 黑名单服务器

    c# - 绘制圆后如何将其视为控件? - 移动和选择形状

    c# - 位图字体渲染和字距微调

    c++ - 从设备上下文或 gdiplus (GDI+) 中的图形对象获取图像/位图

    c++ - 删除 GDIPlus 位图不能减少内存使用

    c# - 用。。。来代替\

    c# - 使用 sqlite-net 删除行的 SQLite 异常

    c# - 使用 C# 安装项目分发图像

    c# - 克服 32 位限制,当使用由数百万个单位翻译的 System.Drawing.Graphics 表面时