JDK-4262130 : JLabel having last few pixels cut off when italic font used
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.2.2
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_nt
  • CPU: x86
  • Submitted: 1999-08-12
  • Updated: 2001-09-11
  • Resolved: 2001-09-11
Related Reports
Duplicate :  
Relates :  
Relates :  
Description

Name: krT82822			Date: 08/12/99


8/12/99 kevin.ryan@eng -- definitely looks like a problem.  Previous, similar reports closed (e.g., for lack of a pack()).  Filing new bug.

This seems similar to the problem with menubar items mentioned in bugs 4117502 and 4046147, except that this still happens and it is dealing with JLabels.
If I create a JLabel, and use setFont() to set its font to one that has Font.ITALIC set, it seems that the JLabel doesn't properly size its width to handle the extra space needed by the italicized font.  This is most noticeable with a letter such as an uppercase N in a serif font, and it happens both on the display screen and in printing (it is worse with printing, but noticeable on the screen).
Below is a short program (thanks to Gordon Tranter at KL Group for the code sample) that will reproduce the problem, and show the effect.  It creates two labels, both saying "Hello WorldN" in italics.  The second label adds a space at the end, which causes the JLabel to create a little more space.  When run (at least on WinNT4.0), you can plainly see the top right part of the N gets a few pixels lobbed off.
The code:
import java.awt.*;
import javax.swing.*;

public class JLabelFontClip extends JFrame
{
JLabel badLabel, goodLabel;
JPanel panel;
JButton button;

public JLabelFontClip() {

   badLabel = new JLabel("Hello WorldN");
   badLabel.setFont(new Font("Serif",Font.BOLD + Font.ITALIC, 24));
   badLabel.setHorizontalAlignment(JLabel.LEFT);

   goodLabel = new JLabel("Hello WorldN ");
   goodLabel.setFont(new Font("Serif",Font.BOLD + Font.ITALIC, 24));
   goodLabel.setHorizontalAlignment(JLabel.LEFT);

   getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
   getContentPane().add(badLabel);
   getContentPane().add(goodLabel);
   
   pack();
   setVisible(true);
}

public static void main(java.lang.String[] args) {
   new JLabelFontClip();
}
}

Info on my system (where I can reproduce it any time):
------------------------------------------------------
Windows NT 4.0, Service Pack 4
JDK 1.2.2
java -version returns:
java version "1.2.2"
HotSpot VM (1.0fcs, mixed mode, build E)
java -fullversion returns:
java full version "JDK-1.2.2-W"
(Review ID: 93845) 
======================================================================

Comments
WORK AROUND Name: krT82822 Date: 08/12/99 Pad the end of the JLabel with a space or two to make it increase its size enough to not clip the final pixels of the text. ======================================================================
11-06-2004

