# HG changeset patch # User gsm # Date 1253103341 -14400 # Node ID 6758b915b581b8af0e1e39a0eb4fdaefb41c92a8 # Parent 097245f7db08e74bc79b01bdebf6893c50b68870 4337267: Arabic Numeral Shaping Reviewed-by: peterz diff -r 097245f7db08 -r 6758b915b581 jdk/src/share/classes/javax/swing/text/TextLayoutStrategy.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(); keys.add(TextAttribute.FONT); keys.add(TextAttribute.RUN_DIRECTION); + keys.add(TextAttribute.NUMERIC_SHAPING); } + private Object shaper = null; } } diff -r 097245f7db08 -r 6758b915b581 jdk/src/share/classes/sun/swing/SwingUtilities2.java --- 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 a = new HashMap(); + 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 : diff -r 097245f7db08 -r 6758b915b581 jdk/test/javax/swing/JComponent/4337267/bug4337267.java --- /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("" + 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 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"); + } +}