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