Java-如何在矩形中可视地将特定字符串(不仅仅是字体)居中


问题内容

我试图在视觉上将用户提供的任意字符串放在JPanel上。我在这里已经阅读了许多其他类似的问题和答案,但是还没有找到任何直接解决我所遇到的问题的方法。

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

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上,小写字母(延伸到字体基线以下的部分)的下降字体绘制得太低,以致文本看起来居中。

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

....
    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和y等小写字母的字符串进行了测试,效果很好!呜呜!可是等等。啊。现在,当我只尝试使用大写字母时,JPanel上的文本太高而无法居中显示。

那就是当我发现TextLayout.getDescent()(以及我为其他类找到的所有其他getDescent()方法)返回FONT的最大下降而不是特定字符串的最大下降。因此,我的大写字符串被提高了,以解决该字符串中甚至没有出现的后代。

我是什么做的?如果我不调整drawString()的y参数以考虑到降序,则JPanel上带有降序的小写字符串在视觉上太低了。如果我确实将drawString()的y参数调整为考虑到降序,那么在JPanel上,不包含任何带有降序字符的字符串在视觉上会过高。我似乎没有任何方法可以确定基线在GIVEN字符串的文本边界矩形中的位置。因此,我无法确切确定要传递给drawString()的y。

感谢您的帮助或建议。


问题答案:

当我为之苦恼时TextLayout,您可以仅使用Graphics上下文的FontMetrics,例如…

文本

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

}

好吧,在大惊小怪之后…

基本上,文本渲染发生在基线处,这使y边界的位置通常显示在该点的上方,使其看起来像在y位置上方绘制了文本

为了克服这个问题,我们需要将字体的上升减去字体下降的y位置添加到该位置…

例如…

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

…这就是为什么我喜欢手工呈现文本的原因…

可运行的示例…

布局

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以获得更多信息…

更新

正如已经建议的,您可以使用GlyphVector…

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

猫狗

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

}