From 36bb41faf936be792e48168e162f85e89e17c5ba Mon Sep 17 00:00:00 2001 From: Alexander Scherbatiy Date: Mon, 7 Nov 2016 11:22:53 +0300 Subject: [PATCH] 8168992: Add floating point implementation for new BasicGraphicsUtils text related methods use floating point API Reviewed-by: serb, ssadetsky --- .../swing/plaf/basic/BasicGraphicsUtils.java | 6 +- .../classes/javax/swing/text/Utilities.java | 2 +- .../classes/sun/swing/SwingUtilities2.java | 68 +++- .../8132119/bug8132119.java | 339 ++++++++++++++++++ 4 files changed, 401 insertions(+), 14 deletions(-) create mode 100644 jdk/test/javax/swing/plaf/basic/BasicGraphicsUtils/8132119/bug8132119.java diff --git a/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicGraphicsUtils.java b/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicGraphicsUtils.java index fa6c0c9c61a..2760b1dcfd2 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicGraphicsUtils.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicGraphicsUtils.java @@ -403,7 +403,7 @@ public class BasicGraphicsUtils */ public static void drawString(JComponent c, Graphics2D g, String string, float x, float y) { - SwingUtilities2.drawString(c, g, string, (int) x, (int) y); + SwingUtilities2.drawString(c, g, string, x, y, true); } /** @@ -439,7 +439,7 @@ public class BasicGraphicsUtils public static void drawStringUnderlineCharAt(JComponent c, Graphics2D g, String string, int underlinedIndex, float x, float y) { SwingUtilities2.drawStringUnderlineCharAt(c, g, string, underlinedIndex, - (int) x, (int) y); + x, y, true); } /** @@ -482,6 +482,6 @@ public class BasicGraphicsUtils * @since 9 */ public static float getStringWidth(JComponent c, FontMetrics fm, String string) { - return SwingUtilities2.stringWidth(c, fm, string); + return SwingUtilities2.stringWidth(c, fm, string, true); } } diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java b/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java index 940abb47c2c..7fc2cd11cd0 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java @@ -107,7 +107,7 @@ public class Utilities { TabExpander e, int startOffset) { - return drawTabbedText(s, (int) x, (int) y, (Graphics) g, e, startOffset); + return drawTabbedText(null, s, x, y, g, e, startOffset, null, true); } /** diff --git a/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java b/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java index 74085dd91c3..5bf01c5f99d 100644 --- a/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java +++ b/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java @@ -318,7 +318,21 @@ public class SwingUtilities2 { * @param fm FontMetrics used to measure the String width * @param string String to get the width of */ - public static int stringWidth(JComponent c, FontMetrics fm, String string){ + public static int stringWidth(JComponent c, FontMetrics fm, String string) { + return (int) stringWidth(c, fm, string, false); + } + + /** + * Returns the width of the passed in String. + * If the passed String is {@code null}, returns zero. + * + * @param c JComponent that will display the string, may be null + * @param fm FontMetrics used to measure the String width + * @param string String to get the width of + * @param useFPAPI use floating point API + */ + public static float stringWidth(JComponent c, FontMetrics fm, String string, + boolean useFPAPI){ if (string == null || string.equals("")) { return 0; } @@ -333,9 +347,9 @@ public class SwingUtilities2 { if (needsTextLayout) { TextLayout layout = createTextLayout(c, string, fm.getFont(), fm.getFontRenderContext()); - return (int) layout.getAdvance(); + return layout.getAdvance(); } else { - return fm.stringWidth(string); + return getFontStringWidth(string, fm, useFPAPI); } } @@ -426,6 +440,21 @@ public class SwingUtilities2 { */ public static void drawString(JComponent c, Graphics g, String text, int x, int y) { + drawString(c, g, text, x, y, false); + } + + /** + * Draws the string at the specified location. + * + * @param c JComponent that will display the string, may be null + * @param g Graphics to draw the text to + * @param text String to display + * @param x X coordinate to draw the text at + * @param y Y coordinate to draw the text at + * @param useFPAPI use floating point API + */ + public static void drawString(JComponent c, Graphics g, String text, + float x, float y, boolean useFPAPI) { // c may be null // All non-editable widgets that draw strings call into this @@ -509,7 +538,7 @@ public class SwingUtilities2 { g2.getFontRenderContext()); layout.draw(g2, x, y); } else { - g.drawString(text, x, y); + g2.drawString(text, x, y); } if (oldAAValue != null) { @@ -530,7 +559,7 @@ public class SwingUtilities2 { } } - g.drawString(text, x, y); + g.drawString(text, (int) x, (int) y); } /** @@ -544,17 +573,36 @@ public class SwingUtilities2 { * @param x X coordinate to draw the text at * @param y Y coordinate to draw the text at */ + public static void drawStringUnderlineCharAt(JComponent c,Graphics g, - String text, int underlinedIndex, int x,int y) { + String text, int underlinedIndex, int x, int y) { + drawStringUnderlineCharAt(c, g, text, underlinedIndex, x, y, false); + } + /** + * Draws the string at the specified location underlining the specified + * character. + * + * @param c JComponent that will display the string, may be null + * @param g Graphics to draw the text to + * @param text String to display + * @param underlinedIndex Index of a character in the string to underline + * @param x X coordinate to draw the text at + * @param y Y coordinate to draw the text at + * @param useFPAPI use floating point API + */ + public static void drawStringUnderlineCharAt(JComponent c, Graphics g, + String text, int underlinedIndex, + float x, float y, + boolean useFPAPI) { if (text == null || text.length() <= 0) { return; } - SwingUtilities2.drawString(c, g, text, x, y); + SwingUtilities2.drawString(c, g, text, x, y, useFPAPI); int textLength = text.length(); if (underlinedIndex >= 0 && underlinedIndex < textLength ) { - int underlineRectY = y; + float underlineRectY = y; int underlineRectHeight = 1; - int underlineRectX = 0; + float underlineRectX = 0; int underlineRectWidth = 0; boolean isPrinting = isPrinting(g); boolean needsTextLayout = isPrinting; @@ -594,7 +642,7 @@ public class SwingUtilities2 { underlineRectWidth = rect.width; } } - g.fillRect(underlineRectX, underlineRectY + 1, + g.fillRect((int) underlineRectX, (int) underlineRectY + 1, underlineRectWidth, underlineRectHeight); } } diff --git a/jdk/test/javax/swing/plaf/basic/BasicGraphicsUtils/8132119/bug8132119.java b/jdk/test/javax/swing/plaf/basic/BasicGraphicsUtils/8132119/bug8132119.java new file mode 100644 index 00000000000..d0f4a567a30 --- /dev/null +++ b/jdk/test/javax/swing/plaf/basic/BasicGraphicsUtils/8132119/bug8132119.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; +import java.awt.font.FontRenderContext; +import java.awt.font.NumericShaper; +import java.awt.font.TextAttribute; +import java.awt.font.TextLayout; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.basic.BasicGraphicsUtils; +import javax.swing.plaf.metal.MetalLookAndFeel; + +/** + * @test + * @bug 8132119 8168992 + * @author Alexandr Scherbatiy + * @summary Provide public API for text related methods in SwingBasicGraphicsUtils2 + */ +public class bug8132119 { + + private static final int WIDTH = 50; + private static final int HEIGHT = 50; + private static final Color DRAW_COLOR = Color.RED; + private static final Color BACKGROUND_COLOR = Color.GREEN; + private static final NumericShaper NUMERIC_SHAPER = NumericShaper.getShaper( + NumericShaper.ARABIC); + + public static void main(String[] args) throws Exception { + SwingUtilities.invokeAndWait(bug8132119::testStringMethods); + } + + private static void testStringMethods() { + setMetalLAF(); + testStringWidth(); + testStringClip(); + testDrawEmptyString(); + testDrawString(false); + testDrawString(true); + checkNullArguments(); + } + + private static void testStringWidth() { + + String str = "12345678910\u036F"; + JComponent comp = createComponent(str); + Font font = comp.getFont(); + FontMetrics fontMetrics = comp.getFontMetrics(font); + float stringWidth = BasicGraphicsUtils.getStringWidth(comp, fontMetrics, str); + + if (stringWidth == fontMetrics.stringWidth(str)) { + throw new RuntimeException("Numeric shaper is not used!"); + } + + if (stringWidth != getLayoutWidth(str, font, NUMERIC_SHAPER)) { + throw new RuntimeException("Wrong text width!"); + } + } + + private static void testStringClip() { + + String str = "1234567890"; + JComponent comp = createComponent(str); + FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); + + int width = (int) BasicGraphicsUtils.getStringWidth(comp, fontMetrics, str); + + String clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, str, width); + checkClippedString(str, clip, str); + + clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, str, width + 1); + checkClippedString(str, clip, str); + + clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, str, -1); + checkClippedString(str, clip, "..."); + + clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, str, 0); + checkClippedString(str, clip, "..."); + + clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, + str, width - width / str.length()); + int endIndex = str.length() - 3; + checkClippedString(str, clip, str.substring(0, endIndex) + "..."); + } + + private static void checkClippedString(String str, String res, String golden) { + if (!golden.equals(res)) { + throw new RuntimeException(String.format("The string '%s' is not " + + "properly clipped. The result is '%s' instead of '%s'", + str, res, golden)); + } + } + + private static void testDrawEmptyString() { + JLabel label = new JLabel(); + BufferedImage buffImage = createBufferedImage(50, 50); + Graphics2D g2 = buffImage.createGraphics(); + g2.setColor(DRAW_COLOR); + BasicGraphicsUtils.drawString(null, g2, null, 0, 0); + BasicGraphicsUtils.drawString(label, g2, null, 0, 0); + BasicGraphicsUtils.drawString(null, g2, "", 0, 0); + BasicGraphicsUtils.drawString(label, g2, "", 0, 0); + BasicGraphicsUtils.drawStringUnderlineCharAt(null, g2, null, 3, 0, 0); + BasicGraphicsUtils.drawStringUnderlineCharAt(label, g2, null, 3, 0, 0); + BasicGraphicsUtils.drawStringUnderlineCharAt(null, g2, "", 3, 0, 0); + BasicGraphicsUtils.drawStringUnderlineCharAt(label, g2, "", 3, 0, 0); + g2.dispose(); + checkImageIsEmpty(buffImage); + } + + private static void testDrawString(boolean underlined) { + String str = "AOB"; + JComponent comp = createComponent(str); + + BufferedImage buffImage = createBufferedImage(WIDTH, HEIGHT); + Graphics2D g2 = buffImage.createGraphics(); + + g2.setColor(DRAW_COLOR); + g2.setFont(comp.getFont()); + + FontMetrics fontMetrices = comp.getFontMetrics(comp.getFont()); + float width = BasicGraphicsUtils.getStringWidth(comp, fontMetrices, str); + float x = (WIDTH - width) / 2; + int y = 3 * HEIGHT / 4; + + if (underlined) { + BasicGraphicsUtils.drawStringUnderlineCharAt(comp, g2, str, 1, x, y); + } else { + BasicGraphicsUtils.drawString(comp, g2, str, x, y); + } + g2.dispose(); + + float xx = (WIDTH - width / 8) / 2; + checkImageContainsSymbol(buffImage, (int) xx, underlined ? 3 : 2); + } + + private static void checkNullArguments() { + + Graphics2D g = null; + try { + String text = "Test"; + JComponent component = new JLabel(text); + BufferedImage img = createBufferedImage(100, 100); + g = img.createGraphics(); + checkNullArguments(component, g, text); + } finally { + g.dispose(); + } + } + + private static void checkNullArguments(JComponent comp, Graphics2D g, + String text) { + + checkNullArgumentsDrawString(comp, g, text); + checkNullArgumentsDrawStringUnderlineCharAt(comp, g, text); + checkNullArgumentsGetClippedString(comp, text); + checkNullArgumentsGetStringWidth(comp, text); + } + + private static void checkNullArgumentsDrawString(JComponent comp, Graphics2D g, + String text) { + + float x = 50; + float y = 50; + BasicGraphicsUtils.drawString(null, g, text, x, y); + BasicGraphicsUtils.drawString(comp, g, null, x, y); + + try { + BasicGraphicsUtils.drawString(comp, null, text, x, y); + } catch (NullPointerException e) { + return; + } + + throw new RuntimeException("NPE is not thrown"); + } + + private static void checkNullArgumentsDrawStringUnderlineCharAt( + JComponent comp, Graphics2D g, String text) { + + int x = 50; + int y = 50; + BasicGraphicsUtils.drawStringUnderlineCharAt(null, g, text, 1, x, y); + BasicGraphicsUtils.drawStringUnderlineCharAt(comp, g, null, 1, x, y); + + try { + BasicGraphicsUtils.drawStringUnderlineCharAt(comp, null, text, 1, x, y); + } catch (NullPointerException e) { + return; + } + + throw new RuntimeException("NPE is not thrown"); + } + + private static void checkNullArgumentsGetClippedString( + JComponent comp, String text) { + + FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); + + BasicGraphicsUtils.getClippedString(null, fontMetrics, text, 1); + String result = BasicGraphicsUtils.getClippedString(comp, fontMetrics, null, 1); + if (!"".equals(result)) { + throw new RuntimeException("Empty string is not returned!"); + } + + try { + BasicGraphicsUtils.getClippedString(comp, null, text, 1); + } catch (NullPointerException e) { + return; + } + + throw new RuntimeException("NPE is not thrown"); + } + + private static void checkNullArgumentsGetStringWidth(JComponent comp, + String text) { + + FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); + BasicGraphicsUtils.getStringWidth(null, fontMetrics, text); + float result = BasicGraphicsUtils.getStringWidth(comp, fontMetrics, null); + + if (result != 0) { + throw new RuntimeException("The string length is not 0"); + } + + try { + BasicGraphicsUtils.getStringWidth(comp, null, text); + } catch (NullPointerException e) { + return; + } + + throw new RuntimeException("NPE is not thrown"); + } + + private static void setMetalLAF() { + try { + UIManager.setLookAndFeel(new MetalLookAndFeel()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static JComponent createComponent(String str) { + JComponent comp = new JLabel(str); + comp.setSize(WIDTH, HEIGHT); + comp.putClientProperty(TextAttribute.NUMERIC_SHAPING, NUMERIC_SHAPER); + comp.setFont(getFont()); + return comp; + } + + private static Font getFont() { + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + String[] fontNames = ge.getAvailableFontFamilyNames(); + String fontName = fontNames[0]; + for (String name : fontNames) { + if ("Arial".equals(name)) { + fontName = name; + break; + } + } + return new Font(fontName, Font.PLAIN, 28); + } + + private static float getLayoutWidth(String text, Font font, NumericShaper shaper) { + HashMap map = new HashMap(); + map.put(TextAttribute.FONT, font); + map.put(TextAttribute.NUMERIC_SHAPING, shaper); + FontRenderContext frc = new FontRenderContext(null, false, false); + TextLayout layout = new TextLayout(text, map, frc); + return layout.getAdvance(); + } + + private static void checkImageIsEmpty(BufferedImage buffImage) { + int background = BACKGROUND_COLOR.getRGB(); + + for (int i = 0; i < buffImage.getWidth(); i++) { + for (int j = 0; j < buffImage.getHeight(); j++) { + if (background != buffImage.getRGB(i, j)) { + throw new RuntimeException("Image is not empty!"); + } + } + } + } + + private static void checkImageContainsSymbol(BufferedImage buffImage, + int x, int intersections) { + int background = BACKGROUND_COLOR.getRGB(); + boolean isBackground = true; + int backgroundChangesCount = 0; + + for (int y = 0; y < buffImage.getHeight(); y++) { + if (!(isBackground ^ (background != buffImage.getRGB(x, y)))) { + isBackground = !isBackground; + backgroundChangesCount++; + } + } + if (backgroundChangesCount != intersections * 2) { + throw new RuntimeException("String is not properly drawn!"); + } + } + + private static BufferedImage createBufferedImage(int width, int height) { + BufferedImage bufffImage = new BufferedImage(width, height, + BufferedImage.TYPE_INT_RGB); + + Graphics2D g = bufffImage.createGraphics(); + g.setColor(BACKGROUND_COLOR); + g.fillRect(0, 0, width, height); + g.dispose(); + return bufffImage; + } +}