Подтвердить что ты не робот

Как получить реальную высоту строки в Java?

Я использую FontMetrics.getHeight(), чтобы получить высоту строки, но это дает мне неправильное значение, отсекая дескрипторы строковых символов. Есть ли лучшая функция, которую я могу использовать?

4b9b3361

Ответ 1

Ниже приведен метод getStringBounds() на основе GlyphVector для текущего шрифта Graphics2D, который очень хорошо работает для одной строки строки текста:

public class StringBoundsPanel extends JPanel
{
    public StringBoundsPanel()
    {
        setBackground(Color.white);
        setPreferredSize(new Dimension(400, 247));
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);

        // must be called before getStringBounds()
        g2.setFont(getDesiredFont());

        String str = "My Text";
        float x = 140, y = 128;

        Rectangle bounds = getStringBounds(g2, str, x, y);

        g2.setColor(Color.red);
        g2.drawString(str, x, y);

        g2.setColor(Color.blue);
        g2.draw(bounds);

        g2.dispose();
    }

    private Rectangle getStringBounds(Graphics2D g2, String str,
                                      float x, float y)
    {
        FontRenderContext frc = g2.getFontRenderContext();
        GlyphVector gv = g2.getFont().createGlyphVector(frc, str);
        return gv.getPixelBounds(null, x, y);
    }

    private Font getDesiredFont()
    {
        return new Font(Font.SANS_SERIF, Font.BOLD, 28);
    }

    private void startUI()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(this);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) throws Exception
    {
        final StringBoundsPanel tb = new StringBoundsPanel();

        SwingUtilities.invokeAndWait(new Runnable()
        {
            public void run()
            {
                tb.startUI();
            }
        });
    }
}

Заметьте, что я явно не указал импорт.

Результат:

Result screenshot.

Ответ 2

Почему вы думаете, что он возвращает неправильное значение? Это гораздо более вероятно, что ваше ожидание того, что оно возвращает, не соответствует спецификации. Обратите внимание, что это отлично, если некоторые глифы в Шрифте переходят или под этими значениями.

getMaxDescent() и getMaxAscent() должны указывать абсолютные максимальные значения этих полей для любого глифа в шрифте.

Если вы хотите знать метрики для определенной строки, то вы определенно хотите вызвать getLineMetrics().

Ответ 4

Недавно я написал код ниже, поскольку мне нужны пиксельные измерения идеальной высоты для определенных диапазонов шрифта (например: все нижние символы или все числа).

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

В основном код рисует все символы из входной строки в одном и том же месте, наложенные на растровое изображение 250x250 (при необходимости увеличивайте или уменьшайте), он начинает искать пиксели сверху, затем снизу, а затем возвращает максимальную высоту. Он работает с обычными строками, даже если он предназначен для диапазонов символов. Это означает, что есть некоторая избыточность при оценке регулярных строк, когда повторяются некоторые из символов. Поэтому, если ваша строка imput превышает число алфавитов (26), используйте как "tRange" imput: "abcd... z" и другие символы, которые могут быть использованы. Это быстрее.

Надеюсь, что это поможет.

public int getFontPixelHeight(float inSize, Paint sourcePaint, String tRange)
{
    // It is assumed that the font is already set in the sourcePaint

    int bW = 250, bH = 250;                                     // bitmap width and height
    int firstContact = -1, lastContact = -2;                    // Used when scanning the pixel rows. Initial values are set so that if no pixels found, the returned result is zero.
    int tX = (int)(bW - inSize)/2, tY = (int)(bH - inSize)/2;   // Used for a rough centering of the displayed characters

    int tSum = 0;

    // Preserve the original paint attributes
    float oldSize = sourcePaint.getTextSize();
    int oldColor = sourcePaint.getColor();
    // Set the size/color
    sourcePaint.setTextSize(inSize); sourcePaint.setColor(Color.WHITE);

    // Create the temporary bitmap/canvas
    Bitmap.Config bConf = Bitmap.Config.ARGB_8888;
    Bitmap hld = Bitmap.createBitmap(250, 250, bConf);
    Canvas canv = new Canvas(hld);

    for (int i = 0; i < bH; i++)
    {
        for (int j = 0; j < bW; j++)
        {
            hld.setPixel(j, i, 0); // Zero all pixel values. This might seem redundant, but I am not quite sure that creating a blank bitmap means the pixel color value is indeed zero, and I need them to be zero so the addition performed below is correct.
        }
    }

    // Display all characters overlapping at the same position
    for (int i = 0; i < tRange.length(); i++)
    {
        canv.drawText("" + tRange.charAt(i), tX, tY, sourcePaint);
    }

    for (int i = 0; i < bH; i++)
    {
        for (int j = 0; j < bW; j++)
        {
            tSum = tSum + hld.getPixel(j, i);
        }

        if (tSum > 0) // If we found at least a pixel, save row index and exit loop
        {
            firstContact = i;
            tSum = 0;   // Reset
            break;
        }   
    }

    for (int i = bH - 1; i > 0 ; i--)
    {
        for (int j = 0; j < bW; j++)
        {
            tSum = tSum + hld.getPixel(j, i);
        }

        if (tSum > 0) // If we found at least a pixel, save row index and exit loop
        {
            lastContact = i;
            break;
        }   
    }

    // Restore the initial attributes, just in case the paint was passed byRef somehow
    sourcePaint.setTextSize(oldSize);
    sourcePaint.setColor(oldColor);

    return lastContact - firstContact + 1;
}

Ответ 5

getHeight() не может обрезать дескрипторы строки, это может сделать только рисование строки. Вы используете высоту, возвращаемую из getHeight, чтобы как-то рисовать строку, и, вероятно, вы неправильно используете высоту. Например, если вы позиционируете начальную точку строки в нижней части окна с высоким значением getHeight(), базовый текст будет располагаться в нижнем краю окна, и, скорее всего, ограничители будут обрезаны.

Текстовая геометрия - сложная тема, наполненная необычными историческими артефактами. Как предложили другие, используйте getAscent и getDescent, чтобы попытаться правильно установить базовую линию в вашем поле.