JDK-4374406 : Mnemonics underscore fails to display correctly under joined Arabic characters
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.3.0,1.4.0
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic,windows_2000
  • CPU: x86
  • Submitted: 2000-09-27
  • Updated: 2005-02-12
  • Resolved: 2005-02-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6
6 b24Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Description
Name: jbT81659			Date: 09/27/2000

OS: Win98-Arabic/Hebrew
JDK:1.4.0-Beta32

This bug applies to other Swing components like JButton, JCheckBox....
In any Arabic word that has joined characters, the Mnemonics underscore 
extends under the last two joined characters instead of the correct character 
for Mnemonics.

To reproduce bug:
1- Compile and run the following code
2- Observe the Mnemonics underscore under joined Arabic chracters.
3- Verify that two characters are affected by this behavior

--------Code-----------

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

public class mnemonics extends JApplet 
{
    public void init()
    {
        JFrame frame = new JFrame("\u0645\u062b\u0627\u0644");
        frame.setJMenuBar(new jMenuX());
        frame.setSize(500, 400);
        frame.setVisible(true);
    }
    public static void main(String[] argv)
    {
        jMenuX example = new jMenuX();
        example.pane = new JTextPane();
        example.pane.setPreferredSize(new Dimension(250, 300));
        example.pane.setBorder(new BevelBorder(BevelBorder.LOWERED));
        JFrame frame = new JFrame("\u0645\u062b\u0627\u0644");
        frame.getContentPane().add(example, BorderLayout.NORTH);
        frame.getContentPane().add(example.pane, BorderLayout.SOUTH);
        frame.setFont(new Font("Lucida Sans Regular", Font.PLAIN,8));
        frame.setSize(500, 400);
        frame.setVisible(true);
        frame.addWindowListener( new WindowAdapter()
        {
           public void windowClosing( WindowEvent e)
           {
              System.exit(0);
           }
        });
     }
}
class jMenuX extends JMenuBar implements ActionListener 
{
      public JTextPane pane;

   String[] fileItemsEnglish = new String[] 
   {    
       "\u0041\u0042\u0043\u0044", 
       "\u0061\u0062\u0063\u0064", 
       "\u0065\u0066\u0067\u0068", 
       "\u0030\u0031\u0032\u0033" 
   };


   String[] fileItemsArabic = new String[] 
   {    
       "\u062c\u062f\u064a\u062f", 
       "\u0641\u062a\u062d", 
       "\u062d\u0641\u0638", 
       "\u062e\u0631\u0648\u062c" 
   };


   String[] fileItemsHebrew = new String[] 
   {    
       "\u05d7\u05d3\u05e9", 
       "\u05e4\u05ea\u05d9\u05d7\u05d4", 
       "\u05e9\u05de\u05d9\u05e8\u05d4", 
       "\u05d9\u05e6\u05d9\u05d0\u05d4" 
   };

   String[] fileItemsBidi = new String[] 
   {    
       "\u062c\u062f\u064a\u062f\u0020\u05d7\u05d3\u05e9", 
       "\u0641\u062a\u062d\u0020\u05e4\u05ea\u05d9\u05d7\u05d4", 
       "\u062d\u0641\u0638\u0020\u05e9\u05de\u05d9\u05e8\u05d4", 
       "\u05d9\u05e6\u05d9\u05d0\u05d4\u0020\u062e\u0631\u0648\u062c" 
   };

   char[] fileShortcutsEnglish = { '\u0041','\u0061','\u0065','\u0030' };
   char[] fileShortcutsArabic = { '\u062c','\u0641','\u062d','\u062e' };
   char[] fileShortcutsHebrew = { '\u05d7','\u05e4','\u05e9','\u05d9' };
   char[] fileShortcutsBidi   =   { '\u062c','\u0641','\u05de','\u05d9' };

   public jMenuX() 
   {

      JMenu fileMenu = new JMenu("English");
      JMenu Menu2 = new JMenu("Menu2 \u0639\u0631\u0628\u064a");
      JMenu Menu3 = new JMenu("Menu3 \u05e2\u05d1\u05e8\u05d9\u05ea");
      JMenu Menu4 = new JMenu("Menu4 Bidi\u05e2\u05d1\u05e8\u05d9\u05ea\u0020\u0639\u0631\u0628\u064a");
      fileMenu.setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
      Menu2.setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
      Menu3.setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
      Menu4.setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
      fileMenu.setMnemonic('G');
      Menu2.setMnemonic('\u0639');
      Menu3.setMnemonic('\u05e2');
      Menu4.setMnemonic('\u05d1');


      for (int i=0; i < fileItemsEnglish.length; i++) 
      {
          JMenuItem item = new JMenuItem(fileItemsEnglish[i]);
          item.setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
          item.setMnemonic(fileShortcutsEnglish[i]);
          item.addActionListener(this);
          fileMenu.add(item);
      }

      for (int i=0; i < fileItemsArabic.length; i++) 
      {
          JMenuItem item = new JMenuItem(fileItemsArabic[i]);
          item.setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
          item.setMnemonic(fileShortcutsArabic[i]);
          item.addActionListener(this);
          Menu2.add(item);
      }


      for (int i=0; i < fileItemsHebrew.length; i++) 
      {
          JMenuItem item = new JMenuItem(fileItemsHebrew[i]);
          item.setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
          item.setMnemonic(fileShortcutsHebrew[i]);
          item.addActionListener(this);
          Menu3.add(item);
      }
      for (int i=0; i < fileItemsBidi.length; i++) 
      {
          JMenuItem item = new JMenuItem(fileItemsBidi[i]);
          item.setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
          item.setMnemonic(fileShortcutsBidi[i]);
          item.addActionListener(this);
          Menu4.add(item);
      }

this.setPreferredSize( new Dimension(400, 40));
      add(fileMenu);
      add(Menu2);
      add(Menu3);
      add(Menu4);
     }

