Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-7157981

Arabic FATHATAN combining mark misplaced when using drawString / TextLayout

    XMLWordPrintable

Details

    • 2d
    • x86
    • windows_7

    Description

      FULL PRODUCT VERSION :
      java version "1.7.0_03"
      Java(TM) SE Runtime Environment (build 1.7.0_03-b05)
      Java HotSpot(TM) Client VM (build 22.1-b02, mixed mode, sharing)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 6.1.7601]

      A DESCRIPTION OF THE PROBLEM :
      When drawing text on a Graphics2D instance to which a TRANSFORM HAS BEEN APPLIED, the positioning of combined glyphs may be wrong.
      This is the case for the following string:
      \u062A\u0644\u0642\u0627\u0626\u064A\u0627\u064B
      where the Arabic FATHATAN character (u064B) which is combined with \u0627 is positioned with a different transform than the rest of the output string. The glyph should be just above the other glyph. Instead the position seems to be subject to the current transformation matrix and depends on e.g. the scaling and rotation of the graphics object.

      The error occurs, if the TextLayout object is constructed for a graphics instance to which a transform has already been applied.
      It does not occur, if the TextLayout object is constructed for a graphics instance which has been reset to the identity transform. I.e. if I apply the transform after the TextLayout object for the text has been created and draw the TextLayout object after applying the transform, the mark will be positioned correctly. This workaround only works for drawing with TextLayout (or using the GlyphVector) only. drawString() will always fail in that respect.

      The problem was noticed as significant difference in output when a document containing the above mentioned arabic text was printed.

      I found at least one bug referring to misplaced ligature marks in Arabic text, but this has been marked as fixed.


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Compile and execute the example below or try to draw the above mentioned string on a scaled Graphics2D canvas using the drawString method (or TextLayout).

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      TextLayout should position all glyphs correctly. Not only "most of them".

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      package examples;

      import java.awt.Font;
      import java.awt.Graphics;
      import java.awt.Graphics2D;
      import javax.swing.JPanel;
      import javax.swing.event.ChangeEvent;

      public class TextLayoutBug extends javax.swing.JFrame {
          
          /** Creates new form TextLayoutBug */
          public TextLayoutBug() {
              initComponents();
          }
          
          public double scale = 1.0;
          public double angle = 0.0;
          
          /** This method is called from within the constructor to
           * initialize the form.
           * WARNING: Do NOT modify this code. The content of this method is
           * always regenerated by the Form Editor.
           */
          // <editor-fold defaultstate="collapsed" desc="Generated Code">
          private void initComponents() {

              jPanel3 = // custom creation code
              new JPanel() {

                  public void paintComponent(Graphics g) {

                      super.paintComponent(g);

                      Font f = new Font("Arial", 0, 24);
                      Graphics2D g2 = (Graphics2D) g;
                      g2.setFont(f);

                      g2.scale(scale, scale);
                      g2.rotate((angle * Math.PI / 180), 50,50);

                      g2.drawString("\u062A\u0644\u0642\u0627\u0626\u064A\u0627\u064B.",50,50);

                  }

              };
              // preCreation Code
              jSlider1 = new javax.swing.JSlider();
              jSlider2 = new javax.swing.JSlider();
              jLabel1 = new javax.swing.JLabel();
              jLabel2 = new javax.swing.JLabel();
              jLabel3 = new javax.swing.JLabel();
              jLabel4 = new javax.swing.JLabel();

              setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
              setTitle("TextLayout Test");

              org.jdesktop.layout.GroupLayout jPanel3Layout = new org.jdesktop.layout.GroupLayout(jPanel3);
              jPanel3.setLayout(jPanel3Layout);
              jPanel3Layout.setHorizontalGroup(
                  jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                  .add(0, 0, Short.MAX_VALUE)
              );
              jPanel3Layout.setVerticalGroup(
                  jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                  .add(0, 224, Short.MAX_VALUE)
              );

              jSlider1.setMaximum(400);
              jSlider1.setMinimum(20);
              jSlider1.addMouseListener(new java.awt.event.MouseAdapter() {
                  public void mouseReleased(java.awt.event.MouseEvent evt) {
                      jSlider1MouseReleased(evt);
                  }
              });
              jSlider1.addChangeListener(new javax.swing.event.ChangeListener() {
                  public void stateChanged(javax.swing.event.ChangeEvent evt) {
                      jSlider1StateChanged(evt);
                  }
              });
              jSlider1.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
                  public void propertyChange(java.beans.PropertyChangeEvent evt) {
                      jSlider1PropertyChange(evt);
                  }
              });

              jSlider2.setMaximum(360);
              jSlider2.addChangeListener(new javax.swing.event.ChangeListener() {
                  public void stateChanged(javax.swing.event.ChangeEvent evt) {
                      jSlider2StateChanged(evt);
                  }
              });

              jLabel1.setText("1.0");

              jLabel2.setText("0");

              jLabel3.setText("Scale:");

              jLabel4.setText("Rotate:");

              org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
              getContentPane().setLayout(layout);
              layout.setHorizontalGroup(
                  layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                  .add(layout.createSequentialGroup()
                      .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                          .add(layout.createSequentialGroup()
                              .addContainerGap()
                              .add(jPanel3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                          .add(layout.createSequentialGroup()
                              .add(14, 14, 14)
                              .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                                  .add(layout.createSequentialGroup()
                                      .add(jLabel3)
                                      .add(18, 18, 18)
                                      .add(jSlider1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                                  .add(layout.createSequentialGroup()
                                      .add(jLabel4)
                                      .add(18, 18, 18)
                                      .add(jSlider2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
                              .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                              .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                                  .add(jLabel2)
                                  .add(jLabel1))
                              .add(0, 235, Short.MAX_VALUE)))
                      .addContainerGap())
              );
              layout.setVerticalGroup(
                  layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                  .add(layout.createSequentialGroup()
                      .addContainerGap()
                      .add(jPanel3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                      .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 23, Short.MAX_VALUE)
                      .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                          .add(jSlider1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                          .add(jLabel3)
                          .add(jLabel1))
                      .add(18, 18, 18)
                      .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                          .add(jLabel4)
                          .add(jSlider2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                          .add(jLabel2))
                      .addContainerGap())
              );

              pack();
          }// </editor-fold>

          private void jSlider1PropertyChange(java.beans.PropertyChangeEvent evt) {
              // TODO add your handling code here:
              
              
          }

          private void jSlider1MouseReleased(java.awt.event.MouseEvent evt) {
              // TODO add your handling code here:
              
          }

          private void jSlider2StateChanged(javax.swing.event.ChangeEvent evt) {
              // TODO add your handling code here:
              angle = (float) jSlider2.getValue() ;
              jLabel2.setText(""+angle);
              jPanel3.repaint();
             
          }

          private void jSlider1StateChanged(javax.swing.event.ChangeEvent evt) {
              scale = (float) jSlider1.getValue() / 100f;
              jLabel1.setText(""+scale);
              jPanel3.repaint();
          }
          
          /**
           * @param args the command line arguments
           */
          public static void main(String args[]) {
              /* Set the Nimbus look and feel */
              //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
              /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
               * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
               */
              try {
                  javax.swing.UIManager.LookAndFeelInfo[] installedLookAndFeels=javax.swing.UIManager.getInstalledLookAndFeels();
                  for (int idx=0; idx<installedLookAndFeels.length; idx++)
                      if ("Nimbus".equals(installedLookAndFeels[idx].getName())) {
                          javax.swing.UIManager.setLookAndFeel(installedLookAndFeels[idx].getClassName());
                          break;
                      }
              } catch (ClassNotFoundException ex) {
                  java.util.logging.Logger.getLogger(TextLayoutBug.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
              } catch (InstantiationException ex) {
                  java.util.logging.Logger.getLogger(TextLayoutBug.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
              } catch (IllegalAccessException ex) {
                  java.util.logging.Logger.getLogger(TextLayoutBug.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
              } catch (javax.swing.UnsupportedLookAndFeelException ex) {
                  java.util.logging.Logger.getLogger(TextLayoutBug.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
              }
              //</editor-fold>

              /* Create and display the form */
              java.awt.EventQueue.invokeLater(new Runnable() {
                  public void run() {
                      new TextLayoutBug().setVisible(true);
                  }
              });
          }
          
          // Variables declaration - do not modify
          private javax.swing.JLabel jLabel1;
          private javax.swing.JLabel jLabel2;
          private javax.swing.JLabel jLabel3;
          private javax.swing.JLabel jLabel4;
          private javax.swing.JPanel jPanel3;
          private javax.swing.JSlider jSlider1;
          private javax.swing.JSlider jSlider2;
          // End of variables declaration
          
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      reset the transform currently applied to the Graphics2D instance. Create the TextLayout instance for the text to be output and restore the previous transform before drawing the textlayout.

      Attachments

        Activity

          People

            srl Steven Loomis
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:
              Imported:
              Indexed: