4337267: Arabic Numeral Shaping
authorgsm
Wed, 16 Sep 2009 16:15:41 +0400
changeset 3976 6758b915b581
parent 3975 097245f7db08
child 3977 0da8e3baf0b5
4337267: Arabic Numeral Shaping Reviewed-by: peterz
jdk/src/share/classes/javax/swing/text/TextLayoutStrategy.java
jdk/src/share/classes/sun/swing/SwingUtilities2.java
jdk/test/javax/swing/JComponent/4337267/bug4337267.java
--- a/jdk/src/share/classes/javax/swing/text/TextLayoutStrategy.java	Tue Sep 15 16:26:40 2009 +0400
+++ b/jdk/src/share/classes/javax/swing/text/TextLayoutStrategy.java	Wed Sep 16 16:15:41 2009 +0400
@@ -30,6 +30,7 @@
 import java.text.BreakIterator;
 import java.awt.font.*;
 import java.awt.geom.AffineTransform;
+import javax.swing.JComponent;
 import javax.swing.event.DocumentEvent;
 import sun.font.BidiUtils;
 
@@ -301,6 +302,13 @@
             iter = BreakIterator.getLineInstance();
         }
 
+        Object shaper = null;
+        if (c instanceof JComponent) {
+            shaper = ((JComponent) c).getClientProperty(
+                                            TextAttribute.NUMERIC_SHAPING);
+        }
+        text.setShaper(shaper);
+
         measurer = new LineBreakMeasurer(text, iter, frc);
 
         // If the children of the FlowView's logical view are GlyphViews, they
@@ -399,6 +407,10 @@
             return pos - v.getStartOffset() + getBeginIndex();
         }
 
+        private void setShaper(Object shaper) {
+            this.shaper = shaper;
+        }
+
         // --- AttributedCharacterIterator methods -------------------------
 
         /**
@@ -511,6 +523,8 @@
             } else if( attribute == TextAttribute.RUN_DIRECTION ) {
                 return
                     v.getDocument().getProperty(TextAttribute.RUN_DIRECTION);
+            } else if (attribute == TextAttribute.NUMERIC_SHAPING) {
+                return shaper;
             }
             return null;
         }
@@ -532,8 +546,10 @@
             keys = new HashSet<Attribute>();
             keys.add(TextAttribute.FONT);
             keys.add(TextAttribute.RUN_DIRECTION);
+            keys.add(TextAttribute.NUMERIC_SHAPING);
         }
 
+        private Object shaper = null;
     }
 
 }
--- a/jdk/src/share/classes/sun/swing/SwingUtilities2.java	Tue Sep 15 16:26:40 2009 +0400
+++ b/jdk/src/share/classes/sun/swing/SwingUtilities2.java	Wed Sep 16 16:15:41 2009 +0400
@@ -193,6 +193,19 @@
     }
 
     /**
+     * Fill the character buffer cache.  Return the buffer length.
+     */
+    private static int syncCharsBuffer(String s) {
+        int length = s.length();
+        if ((charsBuffer == null) || (charsBuffer.length < length)) {
+            charsBuffer = s.toCharArray();
+        } else {
+            s.getChars(0, length, charsBuffer, 0);
+        }
+        return length;
+    }
+
+    /**
      * checks whether TextLayout is required to handle characters.
      *
      * @param text characters to be tested
@@ -353,7 +366,21 @@
         if (string == null || string.equals("")) {
             return 0;
         }
-        return fm.stringWidth(string);
+        boolean needsTextLayout = ((c != null) &&
+                (c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null));
+        if (needsTextLayout) {
+            synchronized(charsBufferLock) {
+                int length = syncCharsBuffer(string);
+                needsTextLayout = isComplexLayout(charsBuffer, 0, length);
+            }
+        }
+        if (needsTextLayout) {
+            TextLayout layout = createTextLayout(c, string,
+                                    fm.getFont(), fm.getFontRenderContext());
+            return (int) layout.getAdvance();
+        } else {
+            return fm.stringWidth(string);
+        }
     }
 
 
@@ -394,21 +421,11 @@
                                     String string, int availTextWidth) {
         // c may be null here.
         String clipString = "...";
-        int stringLength = string.length();
         availTextWidth -= SwingUtilities2.stringWidth(c, fm, clipString);
-        if (availTextWidth <= 0) {
-            //can not fit any characters
-            return clipString;
-        }
-
         boolean needsTextLayout;
 
         synchronized (charsBufferLock) {
-            if (charsBuffer == null || charsBuffer.length < stringLength) {
-                charsBuffer  = string.toCharArray();
-            } else {
-                string.getChars(0, stringLength, charsBuffer, 0);
-            }
+            int stringLength = syncCharsBuffer(string);
             needsTextLayout =
                 isComplexLayout(charsBuffer, 0, stringLength);
             if (!needsTextLayout) {
@@ -425,6 +442,10 @@
         if (needsTextLayout) {
             FontRenderContext frc = getFontRenderContext(c, fm);
             AttributedString aString = new AttributedString(string);
+            if (c != null) {
+                aString.addAttribute(TextAttribute.NUMERIC_SHAPING,
+                        c.getClientProperty(TextAttribute.NUMERIC_SHAPING));
+            }
             LineBreakMeasurer measurer =
                 new LineBreakMeasurer(aString.getIterator(), frc);
             int nChars = measurer.nextOffset(availTextWidth);
@@ -465,7 +486,7 @@
                  */
                 float screenWidth = (float)
                    g2d.getFont().getStringBounds(text, DEFAULT_FRC).getWidth();
