Как получить реальную высоту строки в Java? - PullRequest
17 голосов
/ 15 декабря 2008

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

Ответы [ 5 ]

19 голосов
/ 19 сентября 2012

Приведенный ниже метод 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.

12 голосов
/ 15 декабря 2008

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

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

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

7 голосов
/ 15 декабря 2008
3 голосов
/ 28 марта 2013

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

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

Код в основном рисует все символы из входной строки в одном и том же месте, наложенном на битовый массив 250x250 (при необходимости увеличивается или уменьшается), он начинает искать пиксели сверху, затем снизу, а затем возвращает максимальную найденную высоту. Он работает с обычными строками, даже если он был разработан для диапазонов символов. Это означает, что при оценке обычных строк возникает некоторая избыточность, поскольку некоторые символы повторяются. Поэтому, если ваша строка ввода превышает число алфавитов (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's 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;
}
2 голосов
/ 15 декабря 2008

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

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

...