    public void actionPerformed(ActionEvent e)
    {
        try
        {
           setFont(new Font("Lucida Sans Regular",Font.PLAIN,14));
           pane.getStyledDocument().insertString(0 ,"Action ["+e.getActionCommand()+"] performed!\n", null);
        }
        catch (Exception ex)
                {;}

     }
}


WorkAround:
======================================================================
###@###.### 10/22/04 01:51 GMT

Comments
EVALUATION the fix for this bug introduced a regression: 6501991 [java.awt.font.LineBreakMeasurer.nextOffset ArrayIndexOutOfBoundsException]
11-07-2007

SUGGESTED FIX ------- SwingUtilities2.java ------- *** /tmp/sccs.Vv2yda Mon Jan 24 21:59:09 2005 --- SwingUtilities2.java Thu Jan 13 17:55:20 2005 *************** *** 14,20 **** --- 14,22 ---- import java.awt.font.*; import java.awt.geom.*; import java.awt.print.PrinterGraphics; + import java.text.Bidi; import java.text.AttributedCharacterIterator; + import java.text.AttributedString; import javax.swing.*; import javax.swing.plaf.*; import javax.swing.text.Highlighter; *************** *** 107,112 **** --- 109,119 ---- private static final String UntrustedClipboardAccess = "UNTRUSTED_CLIPBOARD_ACCESS_KEY"; + //all access to charsBuffer is to be synchronized on charsBufferLock + private static final int CHAR_BUFFER_SIZE = 100; + private static final Object charsBufferLock = new Object(); + private static char[] charsBuffer = new char[CHAR_BUFFER_SIZE]; + static { fontCache = new LSBCacheEntry[CACHE_SIZE]; Object aa = java.security.AccessController.doPrivileged( *************** *** 116,121 **** --- 123,185 ---- AA_FRC = new FontRenderContext(null, true, false); } + + + /** + * Indic, Thai, and surrogate char values require complex + * text layout and cursor support. + * + * @param ch character to be tested + * @return <tt>true</tt> if TextLayout is required + */ + private static final boolean isComplexLayout(char ch) { + return (ch >= '\u0900' && ch <= '\u0D7F') || // Indic + (ch >= '\u0E00' && ch <= '\u0E7F') || // Thai + (ch >= '\uD800' && ch <= '\uDFFF'); // surrogate value range + } + + /** + * Fast method to check that TextLayout is not required for the + * character. + * if returns <tt>true</tt> TextLayout is not required + * if returns <tt>false</tt> TextLayout might be required + * + * @param ch character to be tested + * @return <tt>true</tt> if TextLayout is not required + */ + private static final boolean isSimpleLayout(char ch) { + return ch < 0x590 || (0x2E00 <= ch && ch < 0xD800); + } + + + /** + * checks whether TextLayout is required to handle characters. + * + * @param text characters to be tested + * @param start start + * @param limit limit + * @return <tt>true</tt> if TextLayout is required + * <tt>false</tt> if TextLayout is not required + */ + public static final boolean isComplexLayout(char[] text, int start, int limit) { + boolean simpleLayout = true; + char ch; + for (int i = start; i < limit; ++i) { + ch = text[i]; + if (isComplexLayout(ch)) { + return true; + } + if (simpleLayout) { + simpleLayout = isSimpleLayout(ch); + } + } + if (simpleLayout) { + return false; + } else { + return Bidi.requiresBidi(text, start, limit); + } + } + // // WARNING WARNING WARNING WARNING WARNING WARNING // Many of the following methods are invoked from older API. *************** *** 326,343 **** String string, int availTextWidth) { // c may be null here. String clipString = "..."; ! int width = SwingUtilities2.stringWidth(c, fm, clipString); ! // NOTE: This does NOT work for surrogate pairs and other fun ! // stuff ! int nChars = 0; ! for(int max = string.length(); nChars < max; nChars++) { ! width += fm.charWidth(string.charAt(nChars)); ! if (width > availTextWidth) { ! break; } } ! string = string.substring(0, nChars) + clipString; ! return string; } /** --- 390,428 ---- String string, int availTextWidth) { // c may be null here. String clipString = "..."; ! int stringLength = string.length(); ! availTextWidth -= SwingUtilities2.stringWidth(c, fm, clipString); ! boolean needsTextLayout = false; ! ! synchronized (charsBufferLock) { ! if (charsBuffer == null || charsBuffer.length < stringLength) { ! charsBuffer = string.toCharArray(); ! } else { ! string.getChars(0, stringLength, charsBuffer, 0); ! } ! needsTextLayout = ! isComplexLayout(charsBuffer, 0, stringLength); ! if (!needsTextLayout) { ! int width = 0; ! for (int nChars = 0; nChars < stringLength; nChars++) { ! width += fm.charWidth(charsBuffer[nChars]); ! if (width > availTextWidth) { ! string = string.substring(0, nChars); ! break; ! } ! } } } ! if (needsTextLayout) { ! FontRenderContext frc = getFRC(c, fm); ! AttributedString aString = new AttributedString(string); ! LineBreakMeasurer measurer = ! new LineBreakMeasurer(aString.getIterator(), frc); ! int nChars = measurer.nextOffset(availTextWidth); ! string = string.substring(0, nChars); ! ! } ! return string + clipString; } /** *************** *** 414,429 **** */ public static void drawStringUnderlineCharAt(JComponent c,Graphics g, String text, int underlinedIndex, int x,int y) { SwingUtilities2.drawString(c, g, text, x, y); ! if (underlinedIndex >= 0 && underlinedIndex < text.length() ) { ! // PENDING: this needs to change. ! FontMetrics fm = g.getFontMetrics(); ! int underlineRectX = x + SwingUtilities2.stringWidth(c, ! fm, text.substring(0,underlinedIndex)); int underlineRectY = y; - int underlineRectWidth = fm.charWidth(text. - charAt(underlinedIndex)); int underlineRectHeight = 1; g.fillRect(underlineRectX, underlineRectY + 1, underlineRectWidth, underlineRectHeight); } --- 499,553 ---- */ public static void drawStringUnderlineCharAt(JComponent c,Graphics g, String text, int underlinedIndex, int x,int y) { + if (text == null || text.length() <= 0) { + return; + } SwingUtilities2.drawString(c, g, text, x, y); ! int textLength = text.length(); ! if (underlinedIndex >= 0 && underlinedIndex < textLength ) { int underlineRectY = y; int underlineRectHeight = 1; + int underlineRectX = 0; + int underlineRectWidth = 0; + boolean isPrinting = isPrinting(g); + boolean needsTextLayout = isPrinting; + if (!needsTextLayout) { + synchronized (charsBufferLock) { + if (charsBuffer == null || charsBuffer.length < textLength) { + charsBuffer = text.toCharArray(); + } else { + text.getChars(0, textLength, charsBuffer, 0); + } + needsTextLayout = + isComplexLayout(charsBuffer, 0, textLength); + } + } + if (!needsTextLayout) { + FontMetrics fm = g.getFontMetrics(); + underlineRectX = x + + SwingUtilities2.stringWidth(c,fm, + text.substring(0,underlinedIndex)); + underlineRectWidth = fm.charWidth(text. + charAt(underlinedIndex)); + } else { + Graphics2D g2d = getGraphics2D(g); + if (g2d != null) { + FontRenderContext frc = (isPrinting) ? DEFAULT_FRC : + g2d.getFontRenderContext(); + TextLayout layout = + new TextLayout(text, g2d.getFont(), + frc); + TextHitInfo leading = + TextHitInfo.leading(underlinedIndex); + TextHitInfo trailing = + TextHitInfo.trailing(underlinedIndex); + Shape shape = + layout.getVisualHighlightShape(leading, trailing); + Rectangle rect = shape.getBounds(); + underlineRectX = x + rect.x; + underlineRectWidth = rect.width; + } + } g.fillRect(underlineRectX, underlineRectY + 1, underlineRectWidth, underlineRectHeight); } ###@###.### 2005-1-25 03:00:14 GMT
25-01-2005

EVALUATION This is not a bidi bug but a text rendering problem. English character w/ underline seems to have same problems. ###@###.### 2001-12-07 The english mnemonic looks fine. The arabic and arabic/hebrew ones look incorrect. ###@###.### 2001-12-07 ================ Swing did not pay attention to the cases where TextLayout is needed for measuring text. There are two such cases: 1. finding where to clip text for given span 2. painting underline under character with given index New method was added to com/sun/java/swing/SwingUtilities2.java /** * checks whether TextLayout is required to handle characters. * * @param text characters to be tested * @param start start * @param limit limit * @return <tt>true</tt> if TextLayout is required * <tt>false</tt> if TextLayout is not required */ public static final boolean isComplexLayout(char[] text, int start, int limit) { ---- public static String clipString(JComponent c, FontMetrics fm, String string, int availTextWidth) { and public static void drawStringUnderlineCharAt(JComponent c,Graphics g, String text, int underlinedIndex, int x,int y) { updated to use TextLayout is needed ###@###.### 2005-1-25 03:00:14 GMT
25-01-2005