EVALUATION I checked the label code for miscalculations and don't see any. I believe this is a font metrics problem. Changing to 2d. jeff.dinkins@Eng 1999-09-19 Changing back to swing. Jim Graham says that we need to use a string bounding box (technical description problem inaccurate, on my part) instead of stringwidth. jeff.dinkins@Eng 1999-09-20 Name: keR10081 Date: 07/27/2000 This bug is due to incorrect value returned by getFontMetrics() for italic fonts. In fact, it returns the same width for string written in italic font and non-italic font, although italic requires bigger space. Probably should be recategorized to java2d. See the test case. import java.awt.*; import javax.swing.*; public class bug4262130 extends JFrame { JLabel badLabel, goodLabel; JPanel panel; JButton button; public bug4262130() { badLabel = new JLabel("I"); badLabel.setFont(new Font("Serif",Font.BOLD + Font.ITALIC, 72)); badLabel.setHorizontalAlignment(JLabel.LEFT); badLabel.setOpaque(true); badLabel.setBackground(Color.white); goodLabel = new JLabel("I"); goodLabel.setFont(new Font("Serif",Font.BOLD, 72)); goodLabel.setHorizontalAlignment(JLabel.LEFT); goodLabel.setOpaque(true); goodLabel.setBackground(Color.white); getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); getContentPane().add(badLabel); getContentPane().add(goodLabel); pack(); System.out.println(badLabel.getGraphics().getFontMetrics().stringWidth("I")); setVisible(true); } public static void main(java.lang.String[] args) { new bug4262130(); } } ====================================================================== doug.felt@eng 2000-07-31 No, getFontMetrics is returning the right thing. The docs aren't so clear about this. The 'advance' is the distance along the baseline for the string, from where you start drawing the string, to where you will start the next string. It is not a visual bounds of the string. Portions of characters can and will render to the left of the start of the string, and to the right of the advance of the string, particularly if the font is italic. The advance is a 'logical' metric and not a 'visual' one (similarly, ascent and descent are logical metrics, and not guaranteed to bound the visual representation of the string). Logical metrics are used to position strings in relation to other strings. In order to get visual bounds, you need to construct a TextLayout from the string and call getBounds() on it. This will return the bounds of the outlines of the characters. Owing to rasterization and hinting issues there will occasionally be cases where a pixel falls outside of this bounds. But this will give you a general visual bounding box that you can use to set clipping and margins. You will not want to use the visual metrics of the bounding box alone to set the visual position of the string. This is because the visual bounds will be tight, so for example if there are no descenders in the text, the bottom of the visual bounds will be zero, and if there are only short letters, the top of the visual bounds will be below the ascent. You should probably use the union of the bounds returned from TextLayout, and the bounds as computed from the ascent, descent, leading (maybe), and advance. In cases where the text has lots of accents above or below the ascent line, the 'leading' space above (and below) the text is useful. See the following (untested) code for an example: String myText = ...; Font myFont = ...; Graphics g == ...; Graphics2D g2d = (Graphics2D)g; FontRenderContext frc = g2d.getFontRenderContext(); TextLayout layout = new TextLayout(myText, myFont, frc); Rectangle2D vBounds = layout.getBounds(); Rectangle2D lBounds = new Rectangle2D.Float(0f, -layout.getAscent() - layout.getLeading(), layout.getAdvance(), layout.getAscent() + layout.getDescent() + 2f * layout.getLeading()); Rectangle2D bounds = lBounds.createUnion(vBounds); // bounds is your bounding box, with 0,0 at the point where you will draw the // text (the start of the string). It has been padded on all sides to account // for the visual bounds of the text. Hopefully, the leading will absorb any // slop from unusual accents, so that all strings will align similarly along // the y axis, but if not the visual bounds will ensure that all the // string fits. // later, assuming the graphics origin is at the top left of the bounds, you // can render the layout like this layout.draw(g2d, (float)-bounds.getMinX(), (float)-bounds.getMinY()); I am returning this bug back to swing. scott.violet@eng 2001-02-09 Integrating this change appears to cause the SwingMark test TableRowTest to be 50% slower on Solaris (with 1.4beta build51) and ~60% slower on NT. Trying to see if there is a better solution for this. scott.violet@eng 2001-02-12 From Doug: ---- Currently, TextLayout is not very efficient. It is the only API that give the right results for all cases, though. Mainly I just wanted to explain the problem in the bug report in a succinct way. One answer is to speed up TextLayout for Latin-1 text. Another is to provide a new API to return the visual bounds or visual + logical bounds of a string. Those are both my problem. But you might also not need an exact answer in some cases, for example in clipping it probably doesn't hurt that much to draw a few more pixels in extent than necessary. That allows for more options, and you might be able to implement something simple for swing that obviates the need to use TextLayout. You might consider either one of two approaches. One is to replicate the logic in Font.getStringBounds to only use TextLayout for scripts that require layout, or for fonts that have styles that can cause the visual bounds to extend past the logical bounds (such as italic). But instead of computing the logical bounds, compute the visual bounds. Another approach is to just special case the italic font problem (which I suspect is 90% of your problem) by using Font.getItalicAngle and multiplying it by the ascent to 'extend' the logical bounds to the right, and similarly by the negative descent to 'extend' it to the left, for Latin-1 text only. You'd also have to watch out for troublesome scripts/fonts that draw above the ascent or below the descent. You can try using Font.getMaxCharBounds() to push out the ascent and descent. Fudging the logical bounds using general font metrics like this ought to cover most all of the text that you encounter, but it won't be as exact. I'm not sure that you might not still drop a few pixels, but it's more likely that you'll generate bounds that are too big. (BTW, I expect that swingmark doesn't bother with text outside latin-1, otherwise for some scripts (like Arabic) you'd already be seeing the performance hit since getStringBounds will construct a TextLayout if needed.) Also, you don't necessarily have to call into graphics to get a FontRenderContext, that's just the preferred way if your graphics might be scaled. If clients can't get to the graphics (or aren't expected to) then you can just construct a FontRenderContext directly (and cache it) using a null affine transform and the values for antialiasing and fractionalmetrics that you expect. That would obviate the need to get a Graphics, and the associated overhead. Again, I apologize for the overhead of TextLayout. It was designed for hit testing text, and for supporting troublesome scripts, and initializes as much of its state as it can all at once, and in order to be consistent, does so in a general way. Optimizing its performance to get overall line metrics for Latin-1 hasn't been a priority. Now that there is starting to be more use of it in swing I probably can't keep ignoring this case. ---- As such, Swing will hold off on changing the text layout code until the necessary speedups are in place. The bug to tract speeding up TextLayout is: 4414577 ###@###.### 2001-09-11 Closing this as a duplicate of 4140220 which was filed quite a while ago.
11-09-2001