When I look at the javadoc for FontMetric.getAscent()
I see:
The font ascent is the distance from the font's baseline to the top of most alphanumeric characters. Some characters in the Font might extend above the font ascent line.
But I wrote a quick demo program and I see this:
where the 4 horizontal lines for each row of text are:
- baseline position lowered by
getDescent()
- baseline position
- baseline position raised by
getAscent()
- baseline position raised by
getHeight()
Notice the space between the getAscent() line and the top of the characters. I've looked at most of the fonts and sizes, and there's always this gap. (Whereas the font descent looks just right.) What gives?
package com.example.fonts;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextPane;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class FontMetricsExample extends JFrame
{
static final int marg = 10;
public FontMetricsExample()
{
super(FontMetricsExample.class.getSimpleName());
JPanel panel = new JPanel(new BorderLayout());
JPanel fontPanel = new JPanel(new BorderLayout());
final JTextPane textSource = new JTextPane();
textSource.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"
+"abcdefghijklmnopqrstuvwxyz\n"
+"0123456789!@#$%^&*()[]{}");
final SpinnerNumberModel fontSizeModel =
new SpinnerNumberModel(18, 4, 32, 1);
final String fonts[] =
GraphicsEnvironment.getLocalGraphicsEnvironment()
.getAvailableFontFamilyNames();
final JComboBox fontFamilyBox = new JComboBox(fonts);
fontFamilyBox.setSelectedItem("Arial");
final JPanel text = new JPanel() {
@Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
String fontFamilyName =
fonts[fontFamilyBox.getSelectedIndex()];
int fontSize = fontSizeModel.getNumber().intValue();
Font f = new Font(fontFamilyName, 0, fontSize);
g.setFont(f);
FontMetrics fm = g.getFontMetrics();
int lineHeight = fm.getHeight();
String[] s0 = textSource.getText().split("\n");
int x0 = marg;
int y0 = getHeight()-marg-(marg+lineHeight)*s0.length;
for (int i = 0; i < s0.length; ++i)
{
y0 += marg+lineHeight;
String s = s0[i];
g.drawString(s, x0, y0);
int w = fm.stringWidth(s);
for (int yofs : Arrays.asList(
0, // baseline
-fm.getHeight(),
-fm.getAscent(),
fm.getDescent()))
{
g.drawLine(x0,y0+yofs,x0+w,y0+yofs);
}
}
}
};
final JSpinner fontSizeSpinner = new JSpinner(fontSizeModel);
fontSizeSpinner.getModel().addChangeListener(
new ChangeListener() {
@Override public void stateChanged(ChangeEvent e) {
text.repaint();
}
});
text.setMinimumSize(new Dimension(200,100));
text.setPreferredSize(new Dimension(400,150));
ActionListener repainter = new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
text.repaint();
}
};
textSource.getDocument().addDocumentListener(new DocumentListener() {
@Override public void changedUpdate(DocumentEvent e) {
text.repaint();
}
@Override public void insertUpdate(DocumentEvent e) {}
@Override public void removeUpdate(DocumentEvent e) {}
});
fontFamilyBox.addActionListener(repainter);
fontPanel.add(fontFamilyBox, BorderLayout.CENTER);
fontPanel.add(fontSizeSpinner, BorderLayout.EAST);
fontPanel.add(textSource, BorderLayout.SOUTH);
panel.add(fontPanel, BorderLayout.NORTH);
panel.add(text, BorderLayout.CENTER);
setContentPane(panel);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new FontMetricsExample().setVisible(true);
}
}