Java - 如何在矩形中以视觉方式居中特定字符串(而不仅仅是字体) [英] Java - How to visually center a specific string (not just a font) in a rectangle

查看:177
本文介绍了Java - 如何在矩形中以视觉方式居中特定字符串(而不仅仅是字体)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在JPanel上以视觉方式居中用户提供的任意字符串。我已经在SO上阅读了几十个其他类似的问题和答案,但没有发现任何直接解决我遇到的问题。

I am trying to visually center an arbitrary user-supplied string on a JPanel. I have read dozens of other similar questions and answers here on SO but haven't found any that directly address the problem I am having.

在下面的代码示例中,getWidth ()和getHeight()指的是我放置文本字符串的JPanel的宽度和高度。我发现 TextLayout.getBounds()非常好地告诉我包含文本的边界矩形的大小。因此,我认为通过计算文本边界矩形左下角的JPanel上的x和y位置,将文本矩形居中在JPanel矩形中会相对简单:

In the code sample below, getWidth() and getHeight() refer to the width and height of the JPanel on which I'm placing the text string. I have found that TextLayout.getBounds() does a very good job of telling me the size of a bounding rectangle that encloses the text. So, I figured that it would be relatively simple to center the text rectangle in the JPanel rectangle by calculating the x and y positions on the JPanel of the lower left corner of the text-bounding rectangle:

FontRenderContext context = g2d.getFontRenderContext();
messageTextFont = new Font("Arial", Font.BOLD, fontSize);
TextLayout txt = new TextLayout(messageText, messageTextFont, context);
Rectangle2D bounds = txt.getBounds();
xString = (int)((getWidth() - (int)bounds.getWidth()) / 2 );
yString = (int)((getHeight()/2) + (int)(bounds.getHeight()/2));

g2d.setFont(messageTextFont);
g2d.setColor(rxColor);
g2d.drawString(messageText, xString, yString);

这适用于全部大写的字符串。但是,当我开始使用带有下行字符的小写字母(如g,p,y)进行测试时,文本不再居中。小写字母上的下划线(延伸到字体基线以下的部分)在JPanel上绘制得太低,使文本看起来居中。

This worked perfectly for strings which were all uppercase. However, when I started testing with strings that contained lowercase letters with descenders (like g, p, y), the text was no longer centered. The descenders on the lower case letters (the parts that extend below the baseline of the font) were being drawn too low on the JPanel to have the text appear to be centered.

那时我发现(感谢SO)传递给 drawString()的y参数指定了绘制文本的基线,而不是下限。因此,再次在SO的帮助下,我意识到我需要通过字符串中下划线的长度来调整文本的位置:

That's when I discovered (thanks to SO) that the y parameter passed to drawString() specifies the baseline of the drawn text, not the lower bound. Thus, again with the help of SO, I realized that I needed to adjust the placement of the text by the length of the descenders in my string:

....
    TextLayout txt = new TextLayout(messageText, messageTextFont, context);
    Rectangle2D bounds = txt.getBounds();
    int descent = (int)txt.getDescent();
    xString = (int)((getWidth() - (int)bounds.getWidth()) / 2 );
    yString = (int)((getHeight()/2) + (int)(bounds.getHeight()/2) - descent);
....

我用g,p等小写字母的字符串测试了这个,而且它工作得很好!哇噢!可是等等。啊。现在,当我尝试只使用大写字母时,JPanel上的文字太高,看起来居中。

I tested this with strings heavy in lowercase letters like g, p, and y and it worked great! WooHoo! But....wait. Ugh. Now when I try with only uppercase letters, the text is way too HIGH on the JPanel to look centered.

当我发现 TextLayout.getDescent()时(以及我为其他类找到的所有其他getDescent()方法)返回 FONT 的最大下降而不是特定字符串。因此,我的大写字符串被抬高以说明在该字符串中甚至没有出现的下行符。

That's when I discovered that TextLayout.getDescent() (and all the other getDescent() methods I have found for other classes) returns the maximum descent of the FONT not of the specific string. Thus, my uppercase string was being raised up to account for descenders that didn't even occur in that string.

我该怎么办?如果我没有调整drawString()的y参数来考虑下行,那么带有下行的小写字符串在JPanel上看起来太低了。如果我确实调整了drawString()的y参数来考虑下行器,那么不包含任何带有下行器的字符的字符串在JPanel上看起来太高了。我似乎没有办法确定基线在GIVEN字符串的文本边界矩形中的位置。因此,我无法弄清楚要传递给drawString()的确切内容。

