带有Swing中的小字体的字符串的边界 [英] Bounds of a String with a small Font in Swing

查看:163
本文介绍了带有Swing中的小字体的字符串的边界的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

关于计算应该被绘制到Swing组件中的字符串的大小(宽度或高度)有很多(很多)问题。并有许多建议的解决方案。不过,我注意到这些解决方案中的大部分对于小字体都不能正常工作。



以下是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.Gly​​phVector;
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;
导入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);

字体baseFont = new Font(Sans Serif,Font.PLAIN,10);
字体smallFont0 = baseFont.deriveFont(0.5f);
字体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,$ b $Font + FontRenderContext));
f.getContentPane()。add(
)TextBoundsTestPanel(smallFont1,
TextBoundsTest :: computeBoundsWithFontAndFontRenderContext,
Font + FontRenderContext));

f.getContentPane()。add(
)TextBoundsTestPanel(smallFont0,
TextBoundsTest :: computeBoundsWithGlyphVectorLogicalBounds,$ b $GlyphVectorLogicalBounds));
f.getContentPane()。add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest :: computeBoundsWithGlyphVectorLogicalBounds,$ b $GlyphVectorLogicalBounds));

f.getContentPane()。add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest :: computeBoundsWithGlyphVectorVisualBounds,$ b $GlyphVectorVisualBounds));
f.getContentPane()。add(
)TextBoundsTestPanel(smallFont1,
TextBoundsTest :: computeBoundsWithGlyphVectorVisualBounds,$ b $GlyphVectorVisualBounds));

f.getContentPane()。add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest :: computeBoundsWithTextLayout,
TextLayout));
f.getContentPane()。add(
)TextBoundsTestPanel(smallFont1,
TextBoundsTest :: computeBoundsWithTextLayout,
TextLayout));


f.setSize(600,800);
f.setLocationRelativeTo(null);
f.setVisible(true);

$ b $ private static Rectangle2D computeBoundsWithFontMetrics(
String string,Graphics2D g)
{
FontMetrics fontMetrics = g.getFontMetrics();
Rectangle2D bounds = fontMetrics.getStringBounds(string,g);
返回界限;

$ b $ private static Rectangle2D computeBoundsWithFontAndFontRenderContext(
String string,Graphics2D g)
{
FontRenderContext fontRenderContext =
FontRenderContext(g.getTransform (),true,true);
Font font = g.getFont();
Rectangle2D bounds = font.getStringBounds(string,fontRenderContext);
返回界限;


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

$ b $ private static Rectangle2D computeBoundsWithTextLayout(
String string,Graphics2D g)
{
FontRenderContext fontRenderContext = g.getFontRenderContext();
Font font = g.getFont();
TextLayout textLayout = new TextLayout(string,font,fontRenderContext);
返回textLayout.getBounds();





$ b class TextBoundsTestPanel继承了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;

$ b $覆盖$ b $保护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进行计算。但是这两种方法都非常昂贵,因为它们需要创建字符串的形状和大量的辅助对象。此外,它们只返回视觉边界(即实际形状的边界),而不是文本的逻辑边界。

是否有用于计算小字体字符串的逻辑范围的高效解决方案? 解决方案

您可以先将字体标准化。然后测量,然后通过字体的真实 size2D 缩放矩形的尺寸。

  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();
返回新的Rectangle2D.Double(bounds.getX()* scale,
bounds.getY()* scale,
bounds.getWidth()* scale,
bounds.getHeight )*比例);
}

p>

  TextBoundsCalculator textBoundsCalculator = TextBoundsCalculator.forFont(smallFontX); 

Rectangle2D bounds = textBoundsCalculator.boundsFor(string,g);

其中 TextBoundsCalculator

  import java.awt。*; 
import java.awt.geom.Rectangle2D;

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

私有MeasureStrategy measureStrategy;

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


public static TextBoundsCalculator forFont(Font font){
if(font.getSize()== 0)
返回新的TextBoundsCalculator(新的ScaleMeasureStrategy(font) );

//这个错误似乎只有在font.getSize()== 0的时候才会出现。
//所以没有必要使用字体
//进行标准化,度量和缩放,如果不是这样的话
返回新的TextBoundsCalculator(新的NormalMeasureStrategy(font));


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


私有静态类ScaleMeasureStrategy实现了MeasureStrategy {
private final float scale;
private final Font normalizedFont;

Public Sc​​aleMeasureStrategy(Font font){
scale = font.getSize2D();
normalizedFont = font.deriveFont(1f);


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



private static class NormalMeasureStrategy实现了MeasureStrategy {
private final Font font;

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

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

$ b $ 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);
}
}


There are many (many) questions about computing the size (width or height) of a string that should be painted into a Swing component. And there are many proposed solutions. However, I noticed that most of these solutions do not work properly for small fonts.

The following is an MCVE that shows some of the approaches:

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

The result of this program is shown in this screenshot:

As one can see, the simple methods work nicely for a font with size 0.5, but suddenly bail out and return bounds with a height of 0.0 for the font with size 0.4.

(Side note: I wonder whether this is simply a bug - although it might be caused by some internal round-off-errors, as it happens exactly between font sizes of 0.5 and 0.49 ...)

The only solutions that work for these smaller fonts are the computation using a GlyphVector, or the TextLayout. But both of these approaches are tremendously expensive, as they require the creation of the Shape of the string and lots of auxiliary objects. Furthermore, they only return the visual bounds (that is, the bounds of the actual shape), and not the logical bounds of the text.

Is there any efficient solution for computing the logical bounds of strings in small fonts?

解决方案

You can normalize the font first. Measure that then scale the dimensions of the rectangle by the true size2D of the font.

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

Then obviously you can cache the normalized font and hide this work around inside a calculator class, something like this:

TextBoundsCalculator textBoundsCalculator = TextBoundsCalculator.forFont(smallFontX);

Rectangle2D bounds = textBoundsCalculator.boundsFor(string, g);

Where TextBoundsCalculator:

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

这篇关于带有Swing中的小字体的字符串的边界的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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