关于计算应绘制到 Swing 组件中的字符串的大小(宽度或高度),存在许多(许多)问题。并且提出了许多解决方案。然而,我注意到这些解决方案中的大多数都not对于小字体可以正常工作。
下面是一个MCVE https://stackoverflow.com/help/mcve这显示了一些方法:
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.function.BiFunction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TextBoundsTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Font baseFont = new Font("Sans Serif", Font.PLAIN, 10);
Font smallFont0 = baseFont.deriveFont(0.5f);
Font smallFont1 = baseFont.deriveFont(0.4f);
f.getContentPane().setLayout(new GridLayout(5,2));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithFontMetrics,
"FontMetrics"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithFontMetrics,
"FontMetrics"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithFontAndFontRenderContext,
"Font+FontRenderContext"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithFontAndFontRenderContext,
"Font+FontRenderContext"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithGlyphVectorLogicalBounds,
"GlyphVectorLogicalBounds"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithGlyphVectorLogicalBounds,
"GlyphVectorLogicalBounds"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithGlyphVectorVisualBounds,
"GlyphVectorVisualBounds"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithGlyphVectorVisualBounds,
"GlyphVectorVisualBounds"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithTextLayout,
"TextLayout"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithTextLayout,
"TextLayout"));
f.setSize(600,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static Rectangle2D computeBoundsWithFontMetrics(
String string, Graphics2D g)
{
FontMetrics fontMetrics = g.getFontMetrics();
Rectangle2D bounds = fontMetrics.getStringBounds(string, g);
return bounds;
}
private static Rectangle2D computeBoundsWithFontAndFontRenderContext(
String string, Graphics2D g)
{
FontRenderContext fontRenderContext =
new FontRenderContext(g.getTransform(),true, true);
Font font = g.getFont();
Rectangle2D bounds = font.getStringBounds(string, fontRenderContext);
return bounds;
}
private static Rectangle2D computeBoundsWithGlyphVectorLogicalBounds(
String string, Graphics2D g)
{
FontRenderContext fontRenderContext = g.getFontRenderContext();
Font font = g.getFont();
GlyphVector glyphVector = font.createGlyphVector(
fontRenderContext, string);
return glyphVector.getLogicalBounds();
}
private static Rectangle2D computeBoundsWithGlyphVectorVisualBounds(
String string, Graphics2D g)
{
FontRenderContext fontRenderContext = g.getFontRenderContext();
Font font = g.getFont();
GlyphVector glyphVector = font.createGlyphVector(
fontRenderContext, string);
return glyphVector.getVisualBounds();
}
private static Rectangle2D computeBoundsWithTextLayout(
String string, Graphics2D g)
{
FontRenderContext fontRenderContext = g.getFontRenderContext();
Font font = g.getFont();
TextLayout textLayout = new TextLayout(string, font, fontRenderContext);
return textLayout.getBounds();
}
}
class TextBoundsTestPanel extends JPanel
{
private final Font textFont;
private final BiFunction<String, Graphics2D, Rectangle2D> boundsComputer;
private final String boundsComputerName;
TextBoundsTestPanel(Font textFont,
BiFunction<String, Graphics2D, Rectangle2D> boundsComputer,
String boundsComputerName)
{
this.textFont = textFont;
this.boundsComputer = boundsComputer;
this.boundsComputerName = boundsComputerName;
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
g.drawString("Font size: "+textFont.getSize2D(), 10, 20);
g.drawString("Bounds : "+boundsComputerName, 10, 40);
AffineTransform oldAt = g.getTransform();
AffineTransform at = AffineTransform.getScaleInstance(50, 50);
g.transform(at);
g.translate(1, 2);
g.setFont(textFont);
String string = "Test";
g.drawString(string, 0, 0);
Rectangle2D bounds = boundsComputer.apply(string, g);
Shape boundsShape = at.createTransformedShape(bounds);
g.setTransform(oldAt);
g.setColor(Color.RED);
g.translate(50, 100);
g.draw(boundsShape);
}
}
该程序的结果如以下屏幕截图所示:
正如您所看到的,这些简单的方法对于大小为 0.5 的字体效果很好,但对于大小为 0.4 的字体突然跳出并返回高度为 0.0 的边界。
(旁注:我想知道这是否只是一个错误 - 尽管它可能是由一些内部舍入错误引起的,因为它恰好发生在 0.5 和 0.49 的字体大小之间......)
适用于这些较小字体的唯一解决方案是使用 GlyphVector 或 TextLayout 进行计算。但这两种方法都非常昂贵,因为它们需要创建字符串的形状和大量辅助对象。此外,他们只返回visual边界(即实际形状的边界),而不是logical文本的边界。
有没有高效的计算小字体字符串逻辑边界的解决方案?