United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6464003 Text truncated in JLabel
JDK-6464003 : Text truncated in JLabel

Details
Type:
Bug
Submit Date:
2006-08-25
Status:
Closed
Updated Date:
2011-02-16
Project Name:
JDK
Resolved Date:
2011-01-27
Component:
client-libs
OS:
linux,windows_xp
Sub-Component:
javax.swing
CPU:
x86
Priority:
P4
Resolution:
Won't Fix
Affected Versions:
1.4.2,5.0,6
Fixed Versions:
7

Related Reports
Backport:
Duplicate:
Relates:
Relates:
Relates:
Relates:

Sub Tasks

Description
FULL PRODUCT VERSION :
java version "1.6.0-rc"
Java(TM) SE Runtime Environment (build 1.6.0-rc-b96)
Java HotSpot(TM) Client VM (build 1.6.0-rc-b96, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Linux io 2.6.11.4-21.11-smp #1 SMP Thu Feb 2 20:54:26 UTC 2006 i686 i686 i386 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
The text in a JLabel is truncated when HorizontalAlignment == SwingConstants.TRAILING, a PLAIN style font of the default size is used and the text ends with 'v' or 'y'.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the program attached to this report and have a look at the end of the lines. You can see that all labels are fully visible except for the lines ending with 'v' or 'y'. I have the impression that the dimensions for the 'v' and 'y' characters in the PLAIN font are wrong. If you type two 'v' or two 'y' characters in a JTextField you can see (using xmag or kmag) that the 'v' and 'y' characters share one pixel in the default font size.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The text of all labels should be fully visible.
ACTUAL -
The text of the label is truncated if it ends with 'v' or 'y'.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.*;
import javax.swing.*;

public class TruncatedLabel {

    public TruncatedLabel() {
    }

    private static void addLabels(JPanel panel) {
        JLabel label = new JLabel();
        Font font = label.getFont().deriveFont(Font.PLAIN);
        for (char c = 'a'; c <= 'z'; c++) {
            if (c == 'v' || c == 'y') {
                label = new JLabel("This label is wrong: " + c);
            } else {
                label = new JLabel("This label is correct: " + c);
            }
            label.setFont(font);
            label.setHorizontalAlignment(SwingConstants.TRAILING);
            panel.add(label);
        }
    }

    private static void createAndShowGUI() {
        JLabel label;
        JPanel panel = new JPanel(new GridLayout(26, 1));
        panel.setBorder(BorderFactory.createEmptyBorder(12, 12, 11, 11));

        addLabels(panel);

        JFrame frame = new JFrame("TruncatedLabel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Use bold font style for labels.
There is a problem for left text side too. It is caused by a bug in the SwingUtilities2.getLeftSideBearing() method, which leads to text clipping for any swing labels. See screenshots and a test case in the attachment. Now the left bearing is calculated only for the 'W' character!

Testing strategy:
   1. Run the TextClippingDemo
   2. Select a font and its size
   3. You'll see a frame with clipped strings (if there is any). Each line will begin with a clipped character and it will be followed by the same unclipped one and by the value of the left side bearing (how many pixels were clipped).
Here is the modified version of test, which shows the problem on Windows and Linux:

import java.awt.*;
import javax.swing.*;

public class TruncatedLabel {

    public TruncatedLabel() {
    }

    private static void addLabels(JPanel panel) {
        JLabel label = new JLabel();
        Font font = Font.decode("Lucida Bright Italic-12");
        for (char c = 'A'; c <= 'Z'; c++) {
            label = new JLabel("The letter is " + c);
            label.setFont(font);
            label.setHorizontalAlignment(SwingConstants.TRAILING);
            panel.add(label);
        }
    }

    private static void createAndShowGUI() {
        JLabel label;
        JPanel panel = new JPanel(new GridLayout(26, 1));
        panel.setBorder(BorderFactory.createEmptyBorder(12, 12, 11, 11));

        addLabels(panel);

        JFrame frame = new JFrame("TruncatedLabel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

                                    

Comments
EVALUATION

I made an investigation and found that there is a problem for left text side too! It is caused by a bug in the SwingUtilities2.getLeftSideBearing() method, which leads to text clipping for any swing labels. See screenshots and a test case in the attachment. Now the left bearing is calculated only for the 'W' character! 

Testing strategy:
   1. Run the TextClippingDemo
   2. Select a font and its size
   3. You'll see a frame with clipped strings (if there is any). Each line will begin with a clipped character and it will be followed by the same unclipped one and by the value of the left side bearing (how many pixels were clipped).
                                     
2006-09-05
EVALUATION

The problem of getLeftSideBearing() is in the following lines:

78  private static final int MIN_CHAR_INDEX = (int)'W';
79  private static final int MAX_CHAR_INDEX = (int)'W' + 1;

...

282 public static int getLeftSideBearing(JComponent c, FontMetrics fm,
                                         char firstChar) {
283     int charIndex = (int)firstChar;
284     if (charIndex < MAX_CHAR_INDEX && charIndex >= MIN_CHAR_INDEX) {
 
             // Calculation of the left side bearing

314     }

Now LSB is calculated only for the 'W' character! It must be calculated for every character to solve the problem.

An obvious solution is to set MIN_CHAR_INDEX to Character.MIN_VALUE and MAX_CHAR_INDEX to Character.MAX_VALUE, but it wastes the memory (for the caching of LSB values): 256K for every used font (but not more than for 6 fonts). 

Other approach is to remove the caching at all. I measured the times:
   - getLeftSideBearing() with caching, memory waste ~ 0,4 microseconds
   - getLeftSideBearing() without caching ~ 1,8 microseconds

You can find the performance test and test results in the attachment. For the testing purposes I used modified SwingUtilities2 class: it caches LSB for all characters from 'a' to 'z' (it is the only characters, used in the test).

I made an investigation and found out: typical application can call the getLeftSideBearing() method about 100 - 1000 times per second (while a window is revalidated). In some rare cases (less than 1%) it can reach 10000 (for example, when a table with hundreds of labels is revalidated many times in a row: try in SwingSet2 to drag intensively the "Row Height" slider in the "Table Demo" example). 

Conclusion: we can remove the caching at all, because in the wrost case it makes slower an application only for several mills per second.
                                     
2006-09-06
EVALUATION

Here is my correspondence with Phil Race about SwingUtilities2.getRightBearing() implementation details:

Mikhail Lapshin wrote:
> Hi Phil,
>
> I'm working on the 6464003 bug: Text truncated in JLabel. To fix it I wrote a code, which calculates the right side bearing. It works, but I found the following lines, added by you in the fix for 6304684:
>     if (lsb < 0) {
>         /* HRGB/HBGR LCD glyph images will always have a pixel
>          * on the left used in colour fringe reduction.
>          * Text rendering positions this correctly but here
>          * we are using the glyph image to adjust that position
>          * so must account for it.
>          */
>          Object aaHint = frc.getAntiAliasingHint();
>          if (aaHint == VALUE_TEXT_ANTIALIAS_LCD_HRGB ||
>              aaHint == VALUE_TEXT_ANTIALIAS_LCD_HBGR) {
>              lsb++;
>          }
>     }
>
> Should I do the same for the right side bearing?
> Do HRGB/HBGR LCD glyph images always have a pixel on the right?
> Maybe there are other glyph image types, which always have a pixel on the right?
>
> Thanks!
> --Mikhail 

Tricky question. I think you don't need to worry about it. So proceed on that assumption and if we find otherwise I'll try to figure it out.

-phil.
                                     
2006-09-12
EVALUATION

The first version of my fix calculated LSB and RSB for all characters and didn't any caching. I performed SwingMark tests on different platforms. You can find used SwingMark test kit and full results sheet in the attachment. 

Compared JDK versions:
- Dolphin b1. The LSB is calculated only for the 'W' charcter. The value is cached.
- Dolphin b1 with the first fix version applied: http://sa.sfbay.sun.com/projects/swing_data/dolphin/6464003.0
  The LSB and RSB are calculated for all characters. No caching.

Measured performance downgrade (after the fix):
Linux i586   : 1.2%
Windows XP   : 3.7%
Solaris Sparc: 5.4%, 6.9% (two machines were tested)

The results was not so good. After a discussion with Alex Potochkin and Peter Zhelezniakov we decided that bearings should be cached to decrease the downgrade.
                                     
2006-09-14
EVALUATION

I found out that SwingSet2 uses 5 JLabel fonts and NetBeans uses 7 fonts (except UI designer, where you can add to a panel a JLabel having any font).
Why is the number of used fonts in NetBeans so small while we can see many of them (for example, in the text editor or font selection dialog)? Because the most of fonts are used in text components, which are not based on JLabel and calculate bearings itself.
                                     
2006-09-21
SUGGESTED FIX

Webrevs: http://javaweb.sfbay/jcg/1.7.0-dolphin/swing/6464003/
Regression test: test/javax/swing/SwingUtilities/6464003/bug6464003.java

The last fix version has strong cache and soft cache. The strong cache holds bearings for the 10 most recently used fonts. If there are more fonts than the strong cache size, they will be held by the soft cache. Garbage collector can discard soft cache entries, but it should keep them as log as possible.
Performance decrease is less than 2% according to SwingMark test with default settings.
                                     
2006-12-27
EVALUATION

I tried several caching algorithms. The last fix version has strong cache and soft cache. The strong cache holds bearings for the 10 most recently used fonts. If there are more fonts than the strong cache size, they will be held by the soft cache. Garbage collector can discard soft cache entries, but it should keep them as log as possible.

Performance downgrade is less than 2% according to SwingMark test for all tested platforms:
Windows XP: 0.5%
Solaris Sparc: 1.4%
Linux i686: 0.2%
                                     
2007-01-15
EVALUATION

The fix for this bug caused serious regressions and was finally rolled back
with the fix for CR #6797139
                                     
2009-09-02
EVALUATION

We have had enough problems fixing this minor issue,
will not fix
                                     
2011-01-27
EVALUATION

I can reproduce the bug under JDK 1.4, 1.5 and 1.6. I think, the problem is in the drawing features of 'v' and 'y' characters in the PLAIN font. It seems, the characters are partially drawed at the first pixel of the next character and the first pixel is shared. The problem occurs only if the 'v' or the 'y' character is the last character in JLabel's text and there is no space at the right to draw the shared pixel.

Reassign the bug to AWT team.
                                     
2006-08-28
EVALUATION

The problem doesn't reproducible if we don't restrict the character (i.e. put it into a canvas). Asking 2d team to look into.
                                     
2006-08-29
EVALUATION

For 2D team: the code, calculating width and position of JLabel text, is located in SwingUtilities.layoutCompoundLabelImpl(). Maybe some font feature isn't taken in account here.
                                     
2006-08-29
EVALUATION

The font being used here (Linux) is Lucida Sans Regular - the one that
ships with the JRE. If the test case is changed to explicitly use this font
then you can see the same thing on Windows too.
Swing is using stringWidth to determine the visual bounds of the string
but stringWidth returns the advance of the string which is the position
at which to place the next character. The v and y in the Lucida font
extend to the advance width position for the next char. I have noticed
this before and it seems to be intended to avoid too much white space
between adjacent characters as only the tip of the v or y is close
to the next character position.

So this seems to be the same kind of issue as Swing has had with
the negative left side bearing. In that case a glyph can extend
to the left of the logical position of the glyph. In this case
it can extend to the right of its advance width.
If 'TRAILING' mode is in use then Swing will need to do something
similar to the LSB case - ie it may be special cased so as to
avoid overhead of API that measures the pixel bounds.
                                     
2006-08-29
EVALUATION

Phil, thanks for the detailed comments! 

I shouldn't have reassigned the bug without a careful investigation. Now I see that it is for the Swing team. I get it back.

The bug is related to the 4140220 bunch of bugs. It seems, that we fixed the problem only for the left side case and don't take in account the right side bearing for the last glyph in a string.
                                     
2006-08-29



Hardware and Software, Engineered to Work Together