As shown in the following picture, an AttributedString is drawn on a JPanel (500X500).
The FontMetrics.getStringBounds()
of that AttributedString gives a width of 164.0, as indicated by the trace output.
java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.064453,w=164.0,h=15.09375]
However, the picture suggests the width should be 300-400 (because the width of the panel is 500).
Could you help to comment the reason and the workaround ?

MyJFrame.java
import javax.swing.*;
import java.awt.*;
import java.awt.font.TextAttribute;
import java.text.AttributedString;
class MyJPanel extends JPanel {
MyJPanel() {
setPreferredSize(new Dimension(500,500));
}
@Override
public void paintComponent(Graphics gold) {
super.paintComponent(gold);
Graphics2D g = (Graphics2D)gold;
//
AttributedString text = new AttributedString("Bunny rabits and flying ponies");
text.addAttribute(TextAttribute.FONT, new Font("Arial", Font.BOLD, 24), 0, "Bunny rabits".length());
text.addAttribute(TextAttribute.FOREGROUND, Color.RED, 0, "Bunny rabits".length());
text.addAttribute(TextAttribute.FONT, new Font("Arial", Font.BOLD & Font.ITALIC, 32), 17, 17 + "flying ponies".length());
text.addAttribute(TextAttribute.FOREGROUND, Color.BLUE, 17, 17 + "flying ponies".length());
FontMetrics fm = g.getFontMetrics();
System.out.println(fm.getStringBounds(text.getIterator(), 0, text.getIterator().getEndIndex(), g));
g.drawString(text.getIterator(), 50, 50);
//
g.dispose();
}
}
public class MyJFrame extends JFrame {
public static void main(String[] args) {
MyJFrame frame = new MyJFrame();
frame.setContentPane(new MyJPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
FontMetrics fontMetrics = graphics.getFontMetrics()
returns a FontMetrics
object based on the single font currently set on the graphics
object. You are not changing the font used by graphics
explicitly so it uses the default font designated for JPanel
by the current L&F.
FontMetrics
methods related to bounds calculation accept a "simple" CharacterIterator
(which does not provide font information) instead of AttributedCharacterIterator
(which does). Hence fontMetrics.getStringBounds()
simply calculates the text bounds based on the single font of the same size.
You need to use java.awt.font.TextLayout
to determine the proper bounds when using AttributedCharacterIterator
with different fonts and font sizes:
TextLayout textLayout = new TextLayout(
text.getIterator(),
g.getFontRenderContext()
);
Rectangle2D.Float textBounds = ( Rectangle2D.Float ) textLayout.getBounds();
g.drawString( text.getIterator(), 50, 50 );
// lets draw a bounding rect exactly around our text
// to be sure we calculated it properly
g.draw( new Rectangle2D.Float(
50 + textBounds.x, 50 + textBounds.y,
textBounds.width, textBounds.height
) );
The FontMetrics
only receives a CharacterIterator
, and does not take into account that it is actually an AttributedCharacterIterator
. You can use a TextMeasurer
to compute the actual bounds of your string. For comparison, add this after you called the drawString
method:
// Compensate for the 50,50 of the drawString position
g.translate(50, 50);
g.setColor(Color.RED);
Rectangle2D wrongBounds = fm.getStringBounds(
text.getIterator(), 0, text.getIterator().getEndIndex(), g);
g.draw(wrongBounds);
System.out.println("wrong: "+wrongBounds);
g.setColor(Color.BLUE);
AttributedCharacterIterator iterator = text.getIterator();
TextMeasurer tm = new TextMeasurer(iterator, g.getFontRenderContext());
Rectangle2D rightBounds = tm.getLayout(0, iterator.getEndIndex()).getBounds();
g.draw(rightBounds);
System.out.println("right: "+rightBounds);
(And BTW: Don't call g.dispose()
on the Graphics
that was handed to you in the paintComponent
method)
来源:https://stackoverflow.com/questions/23975076/fontmetrics-getstringbounds-for-attributedstring-gives-wrong-result