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