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 import java.io.File; 24 import javax.imageio.ImageIO; 25 26 import java.awt.Color; 27 import java.awt.Font; 28 import java.awt.FontMetrics; 29 import java.awt.Graphics2D; 30 import java.awt.GraphicsEnvironment; 31 import java.awt.RenderingHints; 32 import java.awt.font.FontRenderContext; 33 import java.awt.font.NumericShaper; 34 import java.awt.font.TextAttribute; 35 import java.awt.font.TextLayout; 36 import java.awt.image.BufferedImage; 37 import java.util.HashMap; 38 import javax.swing.JComponent; 39 import javax.swing.JLabel; 40 import javax.swing.SwingUtilities; 41 import javax.swing.UIManager; 42 import javax.swing.plaf.basic.BasicGraphicsUtils; 43 import javax.swing.plaf.metal.MetalLookAndFeel; 44 45 /** 46 * @test 47 * @bug 8132119 8168992 8169897 8207941 48 * @author Alexandr Scherbatiy 49 * @summary Provide public API for text related methods in SwingBasicGraphicsUtils2 50 */ 51 public class bug8132119 { 52 53 private static final int WIDTH = 50; 54 private static final int HEIGHT = 50; 55 private static final Color DRAW_COLOR = Color.RED; 56 private static final Color BACKGROUND_COLOR = Color.GREEN; 57 private static final NumericShaper NUMERIC_SHAPER = NumericShaper.getShaper( 58 NumericShaper.ARABIC); 59 main(String[] args)60 public static void main(String[] args) throws Exception { 61 SwingUtilities.invokeAndWait(bug8132119::testStringMethods); 62 } 63 testStringMethods()64 private static void testStringMethods() { 65 setMetalLAF(); 66 testStringWidth(); 67 testStringClip(); 68 testDrawEmptyString(); 69 testDrawString(false); 70 testDrawString(true); 71 checkNullArguments(); 72 } 73 testStringWidth()74 private static void testStringWidth() { 75 76 String str = "12345678910\u036F"; 77 JComponent comp = createComponent(str); 78 Font font = comp.getFont(); 79 FontMetrics fontMetrics = comp.getFontMetrics(font); 80 float stringWidth = BasicGraphicsUtils.getStringWidth(comp, fontMetrics, str); 81 82 if (stringWidth == fontMetrics.stringWidth(str)) { 83 throw new RuntimeException("Numeric shaper is not used!"); 84 } 85 86 if (stringWidth != getLayoutWidth(str, font, NUMERIC_SHAPER)) { 87 throw new RuntimeException("Wrong text width!"); 88 } 89 } 90 testStringClip()91 private static void testStringClip() { 92 93 String str = "1234567890"; 94 JComponent comp = createComponent(str); 95 FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); 96 97 int width = (int) BasicGraphicsUtils.getStringWidth(comp, fontMetrics, str); 98 99 String clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, str, width); 100 checkClippedString(str, clip, str); 101 102 clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, str, width + 1); 103 checkClippedString(str, clip, str); 104 105 clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, str, -1); 106 checkClippedString(str, clip, "..."); 107 108 clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, str, 0); 109 checkClippedString(str, clip, "..."); 110 111 clip = BasicGraphicsUtils.getClippedString(comp, fontMetrics, 112 str, width - width / str.length()); 113 int endIndex = str.length() - 3; 114 checkClippedString(str, clip, str.substring(0, endIndex) + "..."); 115 } 116 checkClippedString(String str, String res, String golden)117 private static void checkClippedString(String str, String res, String golden) { 118 if (!golden.equals(res)) { 119 throw new RuntimeException(String.format("The string '%s' is not " 120 + "properly clipped. The result is '%s' instead of '%s'", 121 str, res, golden)); 122 } 123 } 124 testDrawEmptyString()125 private static void testDrawEmptyString() { 126 JLabel label = new JLabel(); 127 BufferedImage buffImage = createBufferedImage(50, 50); 128 Graphics2D g2 = buffImage.createGraphics(); 129 g2.setColor(DRAW_COLOR); 130 BasicGraphicsUtils.drawString(null, g2, null, 0, 0); 131 BasicGraphicsUtils.drawString(label, g2, null, 0, 0); 132 BasicGraphicsUtils.drawString(null, g2, "", 0, 0); 133 BasicGraphicsUtils.drawString(label, g2, "", 0, 0); 134 BasicGraphicsUtils.drawStringUnderlineCharAt(null, g2, null, 3, 0, 0); 135 BasicGraphicsUtils.drawStringUnderlineCharAt(label, g2, null, 3, 0, 0); 136 BasicGraphicsUtils.drawStringUnderlineCharAt(null, g2, "", 3, 0, 0); 137 BasicGraphicsUtils.drawStringUnderlineCharAt(label, g2, "", 3, 0, 0); 138 g2.dispose(); 139 checkImageIsEmpty(buffImage); 140 } 141 testDrawString(boolean underlined)142 private static void testDrawString(boolean underlined) { 143 String str = "AOB"; 144 JComponent comp = createComponent(str); 145 146 BufferedImage buffImage = createBufferedImage(WIDTH, HEIGHT); 147 Graphics2D g2 = buffImage.createGraphics(); 148 149 g2.setColor(DRAW_COLOR); 150 g2.setFont(comp.getFont()); 151 g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 152 RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); 153 154 FontMetrics fontMetrices = comp.getFontMetrics(comp.getFont()); 155 float width = BasicGraphicsUtils.getStringWidth(comp, fontMetrices, str); 156 int y = 3 * HEIGHT / 4; 157 158 if (underlined) { 159 BasicGraphicsUtils.drawStringUnderlineCharAt(comp, g2, str, 1, 0, y); 160 } else { 161 BasicGraphicsUtils.drawString(comp, g2, str, 0, y); 162 } 163 g2.dispose(); 164 165 float xx = 0; 166 if (underlined) { 167 xx = BasicGraphicsUtils.getStringWidth(comp, fontMetrices, "A") + 168 BasicGraphicsUtils.getStringWidth(comp, fontMetrices, "O")/2 - 5; 169 } else { 170 xx = BasicGraphicsUtils.getStringWidth(comp, fontMetrices, "A") + 171 BasicGraphicsUtils.getStringWidth(comp, fontMetrices, "O")/2; 172 } 173 174 checkImageContainsSymbol(buffImage, (int) xx, underlined ? 3 : 2); 175 } 176 checkNullArguments()177 private static void checkNullArguments() { 178 179 Graphics2D g = null; 180 try { 181 String text = "Test"; 182 JComponent component = new JLabel(text); 183 BufferedImage img = createBufferedImage(100, 100); 184 g = img.createGraphics(); 185 checkNullArguments(component, g, text); 186 } finally { 187 g.dispose(); 188 } 189 } 190 checkNullArguments(JComponent comp, Graphics2D g, String text)191 private static void checkNullArguments(JComponent comp, Graphics2D g, 192 String text) { 193 194 checkNullArgumentsDrawString(comp, g, text); 195 checkNullArgumentsDrawStringUnderlineCharAt(comp, g, text); 196 checkNullArgumentsGetClippedString(comp, text); 197 checkNullArgumentsGetStringWidth(comp, text); 198 } 199 checkNullArgumentsDrawString(JComponent comp, Graphics2D g, String text)200 private static void checkNullArgumentsDrawString(JComponent comp, Graphics2D g, 201 String text) { 202 203 float x = 50; 204 float y = 50; 205 BasicGraphicsUtils.drawString(null, g, text, x, y); 206 BasicGraphicsUtils.drawString(comp, g, null, x, y); 207 208 try { 209 BasicGraphicsUtils.drawString(comp, null, text, x, y); 210 } catch (NullPointerException e) { 211 return; 212 } 213 214 throw new RuntimeException("NPE is not thrown"); 215 } 216 checkNullArgumentsDrawStringUnderlineCharAt( JComponent comp, Graphics2D g, String text)217 private static void checkNullArgumentsDrawStringUnderlineCharAt( 218 JComponent comp, Graphics2D g, String text) { 219 220 int x = 50; 221 int y = 50; 222 BasicGraphicsUtils.drawStringUnderlineCharAt(null, g, text, 1, x, y); 223 BasicGraphicsUtils.drawStringUnderlineCharAt(comp, g, null, 1, x, y); 224 225 try { 226 BasicGraphicsUtils.drawStringUnderlineCharAt(comp, null, text, 1, x, y); 227 } catch (NullPointerException e) { 228 return; 229 } 230 231 throw new RuntimeException("NPE is not thrown"); 232 } 233 checkNullArgumentsGetClippedString( JComponent comp, String text)234 private static void checkNullArgumentsGetClippedString( 235 JComponent comp, String text) { 236 237 FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); 238 239 BasicGraphicsUtils.getClippedString(null, fontMetrics, text, 1); 240 String result = BasicGraphicsUtils.getClippedString(comp, fontMetrics, null, 1); 241 if (!"".equals(result)) { 242 throw new RuntimeException("Empty string is not returned!"); 243 } 244 245 try { 246 BasicGraphicsUtils.getClippedString(comp, null, text, 1); 247 } catch (NullPointerException e) { 248 return; 249 } 250 251 throw new RuntimeException("NPE is not thrown"); 252 } 253 checkNullArgumentsGetStringWidth(JComponent comp, String text)254 private static void checkNullArgumentsGetStringWidth(JComponent comp, 255 String text) { 256 257 FontMetrics fontMetrics = comp.getFontMetrics(comp.getFont()); 258 BasicGraphicsUtils.getStringWidth(null, fontMetrics, text); 259 float result = BasicGraphicsUtils.getStringWidth(comp, fontMetrics, null); 260 261 if (result != 0) { 262 throw new RuntimeException("The string length is not 0"); 263 } 264 265 try { 266 BasicGraphicsUtils.getStringWidth(comp, null, text); 267 } catch (NullPointerException e) { 268 return; 269 } 270 271 throw new RuntimeException("NPE is not thrown"); 272 } 273 setMetalLAF()274 private static void setMetalLAF() { 275 try { 276 UIManager.setLookAndFeel(new MetalLookAndFeel()); 277 } catch (Exception e) { 278 throw new RuntimeException(e); 279 } 280 } 281 createComponent(String str)282 private static JComponent createComponent(String str) { 283 JComponent comp = new JLabel(str); 284 comp.setSize(WIDTH, HEIGHT); 285 comp.putClientProperty(TextAttribute.NUMERIC_SHAPING, NUMERIC_SHAPER); 286 comp.setFont(getFont()); 287 return comp; 288 } 289 getFontName(String fn, String[] fontNames)290 private static String getFontName(String fn, String[] fontNames) { 291 String fontName = null; 292 for (String name : fontNames) { 293 if (fn.equals(name)) { 294 fontName = name; 295 break; 296 } 297 } 298 return fontName; 299 } 300 getFont()301 private static Font getFont() { 302 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 303 String[] fontNames = ge.getAvailableFontFamilyNames(); 304 305 // We do not have Arial on all systems so provide some reasonable fallbacks. 306 // In case the fallbacks are not available as well, choose as last fallback 307 // the first font - however this might be a problematic choice. 308 String fontName = getFontName("Arial", fontNames); 309 if (fontName == null) { 310 fontName = getFontName("Bitstream Charter", fontNames); 311 if (fontName == null) { 312 fontName = getFontName("Dialog", fontNames); 313 if (fontName == null) { 314 fontName = fontNames[0]; 315 System.out.println("warning - preferred fonts not on the system, fall back to first font " + fontName); 316 } 317 } 318 } 319 return new Font(fontName, Font.PLAIN, 30); 320 } 321 getLayoutWidth(String text, Font font, NumericShaper shaper)322 private static float getLayoutWidth(String text, Font font, NumericShaper shaper) { 323 HashMap map = new HashMap(); 324 map.put(TextAttribute.FONT, font); 325 map.put(TextAttribute.NUMERIC_SHAPING, shaper); 326 FontRenderContext frc = new FontRenderContext(null, false, false); 327 TextLayout layout = new TextLayout(text, map, frc); 328 return layout.getAdvance(); 329 } 330 checkImageIsEmpty(BufferedImage buffImage)331 private static void checkImageIsEmpty(BufferedImage buffImage) { 332 int background = BACKGROUND_COLOR.getRGB(); 333 334 for (int i = 0; i < buffImage.getWidth(); i++) { 335 for (int j = 0; j < buffImage.getHeight(); j++) { 336 if (background != buffImage.getRGB(i, j)) { 337 throw new RuntimeException("Image is not empty!"); 338 } 339 } 340 } 341 } 342 checkImageContainsSymbol(BufferedImage buffImage, int x, int intersections)343 private static void checkImageContainsSymbol(BufferedImage buffImage, 344 int x, int intersections) { 345 346 int background = BACKGROUND_COLOR.getRGB(); 347 boolean isBackground = true; 348 int backgroundChangesCount = 0; 349 350 for (int y = 0; y < buffImage.getHeight(); y++) { 351 if (!(isBackground ^ (background != buffImage.getRGB(x, y)))) { 352 isBackground = !isBackground; 353 backgroundChangesCount++; 354 } 355 } 356 357 358 if (backgroundChangesCount != intersections * 2) { 359 try { 360 ImageIO.write(buffImage, "png", new File("image.png")); 361 } catch (Exception e) {} 362 throw new RuntimeException("String is not properly drawn!"); 363 } 364 } 365 createBufferedImage(int width, int height)366 private static BufferedImage createBufferedImage(int width, int height) { 367 BufferedImage bufffImage = new BufferedImage(width, height, 368 BufferedImage.TYPE_INT_RGB); 369 370 Graphics2D g = bufffImage.createGraphics(); 371 g.setColor(BACKGROUND_COLOR); 372 g.fillRect(0, 0, width, height); 373 g.dispose(); 374 return bufffImage; 375 } 376 } 377