What am I to do? If I don't adjust the y parameter for drawString() to account for descenders then lowercase strings with descenders are visually too low on the JPanel. If I do adjust the y parameter for drawString() to account for the descenders then strings which do not contain any characters with descenders are visually too high on the JPanel. There doesn't seem to be any way for me to determine where the baseline is in the text-bounding rectangle for a GIVEN string. Thus, I can't figure out exactly what y to pass to drawString().

感谢您提供任何帮助或建议。

Thanks for any help or suggestions.

推荐答案

当我用 TextLayout 搞砸时,你可以使用 Graphics context的 FontMetrics ,例如......

While I muck about with TextLayout, you could just use the Graphics context's FontMetrics, for example...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

    public static void main(String[] args) {
        new LayoutText();
    }

    public LayoutText() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private String text;

        public TestPane() {
            text = "Along time ago, in a galaxy, far, far away";
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.setColor(Color.RED);
            g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
            g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

            Font font = new Font("Arial", Font.BOLD, 48);
            g2d.setFont(font);
            FontMetrics fm = g2d.getFontMetrics();
            int x = ((getWidth() - fm.stringWidth(text)) / 2);
            int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();

            g2d.setColor(Color.BLACK);
            g2d.drawString(text, x, y);

            g2d.dispose();
        }
    }

}

好的,经过一番忙碌...

Okay, after some fussing about...

基本上,文字渲染发生在基线,这使得 y 的位置边界通常出现在此点之上,使其看起来像是在 y 位置上方绘制的文字

Basically, text rendering occurs at the baseline, this makes the y position of the bounds usually appear above this point, making it look like the text is been painted above the y position

克服这一点,我们需要将字体的上升减去字体的下降添加到 y 位置......

To overcome this, we need to add the font's ascent minus the font's descent to the y position...

For示例...

FontRenderContext context = g2d.getFontRenderContext();
Font font = new Font("Arial", Font.BOLD, 48);
TextLayout txt = new TextLayout(text, font, context);

Rectangle2D bounds = txt.getBounds();
int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
y += txt.getAscent() - txt.getDescent();

...这就是为什么我喜欢手工渲染文字...

... This is why I love rendering text by hand ...

Runnable example ...

Runnable example...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

    public static void main(String[] args) {
        new LayoutText();
    }

    public LayoutText() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private String text;

        public TestPane() {
            text = "Along time ago, in a galaxy, far, far away";
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.setColor(Color.RED);
            g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
            g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

            FontRenderContext context = g2d.getFontRenderContext();
            Font font = new Font("Arial", Font.BOLD, 48);
            TextLayout txt = new TextLayout(text, font, context);

            Rectangle2D bounds = txt.getBounds();
            int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
            int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
            y += txt.getAscent() - txt.getDescent();

            g2d.setFont(font);
            g2d.setColor(Color.BLACK);
            g2d.drawString(text, x, y);

            g2d.setColor(Color.BLUE);
            g2d.translate(x, y);
            g2d.draw(bounds);

            g2d.dispose();
        }
    }

}

拿一个查看使用文本API 获取更多信息...

Take a look at Working with Text APIs for more information...

已更新

正如已经建议的那样,您可以使用 GlyphVector ...

As has already been suggested, you could use a GlyphVector...

每个单词( Cat Dog )分开计算以证明差异

Each word (Cat and Dog) is calculated separatly to demonstrate the differences

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

    public static void main(String[] args) {
        new LayoutText();
    }

    public LayoutText() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private String text;

        public TestPane() {
            text = "A long time ago, in a galaxy, far, far away";
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.setColor(Color.RED);
            g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
            g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

            Font font = new Font("Arial", Font.BOLD, 48);
            g2d.setFont(font);

            FontRenderContext frc = g2d.getFontRenderContext();
            GlyphVector gv = font.createGlyphVector(frc, "Cat");
            Rectangle2D box = gv.getVisualBounds();

            int x = 0;
            int y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
            g2d.drawString("Cat", x, y);

            x += box.getWidth();

            gv = font.createGlyphVector(frc, "Dog");
            box = gv.getVisualBounds();

            y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
            g2d.drawString("Dog", x, y);

            g2d.dispose();
        }
    }

}

这篇关于Java - 如何在矩形中以视觉方式居中特定字符串(而不仅仅是字体)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