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