Java Swing中使用小字体的字符串的边界

Java Swing中使用小字体的字符串的边界,java,swing,fonts,Java,Swing,Fonts,关于计算应该绘制到Swing组件中的字符串的大小(宽度或高度),有很多问题。有很多建议的解决方案。然而,我注意到这些解决方案中的大多数都不能正确地用于小字体 以下是一个示例,展示了一些方法: import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout

关于计算应该绘制到Swing组件中的字符串的大小(宽度或高度),有很多问题。有很多建议的解决方案。然而,我注意到这些解决方案中的大多数都不能正确地用于小字体

以下是一个示例,展示了一些方法:

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);
    }
}
导入java.awt.Color;
导入java.awt.Font;
导入java.awt.FontMetrics;
导入java.awt.Graphics;
导入java.awt.Graphics2D;
导入java.awt.GridLayout;
导入java.awt.Shape;
导入java.awt.font.FontRenderContext;
导入java.awt.font.GlyphVector;
导入java.awt.font.TextLayout;
导入java.awt.geom.AffineTransform;
导入java.awt.geom.Rectangle2D;
导入java.util.function.BiFunction;
导入javax.swing.JFrame;
导入javax.swing.JPanel;
导入javax.swing.SwingUtilities;
公共类TextBoundsTest
{
公共静态void main(字符串[]args)
{
SwingUtilities.invokeLater(新的Runnable()
{
@凌驾
公开募捐
{
createAndShowGUI();
}
});
}
私有静态void createAndShowGUI()
{
JFrame f=新的JFrame();
f、 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Font baseFont=新字体(“无衬线”,Font.PLAIN,10);
Font smallFont0=baseFont.deriveFont(0.5f);
Font smallFont1=baseFont.deriveFont(0.4f);
f、 getContentPane().setLayout(新的GridLayout(5,2));
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithFontMetrics,
"丰达");;
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithFontMetrics,
"丰达");;
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithFontAndFontRenderContext,
“字体+字体渲染上下文”);
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithFontAndFontRenderContext,
“字体+字体渲染上下文”);
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithGlyphVectorLogicalBounds,
“符号向量逻辑边界”);
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithGlyphVectorLogicalBounds,
“符号向量逻辑边界”);
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithGlyphVectorVisualBounds,
“GlyphVectorVisualBounds”);
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithGlyphVectorVisualBounds,
“GlyphVectorVisualBounds”);
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithTextLayout,
“文本布局”);
f、 getContentPane().add(
新的TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithTextLayout,
“文本布局”);
f、 设置大小(600800);
f、 setLocationRelativeTo(空);
f、 setVisible(真);
}
私有静态矩形2D computeBoundsWithFontMetrics(
字符串,图形2d(g)
{
FontMetrics FontMetrics=g.getFontMetrics();
矩形2D边界=fontMetrics.getStringBounds(字符串,g);
返回边界;
}
私有静态矩形2D计算边界SwithFontAndFontRenderContext(
字符串,图形2d(g)
{
FontRenderContext FontRenderContext=
新的FontRenderContext(g.getTransform(),true,true);
Font Font=g.getFont();
矩形2D边界=font.getStringBounds(字符串,fontRenderContext);
返回边界;
}
私有静态矩形2D计算边界WithGlyphVectorLogicalBounds(
字符串,图形2d(g)
{
FontRenderContext FontRenderContext=g.getFontRenderContext();
Font Font=g.getFont();
GlyphVector GlyphVector=font.createGlyphVector(
fontRenderContext,字符串);
返回glyphVector.getLogicalBounds();
}
私有静态矩形2D计算边界WithGlyphVectorVisualBounds(
字符串,图形2d(g)
{
FontRenderContext FontRenderContext=g.getFontRenderContext();
Font Font=g.getFont();
GlyphVector GlyphVector=font.createGlyphVector(
fontRenderContext,字符串);
返回glyphVector.getVisualBounds();
}
专用静态矩形2D computeBoundsWithTextLayout(
字符串,图形2d(g)
{
FontRenderContext FontRenderContext=g.getFontRenderContext();
Font Font=g.getFont();
text布局text布局=新的文本布局(字符串、字体、fontRenderContext);
返回textLayout.getBounds();
}
}
类TextBoundsTestPanel扩展了JPanel
{
私有最终字体textFont;
专用最终双功能边界计算机;
私有最终字符串边界计算机名称;
TextBoundsTestPanel(字体textFont,
双功能边界计算机,
字符串边界(计算机名称)
{
this.textFont=textFont;
this.boundsComputer=boundsComputer;
this.boundsComputerName=boundsComputerName;
}
@凌驾
受保护的组件(图形组)
{
超级油漆组件(gr);
Graphics2D g=(Graphics2D)gr;
g、 setColor(Color.WHITE);
g、 fillRect(0,0,getWidth(),getHeight());
g、 设置颜色(颜色为黑色);
g、 抽绳(“字号:”+textFont.getSize2D(),10,20);
g、 抽绳
private static Rectangle2D computeBoundsUsingNormalizedFont(
        String string, Graphics2D g) {
    Font normalizedFont = g.getFont().deriveFont(1f);
    Rectangle2D bounds = normalizedFont.getStringBounds(string, g.getFontRenderContext());

    float scale = g.getFont().getSize2D();
    return new Rectangle2D.Double(bounds.getX() * scale,
            bounds.getY() * scale,
            bounds.getWidth() * scale,
            bounds.getHeight() * scale);
}
TextBoundsCalculator textBoundsCalculator = TextBoundsCalculator.forFont(smallFontX);

Rectangle2D bounds = textBoundsCalculator.boundsFor(string, g);
import java.awt.*;
import java.awt.geom.Rectangle2D;

public final class TextBoundsCalculator {
    private interface MeasureStrategy {
        Rectangle2D boundsFor(String string, Graphics2D g);
    }

    private MeasureStrategy measureStrategy;

    private TextBoundsCalculator(MeasureStrategy measureStrategy) {
        this.measureStrategy = measureStrategy;
    }

    public static TextBoundsCalculator forFont(Font font) {
        if (font.getSize() == 0)
            return new TextBoundsCalculator(new ScaleMeasureStrategy(font));

        // The bug appears to be only when font.getSize()==0.
        // So there's no need to normalize, measure and scale with fonts
        // where this is not the case
        return new TextBoundsCalculator(new NormalMeasureStrategy(font));
    }

    public Rectangle2D boundsFor(String string, Graphics2D g) {
        return measureStrategy.boundsFor(string, g);
    }

    private static class ScaleMeasureStrategy implements MeasureStrategy {
        private final float scale;
        private final Font normalizedFont;

        public ScaleMeasureStrategy(Font font) {
            scale = font.getSize2D();
            normalizedFont = font.deriveFont(1f);
        }

        public Rectangle2D boundsFor(String string, Graphics2D g) {
            Rectangle2D bounds = NormalMeasureStrategy.boundsForFont(normalizedFont, string, g);
            return scaleRectangle2D(bounds, scale);
        }
    }

    private static class NormalMeasureStrategy implements MeasureStrategy {
        private final Font font;

        public NormalMeasureStrategy(Font font) {
            this.font = font;
        }

        public Rectangle2D boundsFor(String string, Graphics2D g) {
            return boundsForFont(font, string, g);
        }

        private static Rectangle2D boundsForFont(Font font, String string, Graphics2D g) {
            return font.getStringBounds(string, g.getFontRenderContext());
        }
    }

    private static Rectangle2D scaleRectangle2D(Rectangle2D rectangle2D, float scale) {
        return new Rectangle2D.Double(
                rectangle2D.getX() * scale,
                rectangle2D.getY() * scale,
                rectangle2D.getWidth() * scale,
                rectangle2D.getHeight() * scale);
    }
}