Java error: FontMetrics wrong?

When I look at javadoc for FontMetric.getAscent() , I see:

Font elevation is the distance from the baseline of the font to the top of most alphanumeric characters. Some characters in a font may extend above the font's ascent line.

But I wrote a small demo program, and I see the following: enter image description here

where 4 horizontal lines for each line of text:

  • base position downgraded to getDescent()
  • base position
  • base position raised by getAscent()
  • base position raised by getHeight()

Notice the space between the getAscent () line and the top of the characters. I looked at most fonts and sizes, and there was always this gap. (While the descent of the font looks 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); } } 
+14
java
Jun 01 2018-11-11T00:
source share
3 answers

One of the possible reasons is that this value takes into account letters from diacritics .

For example, the addition of Γ„Γ–Γœ umlauts shows that their trams are much closer to climbing (although they still do not achieve this).

In search of a more general definition of climbing, I find a definition on Wikipedia :

[..] climbing covers the distance between the baseline and the top of the glyph, which reaches the farthest from the baseline. Ascent and descent may or may not include distance added by accents or diacritics.

So it seems that even inside the printing house there is no exact, absolute definition.

+10
Jun 01 2018-11-11T00:
source share

I ran into the same problem and it seems that the true upper border of the character can be obtained using the GlyphVector class.

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

The y value in the rectangles returned when ascending the characters that appear in the string representing the variable text.

The main difference between pixel borders and visual borders is that pixelBounds are integers and visualBounds is a float. Otherwise, they seem almost equal.

+5
Dec 27
source share

The TrueType Reference Guide says that font ascension is stored in the "hhea" table. Documentation for hhea states: "Values ​​for climbing, lowering, and lineGap are the intentions of the font creator's design, not any calculated value." The OpenType specification is an extension of the TrueType specification. It also stores the clip in the hhea table and refers to the TrueType ascent height definition. The bottom line, the ascension property, is guidance, not absolute. GlyphLayoutVector is the most accurate way to get text borders.

+2
Jan 17 '13 at 20:15
source share



All Articles