Java:FontMetrics 上升不正确?
Posted
技术标签:
【中文标题】Java:FontMetrics 上升不正确?【英文标题】:Java: FontMetrics ascent incorrect? 【发布时间】:2011-06-01 13:48:12 【问题描述】:当我查看 FontMetric.getAscent()
的 javadoc 时,我看到了:
字体上升是从字体基线到大多数字母数字字符顶部的距离。字体中的某些字符可能会超出字体上升线。
但我写了一个快速演示程序,我看到了:
其中每行文本的 4 条水平线是:
基线位置降低了getDescent()
基线位置
getAscent()
提出的基线位置
getHeight()
提出的基线位置
注意 getAscent() 行和字符顶部之间的空间。我查看了大多数字体和大小,总是存在这种差距。 (而字体下降看起来恰到好处。)什么给出了?
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);
【问题讨论】:
使用TextLayout
可能会更好。
@Andrew -- 需要详细说明吗?
【参考方案1】:
一个可能的原因是这个值考虑了diacritics的字母。
例如,添加变音符号 ÄÖÜ 表明他们的颤音更接近上升(尽管他们还没有完全达到)。
寻找更一般的上升定义我发现the definition in Wikipedia:
[..] ascent 跨越基线和离基线最远的字形顶部之间的距离。上升和下降可能包括也可能不包括由重音或变音符号添加的距离。
所以似乎即使在排版中也没有精确、绝对的定义。
【讨论】:
我猜是这样...但是为什么应该考虑变音符号?getAscent()
说它应该在 大多数 字母数字字符的顶部。如果你想要最大的上升,有getMaxAscent()
。
@Jason:我主要是在这里猜测。但由于变音符号很多并且几乎可以应用于所有字母,人们可能会争辩说,大多数字母数字字符实际上确实有变音符号。这只是半开玩笑。
这不是最令人满意的答案,但我会接受,因为它有一定的意义。
我也遇到过这个问题,在尝试将文本居中放置在图像 (baseline = imageHeight/2 - fontHeight/2 + fontAscent;
) 上时,它似乎总是被画得太低了。但事实上,经过一些测试,发现像 "Èj" 这样的字符串是正确居中的。【参考方案2】:
我遇到了同样的问题,似乎可以通过使用 GlyphVector 类来获得字符的真实上限。
package graphics;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class FontMetricsTest2
public static void main(String[] args) throws IOException
//Draw the text to measure it with a drawing program
BufferedImage img = new BufferedImage(
500, 300, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
Font font = new Font(Font.SERIF, Font.PLAIN, 150);
graphics.setFont(font);
String text = "ABCxyz";
graphics.drawString(text, 20, 180);
ImageIO.write(img, "PNG", new File("G:\\someDir\\fontMetrics2.png"));
//Failed attempts to determine ascent with FontMetrics
FontMetrics fm = graphics.getFontMetrics();
System.out.println("FM Ascent=" + fm.getAscent() +
", FM descent=" + fm.getDescent());
//returned ascent is too high
System.out.println("FM string bounds: " +
fm.getStringBounds(text, graphics));
//too high as well
//The succesful way with glyph vector
GlyphVector gv = font.layoutGlyphVector(
graphics.getFontRenderContext(), text.toCharArray(),
0, text.length(), Font.LAYOUT_LEFT_TO_RIGHT);
Rectangle pixBounds = gv.getPixelBounds(
graphics.getFontRenderContext(), 0, 0);
System.out.println("GlyphVector - pixelBounds: " + pixBounds);
Rectangle2D visBounds = gv.getVisualBounds();
System.out.println("GlyphVector - visualBounds: " + visBounds);
由出现在字符串中的字符上升返回的矩形中的y值表示“文本”变量。
像素边界和视觉边界的主要区别在于,pixelBounds 是整数,visualBounds 是浮点数。否则它们看起来几乎相等。
【讨论】:
【参考方案3】:TrueType Reference Manual 表示字体的上升存储在“hhea”表中。 hhea 的文档指出,“上升、下降和 lineGap 的值代表了字体创建者的设计意图,而不是任何计算值。” OpenType specification 是 TrueType 规范的扩展。它还将上升器存储在 hhea 表中,并引用上升的 TrueType 定义。最重要的是,上升属性是一个指南,而不是绝对的。 GlyphLayoutVector 是获取文本边界的最准确方法。
【讨论】:
以上是关于Java:FontMetrics 上升不正确?的主要内容,如果未能解决你的问题,请参考以下文章