Java: восхождение FontMetrics неверно?
Когда я смотрю на Javadoc для FontMetric.getAscent()
Я вижу:
Подъем шрифта - это расстояние от базовой линии шрифта до вершины большинства буквенно-цифровых символов. Некоторые символы в шрифте могут выходить за верхнюю линию шрифта.
Но я написал небольшую демонстрационную программу и вижу это:
где 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);
}
}
3 ответа
Одной из возможных причин является то, что это значение учитывает буквы с диакритическими знаками.
Например, добавление умлаутов ÄÖÜ показывает, что их трема гораздо ближе к восхождению (хотя они до сих пор не достигают его).
В поисках более общего определения восхождения я нахожу определение в Википедии:
[..] восхождение охватывает расстояние между базовой линией и вершиной глифа, которое достигает самого дальнего от базовой линии. Подъем и спуск могут включать или не включать расстояние, добавленное акцентами или диакритическими знаками.
Таким образом, кажется, что даже в типографии нет точного, абсолютного определения.
Я столкнулся с той же проблемой, и оказалось, что истинную верхнюю границу символа можно получить с помощью класса 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 являются числами с плавающей точкой. В противном случае они кажутся почти равными.
Справочное руководство по TrueType гласит, что восхождение шрифта хранится в таблице "hhea". Документация для hhea гласит: "Значения ascent, descent и lineGap представляют намерения создателя шрифта, а не любое вычисленное значение". Спецификация OpenType является расширением спецификации TrueType. Он также сохраняет восходящий элемент в таблице hhea и ссылается на определение восхождения TrueType. В итоге, свойство всплытия является руководством, а не абсолютом. GlyphLayoutVector - самый точный способ получить границы текста.