-                TextLayout layout = new TextLayout(text, g2d.getFont(),
+                TextLayout layout = createTextLayout(c, text, g2d.getFont(),
                                                    g2d.getFontRenderContext());
 
                 layout = layout.getJustifiedLayout(screenWidth);
@@ -505,7 +526,21 @@
                 }
             }
 
-            g.drawString(text, x, y);
+            boolean needsTextLayout = ((c != null) &&
+                (c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null));
+            if (needsTextLayout) {
+                synchronized(charsBufferLock) {
+                    int length = syncCharsBuffer(text);
+                    needsTextLayout = isComplexLayout(charsBuffer, 0, length);
+                }
+            }
+            if (needsTextLayout) {
+                TextLayout layout = createTextLayout(c, text, g2.getFont(),
+                                                    g2.getFontRenderContext());
+                layout.draw(g2, x, y);
+            } else {
+                g.drawString(text, x, y);
+            }
 
             if (oldAAValue != null) {
                 g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue);
@@ -547,11 +582,7 @@
             boolean needsTextLayout = isPrinting;
             if (!needsTextLayout) {
                 synchronized (charsBufferLock) {
-                    if (charsBuffer == null || charsBuffer.length < textLength) {
-                        charsBuffer = text.toCharArray();
-                    } else {
-                        text.getChars(0, textLength, charsBuffer, 0);
-                    }
+                    syncCharsBuffer(text);
                     needsTextLayout =
                         isComplexLayout(charsBuffer, 0, textLength);
                 }
@@ -567,7 +598,7 @@
                 Graphics2D g2d = getGraphics2D(g);
                 if (g2d != null) {
                     TextLayout layout =
-                        new TextLayout(text, g2d.getFont(),
+                        createTextLayout(c, text, g2d.getFont(),
                                        g2d.getFontRenderContext());
                     if (isPrinting) {
                         float screenWidth = (float)g2d.getFont().
@@ -728,7 +759,7 @@
                     !isFontRenderContextPrintCompatible
                     (deviceFontRenderContext, frc)) {
                     TextLayout layout =
-                        new TextLayout(new String(data,offset,length),
+                        createTextLayout(c, new String(data, offset, length),
                                        g2d.getFont(),
                                        deviceFontRenderContext);
                     float screenWidth = (float)g2d.getFont().
@@ -846,6 +877,20 @@
         return retVal;
     }
 
+    private static TextLayout createTextLayout(JComponent c, String s,
+                                            Font f, FontRenderContext frc) {
+        Object shaper = (c == null ?
+                    null : c.getClientProperty(TextAttribute.NUMERIC_SHAPING));
+        if (shaper == null) {
+            return new TextLayout(s, f, frc);
+        } else {
+            Map<TextAttribute, Object> a = new HashMap<TextAttribute, Object>();
+            a.put(TextAttribute.FONT, f);
+            a.put(TextAttribute.NUMERIC_SHAPING, shaper);
+            return new TextLayout(s, a, frc);
+        }
+    }
+
     /*
      * Checks if two given FontRenderContexts are compatible for printing.
      * We can't just use equals as we want to exclude from the comparison :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/swing/JComponent/4337267/bug4337267.java	Wed Sep 16 16:15:41 2009 +0400
@@ -0,0 +1,254 @@
+/*
+ * @test
+ * @bug 4337267
+ * @summary test that numeric shaping works in Swing components
+ * @author Sergey Groznyh
+ * @run main bug4337267
+ */
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.font.NumericShaper;
+import java.awt.font.TextAttribute;
+import java.awt.image.BufferedImage;
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+
+public class bug4337267 {
+    TestJPanel p1, p2;
+    TestBufferedImage i1, i2;
+    JComponent[] printq;
+    JFrame window;
+    static boolean testFailed = false;
+    static boolean done = false;
+
+    String shaped =
+            "000 (E) 111 (A) \u0641\u0642\u0643 \u0662\u0662\u0662 (E) 333";
+    String text = "000 (E) 111 (A) \u0641\u0642\u0643 222 (E) 333";
+
+    void run() {
+        initUI();
+        testTextComponent();
+        testNonTextComponentHTML();
+        testNonTextComponentPlain();
+
+        doneTask();
+    }
+
+    void initUI() {
+        window = new JFrame("bug4337267");
+        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        window.setSize(800, 600);
+        Component content = createContentPane();
+        window.add(content);
+        window.setVisible(true);
+    }
+
+    Runnable printComponents = new Runnable() {
+        public void run() {
+            printComponent(printq[0], i1);
+            printComponent(printq[1], i2);
+        }
+    };
+
+    Runnable compareRasters = new Runnable() {
+        public void run() {
+            assertEquals(p1.image, p2.image);
+            assertEquals(i1, i2);
+        }
+    };
+
+    void doneTask() {
+        final Object monitor = this;
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                done = true;
+                synchronized(monitor) {
+                    monitor.notify();
+                }
+            }
+        });
+    }
+
+
+    void fail(String message) {
+        testFailed = true;
+        throw new RuntimeException(message);
+    }
+
+    void assertEquals(Object o1, Object o2) {
+        if ((o1 == null) && (o2 != null)) {
+            fail("Expected null, got " + o2);
+        } else if ((o1 != null) && (o2 == null)) {
+            fail("Expected " + o1 + ", got null");
+        } else if (!o1.equals(o2)) {
+            fail("Expected " + o1 + ", got " + o2);
+        }
+    }
+
+    void testTextComponent() {
+        System.out.println("testTextComponent:");
+        JTextArea area1 = new JTextArea();
+        injectComponent(p1, area1, false);
+        area1.setText(shaped);
+        JTextArea area2 = new JTextArea();
+        injectComponent(p2, area2, true);
+        area2.setText(text);
+        window.repaint();
+        printq = new JComponent[] { area1, area2 };
+        SwingUtilities.invokeLater(printComponents);
+        SwingUtilities.invokeLater(compareRasters);
+    }
+
+    void testNonTextComponentHTML() {
+        System.out.println("testNonTextComponentHTML:");
+        JLabel label1 = new JLabel();
+        injectComponent(p1, label1, false);
+        label1.setText("<html>" + shaped);
+        JLabel label2 = new JLabel();
+        injectComponent(p2, label2, true);
+        label2.setText("<html>" + text);
+        window.repaint();
+        printq = new JComponent[] { label1, label2 };
+        SwingUtilities.invokeLater(printComponents);
+        SwingUtilities.invokeLater(compareRasters);
+    }
+
+    void testNonTextComponentPlain() {
+        System.out.println("testNonTextComponentHTML:");
+        JLabel label1 = new JLabel();
+        injectComponent(p1, label1, false);
+        label1.setText(shaped);
+        JLabel label2 = new JLabel();
+        injectComponent(p2, label2, true);
+        label2.setText(text);
+        window.repaint();
+        printq = new JComponent[] { label1, label2 };
+        SwingUtilities.invokeLater(printComponents);
+        SwingUtilities.invokeLater(compareRasters);
+    }
+
+    void setShaping(JComponent c) {
+        c.putClientProperty(TextAttribute.NUMERIC_SHAPING,
+                    NumericShaper.getContextualShaper(NumericShaper.ARABIC));
+    }
+
+    void injectComponent(JComponent p, JComponent c, boolean shape) {
+        if (shape) {
+            setShaping(c);
+        }
+        p.removeAll();
+        p.add(c);
+    }
+
+    void printComponent(JComponent c, TestBufferedImage i) {
+        Graphics g = i.getGraphics();
+        g.setColor(c.getBackground());
+        g.fillRect(0, 0, i.getWidth(), i.getHeight());
+        c.print(g);
+    }
+
+    Component createContentPane() {
+        Dimension size = new Dimension(500, 100);
+        i1 = new TestBufferedImage(size.width, size.height,
+                                                BufferedImage.TYPE_INT_ARGB);
+        i2 = new TestBufferedImage(size.width, size.height,
+                                                BufferedImage.TYPE_INT_ARGB);
+        p1 = new TestJPanel();
+        p1.setPreferredSize(size);
+        p2 = new TestJPanel();
+        p2.setPreferredSize(size);
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+        panel.add(p1);
+        panel.add(p2);
+
+        return panel;
+    }
+
+    static class TestBufferedImage extends BufferedImage {
+        int MAX_GLITCHES = 0;
+
+        TestBufferedImage(int width, int height, int imageType) {
+            super(width, height, imageType);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (! (other instanceof TestBufferedImage)) {
+                return false;
+            }
+            TestBufferedImage image2 = (TestBufferedImage) other;
+            int width = getWidth();
+            int height = getHeight();
+            if ((image2.getWidth() != width) || (image2.getHeight() != height)) {
+                return false;
+            }
+            int glitches = 0;
+            for (int x = 0; x < width; x++) {
+                for (int y = 0; y < height; y++) {
+                    int rgb1 = getRGB(x, y);
+                    int rgb2 = image2.getRGB(x, y);
+                    if (rgb1 != rgb2) {
+                        //System.out.println(x+" "+y+" "+rgb1+" "+rgb2);
+                        glitches++;
+                    }
+                }
+            }
+            return glitches <= MAX_GLITCHES;
+        }
+    }
+
+    static class TestJPanel extends JPanel {
+        TestBufferedImage image = createImage(new Dimension(1, 1));
+
+        TestBufferedImage createImage(Dimension d) {
+            return new TestBufferedImage(d.width, d.height,
+                                                BufferedImage.TYPE_INT_ARGB);
+        }
+
+        public void setPreferredSize(Dimension size) {
+            super.setPreferredSize(size);
+            image = createImage(size);
+        }
+
+        public void paint(Graphics g) {
+            Graphics g0 = image.getGraphics();
+            super.paint(g0);
+            g.drawImage(image, 0, 0, this);
+        }
+    }
+
+
+
+    public static void main(String[] args) throws Throwable {
+        final bug4337267 test = new bug4337267();
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                test.run();
+            }
+        });
+
+         synchronized(test) {
+            while (!done) {
+                try {
+                    test.wait();
+                } catch (InterruptedException ex) {
+                    // do nothing
+                }
+            }
+        }
+
+        if (testFailed) {
+            throw new RuntimeException("FAIL");
+        }
+
+        System.out.println("OK");
+    }
+}