【问题标题】:Finding maximum font size to fit in region - Java找到适合区域的最大字体大小 - Java
【发布时间】:2012-09-18 21:21:39
【问题描述】:

我的 JPanel 中有一个区域,以点 (0,0) 和 (width,height) 为界。这是一个正方形。

我有话说

String s;

我想找到s 可以使用的最大字体大小。现在,我知道有一种方法可以使用 FontMetrics 并制作一个 for 循环来不断增加字体的大小,直到它不适合该区域。但这太低效了,必须有一种方法来计算给定字体类型的字体大小,例如适合该区域的“Courier”。

BAD方式示例:

Font f = new Font("Courier", Font.PLAIN, 1);
FontMetrics fm = this.getFontMetrics(f); //this is a JPanel
do {
    f = new Font("Courier", Font.PLAIN, f.getSize()+1);
    fm = this.getFontMetrics(f);
while(fm.stringWidth(s) < width && fm.getHeight() < height);

【问题讨论】:

  • 对于非等宽字体,每个字符可以是不同的宽度。因此,您必须遍历字符串的长度,将每个字符的相对宽度相加,然后乘以当前字体大小的一个因子。这听起来和您已经描述的方法一样“低效”。
  • “但这太低效了” - 你需要它有多高效?我已经成功地在生产代码中实现了这种方法,每秒绘制数百个带有文本的多边形,并且运行得非常顺利 - 它是在 Java 1.4 上实现的。
  • 所以你们俩本质上是说这是最好的方法吗?我只是假设会有更好的东西。
  • @CodeGuy:是的,差不多。我的意思是,您是否真的因此而经历了显着的绩效放缓?如果没有,我不会费心去优化它。从理论上讲,您可以使用稍微好一点的算法来加快速度,但这不值得。比较掉蛋问题:classic-puzzles.blogspot.com/2006/12/…

标签: java jpanel fontmetrics


【解决方案1】:

我遇到了同样的问题,并找到了一个稍微优化的解决方案,与仅迭代所有字体大小相比。我尝试通过调整我添加或减去的差异来收敛到最佳字体大小,直到我发现差异字体大小低于 1。

Graphics2D graphics = image.createGraphics();
graphics.setColor(Color.black);

if (subtitleFont == null) {
    //create rectangle first (from a separate library
    int[] rect = matrix.getEnclosingRectangle();
    // define the maximum rect for the text
    Rectangle2D maxRect = new Rectangle2D.Float(0, 0, w - 7, h - rect[0] - rect[3] - 10);

    subtitleX = 0;
    subtitleY = 0;
    // starting with a very big font due to a high res image
    float size = 80f * 4f;
    // starting with a diff half the size of the font
    float diff = size / 2;
    subtitleFont = graphics.getFont().deriveFont(Font.BOLD).deriveFont(size);
    FontMetrics fontMetrics = graphics.getFontMetrics(subtitleFont);
    Rectangle2D stringBounds = null;

    while (Math.abs(diff) > 1) {
        subtitleFont = subtitleFont.deriveFont(size);
        graphics.setFont(subtitleFont);
        fontMetrics = graphics.getFontMetrics(subtitleFont);
        stringBounds = fontMetrics.getStringBounds(options.subtitle, graphics);
        stringBounds = new Rectangle2D.Float(0f, 0f, (float) (stringBounds.getX() + stringBounds.getWidth()), (float) ( stringBounds.getHeight()));

        if (maxRect.contains(stringBounds)) {
            if (0 < diff) {
                diff = Math.abs(diff);
            } else if (diff < 0) {
                diff = Math.abs(diff) / 2;
            }
        } else {
            if (0 < diff) {
                diff = - Math.abs(diff) / 2;
            } else if (diff < 0) {
                if (size <= Math.abs(diff)) {
                    diff = - Math.abs(diff) / 2;
                } else {
                    diff = - Math.abs(diff);
                }
            }
        }
        size += diff;
    }

    subtitleX = (int) ((w/2) - (stringBounds.getWidth() / 2));
    subtitleY = (int) (h - maxRect.getHeight() + fontMetrics.getAscent());
}

graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics.drawString(options.subtitle, subtitleX, subtitleY);

我已经尝试过使用不同的图像分辨率和字体大小。需要 10 到 12 次迭代,直到找到适合最大矩形的字体。我希望它会对某人有所帮助。

【讨论】:

    【解决方案2】:

    是的,我也遇到了同样的问题,我知道你所说的“效率低下”是什么意思。

    我尝试了一个解决方案,通过字体大小测量字符串的单位计数:1(默认)。

    例如:

    String line = "This is a test";
    Font font = Font.createFont(Font.TRUETYPE_FONT, new File("/var/fonts/times.ttf"));
    FontRenderContext ctx = new FontRenderContext(font.getTransform(), false, false);
    Rectangle2D rect = font.getStringBounds(line, ctx);
    BigDecimal widthUnits = BigDecimal.valueof(rect.getWidth());
    

    然后,按字体大小 1 得到 line 的 widthUnits。如果它除以区域宽度并保留整数部分(向下舍入),您将获得水平方向的最大字体大小。

    int maxSizeHor = BigDecimal.valueOf(width)
                     .divide(widthUnits, 1, BigDecimal.ROUND_DOWN)
                     .intValue();
    

    但是,这还不够。您还应该垂直计算最大字体大小。幸运的是,具有相同字体样式和字体大小的每一行具有相同的高度。如果有多行,你可以使用:

    lines.length * each height in font size 1.
    

    例如:

    String[] lines = new String{"This is a test", "Hello World!"};
    BigDecimal heightUnits = BigDecimal.valueOf(rect.getHeight())
                             .multiply(BigDecimal.valueOf(lines.length));
    int maxSizeVer = BigDecimal.valueOf(height)
                     .divide(heightUnits
                             .multiply(BigDecimal.valueOf(lines.length)), 
                            1,BigDecimal.ROUND_DOWN)
                     .intValue();
    

    最后,比较最小值作为最大字体大小:

    Math.min(maxSizeHor, maxSizeVer)
    

    但是,我遇到了另一个问题:单个字符 'i' 作为字体大小的输入字符串:1,您将获得宽度单位:0.0。但是,实际上,尽管它是一个非常小的值,但它不会为零。所以我将默认字体大小设置为 1000,并在每次进度后除以 1000。然后我得到非零值。

    【讨论】:

      猜你喜欢
      • 2016-10-06
      • 1970-01-01
      • 2016-04-16
      • 1970-01-01
      • 2012-05-27
      • 2012-03-08
      • 1970-01-01
      • 2021-11-11
      • 1970-01-01
      相关资源
      最近更新 更多