1 /* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.awt.Color; 25 import java.awt.Font; 26 import java.awt.FontMetrics; 27 import java.awt.Frame; 28 import java.awt.Graphics; 29 import java.awt.Graphics2D; 30 import java.awt.GraphicsEnvironment; 31 import java.awt.event.WindowAdapter; 32 import java.awt.event.WindowEvent; 33 import java.awt.font.FontRenderContext; 34 import java.awt.font.NumericShaper; 35 import java.awt.font.TextAttribute; 36 import java.awt.font.TextLayout; 37 import java.awt.image.BufferedImage; 38 import java.util.HashMap; 39 import javax.swing.JComponent; 40 import javax.swing.JLabel; 41 import javax.swing.SwingUtilities; 42 import javax.swing.UIManager; 43 import javax.swing.UIManager.LookAndFeelInfo; 44 import javax.swing.plaf.basic.BasicLookAndFeel; 45 import javax.swing.plaf.TextUIDrawing; 46 import javax.swing.plaf.metal.MetalLookAndFeel; 47 48 /** 49 * @test 50 * @bug 8132119 51 * @author Alexandr Scherbatiy 52 * @summary Provide public API for text related methods in SwingUtilities2 53 */ 54 public class bug8132119 { 55 56 private static final int WIDTH = 150; 57 private static final int HEIGHT = 50; 58 private static final Color DEFAULT_DRAW_COLOR = Color.RED; 59 private static final Color TEST_DRAW_COLOR = Color.PINK; 60 private static final Color BACKGROUND_COLOR = Color.GREEN; 61 private static final NumericShaper NUMERIC_SHAPER = NumericShaper.getShaper( 62 NumericShaper.ARABIC); 63 private static final String TEXT_UI_DRAWING_PROPERTY = "uiDrawing.text"; 64 65 public static void main(String[] args) throws Exception { 66 SwingUtilities.invokeAndWait(bug8132119::testDrawStringMethods); 67 } 68 69 private static void testDrawStringMethods() { 70 testTextDrawingProperty(); 71 testDefaultTextDrawing(); 72 testCustomTextDrawing(); 73 } 74 75 private static void testDefaultTextDrawing() { 76 setMetalLAF(); 77 testStringWidth(); 78 testStringClip(); 79 testDrawEmptyString(); 80 testDrawString(false, DEFAULT_DRAW_COLOR); 81 testDrawString(true, DEFAULT_DRAW_COLOR); 82 checkNullArguments(); 83 } 84 85 private static void testCustomTextDrawing() { 86 setMetalLAF(); 87 setCustomTextDrawing(); 88 testDrawString(false, TEST_DRAW_COLOR); 89 testDrawString(true, TEST_DRAW_COLOR); 90 } 91 92 private static void testTextDrawingProperty() { 93 try { 94 LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels(); 95 96 for (LookAndFeelInfo info : infos) { 97 UIManager.setLookAndFeel(info.getClassName()); 98 Class cls = UIManager.getLookAndFeel().getClass(); 99 if (!BasicLookAndFeel.class.isAssignableFrom(cls)) { 100 continue; 101 } 102 103 Object textDrawing = UIManager.get(TEXT_UI_DRAWING_PROPERTY); 104 if (!(textDrawing instanceof TextUIDrawing)) { 105 throw new RuntimeException( 106 String.format("%s property is not set for %s!", 107 TEXT_UI_DRAWING_PROPERTY, info.getClassName())); 108 } 109 110 } 111 } catch (Exception e) { 112 throw new RuntimeException(e); 113 } 114 } 115 116 private static void testStringWidth() { 117 118 String str = "12345678910\u036F"; 119 JComponent comp = createComponent(str); 120 Font font = comp.getFont(); 121 FontMetrics fontMetrics = comp.getFontMetrics(font); 122 int stringWidth = getTextDrawing().getStringWidth(comp, fontMetrics, str); 123 124 if (stringWidth == fontMetrics.stringWidth(str)) { 125 throw new RuntimeException("Numeric shaper is not used!"); 126 } 127 128 if (stringWidth != getLayoutWidth(str, font, NUMERIC_SHAPER)) { 129 throw new RuntimeException("Wrong text width!"); 130 } 131 } 132 133 private static void testStringClip() { 134 135 String str = "1234567890"; 136 JComponent comp = createComponent(str); 137 FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); 138 139 TextUIDrawing textDrawing = getTextDrawing(); 140 141 int width = textDrawing.getStringWidth(comp, fontMetrics, str); 142 143 String clip = textDrawing.getClippedString(comp, fontMetrics, str, width); 144 checkClippedString(str, clip, str); 145 146 clip = textDrawing.getClippedString(comp, fontMetrics, str, width + 1); 147 checkClippedString(str, clip, str); 148 149 clip = textDrawing.getClippedString(comp, fontMetrics, str, -1); 150 checkClippedString(str, clip, "..."); 151 152 clip = textDrawing.getClippedString(comp, fontMetrics, str, 0); 153 checkClippedString(str, clip, "..."); 154 155 clip = textDrawing.getClippedString(comp, fontMetrics, 156 str, width - width / str.length()); 157 int endIndex = str.length() - 3; 158 checkClippedString(str, clip, str.substring(0, endIndex) + "..."); 159 } 160 161 private static void checkClippedString(String str, String res, String golden) { 162 if (!golden.equals(res)) { 163 throw new RuntimeException(String.format("The string '%s' is not " 164 + "properly clipped. The result is '%s' instead of '%s'", 165 str, res, golden)); 166 } 167 } 168 169 private static void testDrawEmptyString() { 170 JLabel label = new JLabel(); 171 BufferedImage buffImage = createBufferedImage(50, 50); 172 Graphics2D g2 = buffImage.createGraphics(); 173 g2.setColor(DEFAULT_DRAW_COLOR); 174 TextUIDrawing textDrawing = getTextDrawing(); 175 textDrawing.drawString(null, g2, null, 0, 0); 176 textDrawing.drawString(label, g2, null, 0, 0); 177 textDrawing.drawString(null, g2, "", 0, 0); 178 textDrawing.drawString(label, g2, "", 0, 0); 179 textDrawing.drawStringUnderlineCharAt(null, g2, null, 3, 0, 0); 180 textDrawing.drawStringUnderlineCharAt(label, g2, null, 3, 0, 0); 181 textDrawing.drawStringUnderlineCharAt(null, g2, "", 3, 0, 0); 182 textDrawing.drawStringUnderlineCharAt(label, g2, "", 3, 0, 0); 183 g2.dispose(); 184 checkImageIsEmpty(buffImage); 185 } 186 187 private static void testDrawString(boolean underlined, Color drawColor) { 188 String str = "AOB"; 189 JComponent comp = createComponent(str, underlined); 190 191 BufferedImage buffImage = createBufferedImage(WIDTH, HEIGHT); 192 Graphics2D g2 = buffImage.createGraphics(); 193 194 comp.paint(g2); 195 g2.dispose(); 196 197 FontMetrics fontMetrices = comp.getFontMetrics(comp.getFont()); 198 TextUIDrawing textDrawing = getTextDrawing(); 199 int width = textDrawing.getStringWidth(comp, fontMetrices, str); 200 int xx = width / 2 + 6; 201 202 checkImageContainsSymbol(buffImage, xx, underlined ? 3 : 2, drawColor); 203 } 204 205 private static void checkNullArguments() { 206 207 Graphics g = null; 208 try { 209 String text = "Test"; 210 JComponent component = new JLabel(text); 211 BufferedImage img = createBufferedImage(100, 100); 212 g = img.createGraphics(); 213 checkNullArguments(component, g, text); 214 } finally { 215 g.dispose(); 216 } 217 } 218 219 private static void checkNullArguments(JComponent comp, Graphics g, 220 String text) { 221 222 checkNullArgumentsDrawString(comp, g, text); 223 checkNullArgumentsDrawStringUnderlineCharAt(comp, g, text); 224 checkNullArgumentsGetClippedString(comp, text); 225 checkNullArgumentsGetStringWidth(comp, text); 226 } 227 228 private static void checkNullArgumentsDrawString(JComponent comp, Graphics g, 229 String text) { 230 231 int x = 50; 232 int y = 50; 233 TextUIDrawing textDrawing = getTextDrawing(); 234 textDrawing.drawString(null, g, text, x, y); 235 textDrawing.drawString(comp, g, null, x, y); 236 237 try { 238 textDrawing.drawString(comp, null, text, x, y); 239 } catch (NullPointerException e) { 240 return; 241 } 242 243 throw new RuntimeException("NPE is not thrown"); 244 } 245 246 private static void checkNullArgumentsDrawStringUnderlineCharAt( 247 JComponent comp, Graphics g, String text) { 248 249 int x = 50; 250 int y = 50; 251 TextUIDrawing textDrawing = getTextDrawing(); 252 textDrawing.drawStringUnderlineCharAt(null, g, text, 1, x, y); 253 textDrawing.drawStringUnderlineCharAt(comp, g, null, 1, x, y); 254 255 try { 256 textDrawing.drawStringUnderlineCharAt(comp, null, text, 1, x, y); 257 } catch (NullPointerException e) { 258 return; 259 } 260 261 throw new RuntimeException("NPE is not thrown"); 262 } 263 264 private static void checkNullArgumentsGetClippedString( 265 JComponent comp, String text) { 266 267 FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); 268 TextUIDrawing textDrawing = getTextDrawing(); 269 textDrawing.getClippedString(null, fontMetrics, text, 1); 270 String result = textDrawing.getClippedString(comp, fontMetrics, null, 1); 271 if (!"".equals(result)) { 272 throw new RuntimeException("Empty string is not returned!"); 273 } 274 275 try { 276 textDrawing.getClippedString(comp, null, text, 1); 277 } catch (NullPointerException e) { 278 return; 279 } 280 281 throw new RuntimeException("NPE is not thrown"); 282 } 283 284 private static void checkNullArgumentsGetStringWidth(JComponent comp, 285 String text) { 286 287 FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); 288 TextUIDrawing textDrawing = getTextDrawing(); 289 textDrawing.getStringWidth(null, fontMetrics, text); 290 int result = textDrawing.getStringWidth(comp, fontMetrics, null); 291 292 if (result != 0) { 293 throw new RuntimeException("The string length is not 0"); 294 } 295 296 try { 297 textDrawing.getStringWidth(comp, null, text); 298 } catch (NullPointerException e) { 299 return; 300 } 301 302 throw new RuntimeException("NPE is not thrown"); 303 } 304 305 private static void setMetalLAF() { 306 try { 307 UIManager.setLookAndFeel(new MetalLookAndFeel()); 308 } catch (Exception e) { 309 throw new RuntimeException(e); 310 } 311 } 312 313 private static JComponent createComponent(String str) { 314 return createComponent(str, false); 315 } 316 317 private static JComponent createComponent(String str, boolean underline) { 318 JLabel comp = new JLabel(str); 319 320 if (underline) { 321 comp.setDisplayedMnemonicIndex(1); 322 UIManager.put("Button.showMnemonics", true); 323 } 324 comp.setSize(WIDTH, HEIGHT); 325 comp.putClientProperty(TextAttribute.NUMERIC_SHAPING, NUMERIC_SHAPER); 326 comp.setFont(getFont()); 327 comp.setForeground(DEFAULT_DRAW_COLOR); 328 return comp; 329 } 330 331 private static Font getFont() { 332 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 333 String[] fontNames = ge.getAvailableFontFamilyNames(); 334 String fontName = fontNames[0]; 335 for (String name : fontNames) { 336 if ("Dialog".equals(name)) { 337 fontName = name; 338 break; 339 } 340 } 341 return new Font(fontName, Font.PLAIN, 28); 342 } 343 344 private static int getLayoutWidth(String text, Font font, NumericShaper shaper) { 345 HashMap map = new HashMap(); 346 map.put(TextAttribute.FONT, font); 347 map.put(TextAttribute.NUMERIC_SHAPING, shaper); 348 FontRenderContext frc = new FontRenderContext(null, false, false); 349 TextLayout layout = new TextLayout(text, map, frc); 350 return (int) layout.getAdvance(); 351 } 352 353 private static TextUIDrawing getTextDrawing() { 354 return (TextUIDrawing) UIManager.get(TEXT_UI_DRAWING_PROPERTY); 355 } 356 357 private static void setCustomTextDrawing() { 358 UIManager.put(TEXT_UI_DRAWING_PROPERTY, new TestLAFTextDrawing()); 359 } 360 361 private static void checkImageIsEmpty(BufferedImage buffImage) { 362 int background = BACKGROUND_COLOR.getRGB(); 363 364 for (int i = 0; i < buffImage.getWidth(); i++) { 365 for (int j = 0; j < buffImage.getHeight(); j++) { 366 if (background != buffImage.getRGB(i, j)) { 367 throw new RuntimeException("Image is not empty!"); 368 } 369 } 370 } 371 } 372 373 private static void checkImageContainsSymbol(BufferedImage buffImage, 374 int x, int intersections, Color drawColor) { 375 boolean hasDrawColor = false; 376 int background = BACKGROUND_COLOR.getRGB(); 377 boolean isBackground = true; 378 int backgroundChangesCount = 0; 379 380 for (int y = 0; y < buffImage.getHeight(); y++) { 381 int imgRGB = buffImage.getRGB(x, y); 382 if (!(isBackground ^ (background != imgRGB))) { 383 isBackground = !isBackground; 384 backgroundChangesCount++; 385 } 386 387 if (!hasDrawColor && imgRGB == drawColor.getRGB()) { 388 hasDrawColor = true; 389 } 390 } 391 if (backgroundChangesCount != intersections * 2) { 392 throw new RuntimeException("String is not properly drawn!"); 393 } 394 if (!hasDrawColor) { 395 throw new RuntimeException("Wrong draw color!"); 396 } 397 } 398 399 private static BufferedImage createBufferedImage(int width, int height) { 400 BufferedImage bufffImage = new BufferedImage(width, height, 401 BufferedImage.TYPE_INT_RGB); 402 403 Graphics2D g = bufffImage.createGraphics(); 404 g.setColor(BACKGROUND_COLOR); 405 g.fillRect(0, 0, width, height); 406 g.dispose(); 407 return bufffImage; 408 } 409 410 private static class TestLAFTextDrawing implements TextUIDrawing { 411 412 @Override 413 public void drawString(JComponent c, Graphics g, String string, int x, int y) { 414 drawStringUnderlineCharAt(c, g, string, -1, x, y); 415 } 416 417 @Override 418 public void drawStringUnderlineCharAt(JComponent c, Graphics g, 419 String string, int underlinedIndex, int x, int y) { 420 g.setColor(BACKGROUND_COLOR); 421 g.fillRect(0, 0, c.getWidth(), c.getHeight()); 422 g.setColor(TEST_DRAW_COLOR); 423 g.drawString(string, x, y); 424 if (underlinedIndex > -1) { 425 int w = WIDTH; 426 int h = 8; 427 g.drawLine(x, y + h, x + w, y + h); 428 } 429 } 430 431 @Override 432 public String getClippedString(JComponent c, FontMetrics fm, 433 String string, int availTextWidth) { 434 return string.substring(0, string.length() - 3) + "***"; 435 } 436 437 @Override 438 public int getStringWidth(JComponent c, FontMetrics fm, String string) { 439 return fm.stringWidth(string); 440 } 441 } 442 }