Нахождение максимального размера шрифта для размещения в регионе - Java

У меня есть область в моей JPanel, ограниченная точкой (0,0) и (ширина, высота). Это квадрат.

У меня есть слово

String s;

Я хотел бы найти максимальный размер шрифта, который я могу использовать для s, Теперь я знаю, что есть способ сделать это с помощью FontMetrics и создания цикла for, чтобы продолжать увеличивать размер шрифта, пока он не помещается внутри региона. Но это ТАК неэффективно, и должен быть способ вычислить размер шрифта для данного типа шрифта, такого как "Курьер", который будет соответствовать этой области.

Пример плохого пути:

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

2 ответа

Решение

У меня была та же проблема, и я нашел решение, которое немного оптимизировано по сравнению с простой итерацией для всех размеров шрифта. Я пытаюсь приблизиться к оптимальному размеру шрифта, регулируя различия, которые я либо добавляю, либо вычитаю, пока не найду размер шрифта разности ниже 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 итераций, пока не будет найден шрифт, который будет соответствовать максимальному прямоугольнику. Я надеюсь, что это будет полезно для кого-то.

да, я тоже столкнулся с той же проблемой, и я знаю, что вы имеете в виду под «неэффективным».

Я попробовал решение, измеряющее количество единиц для строки по размеру шрифта: 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());

затем вы получаете widthUnits строки по размеру шрифта 1. Если его разделить на ширину области и зарезервировать целую часть (округлить вниз), вы получите максимальный размер шрифта по горизонтали.

      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 после каждого прогресса. то я получаю ненулевое значение.

Другие вопросы по тегам