1 /*
2  * Copyright (c) 1997, 2020, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.java2d;
27 
28 import java.awt.AWTError;
29 import java.awt.Color;
30 import java.awt.Dimension;
31 import java.awt.Font;
32 import java.awt.Graphics2D;
33 import java.awt.GraphicsConfiguration;
34 import java.awt.GraphicsDevice;
35 import java.awt.GraphicsEnvironment;
36 import java.awt.Insets;
37 import java.awt.Point;
38 import java.awt.Rectangle;
39 import java.awt.Toolkit;
40 import java.awt.geom.AffineTransform;
41 import java.awt.image.BufferedImage;
42 import java.awt.peer.ComponentPeer;
43 import java.security.AccessController;
44 import java.util.Locale;
45 import java.util.TreeMap;
46 
47 import sun.awt.DisplayChangedListener;
48 import sun.awt.SunDisplayChanger;
49 import sun.font.FontManager;
50 import sun.font.FontManagerFactory;
51 import sun.font.FontManagerForSGE;
52 import sun.java2d.pipe.Region;
53 import sun.security.action.GetPropertyAction;
54 
55 /**
56  * This is an implementation of a GraphicsEnvironment object for the
57  * default local GraphicsEnvironment.
58  *
59  * @see GraphicsDevice
60  * @see GraphicsConfiguration
61  */
62 public abstract class SunGraphicsEnvironment extends GraphicsEnvironment
63     implements DisplayChangedListener {
64 
65     /** Establish the default font to be used by SG2D. */
66     private final Font defaultFont = new Font(Font.DIALOG, Font.PLAIN, 12);
67 
68     private static final boolean uiScaleEnabled;
69     private static final double debugScale;
70 
71     static {
72         uiScaleEnabled = "true".equals(AccessController.doPrivileged(
73                 new GetPropertyAction("sun.java2d.uiScale.enabled", "true")));
74         debugScale = uiScaleEnabled ? getScaleFactor("sun.java2d.uiScale") : -1;
75     }
76 
77     protected GraphicsDevice[] screens;
78 
79     /**
80      * Returns an array of all of the screen devices.
81      */
getScreenDevices()82     public synchronized GraphicsDevice[] getScreenDevices() {
83         GraphicsDevice[] ret = screens;
84         if (ret == null) {
85             int num = getNumScreens();
86             ret = new GraphicsDevice[num];
87             for (int i = 0; i < num; i++) {
88                 ret[i] = makeScreenDevice(i);
89             }
90             screens = ret;
91         }
92         return ret;
93     }
94 
95     /**
96      * Returns the number of screen devices of this graphics environment.
97      *
98      * @return the number of screen devices of this graphics environment
99      */
getNumScreens()100     protected abstract int getNumScreens();
101 
102     /**
103      * Create and return the screen device with the specified number. The
104      * device with number {@code 0} will be the default device (returned
105      * by {@link #getDefaultScreenDevice()}.
106      *
107      * @param screennum the number of the screen to create
108      *
109      * @return the created screen device
110      */
makeScreenDevice(int screennum)111     protected abstract GraphicsDevice makeScreenDevice(int screennum);
112 
113     /**
114      * Returns the default screen graphics device.
115      */
getDefaultScreenDevice()116     public GraphicsDevice getDefaultScreenDevice() {
117         GraphicsDevice[] screens = getScreenDevices();
118         if (screens.length == 0) {
119             throw new AWTError("no screen devices");
120         }
121         return screens[0];
122     }
123 
124     /**
125      * Returns a Graphics2D object for rendering into the
126      * given BufferedImage.
127      * @throws NullPointerException if BufferedImage argument is null
128      */
createGraphics(BufferedImage img)129     public Graphics2D createGraphics(BufferedImage img) {
130         if (img == null) {
131             throw new NullPointerException("BufferedImage cannot be null");
132         }
133         SurfaceData sd = SurfaceData.getPrimarySurfaceData(img);
134         return new SunGraphics2D(sd, Color.white, Color.black, defaultFont);
135     }
136 
getFontManagerForSGE()137     public static FontManagerForSGE getFontManagerForSGE() {
138         FontManager fm = FontManagerFactory.getInstance();
139         return (FontManagerForSGE) fm;
140     }
141 
142     /* Modifies the behaviour of a subsequent call to preferLocaleFonts()
143      * to use Mincho instead of Gothic for dialoginput in JA locales
144      * on windows. Not needed on other platforms.
145      *
146      * @deprecated as of JDK9. To be removed in a future release
147      */
148     @Deprecated
useAlternateFontforJALocales()149     public static void useAlternateFontforJALocales() {
150         getFontManagerForSGE().useAlternateFontforJALocales();
151     }
152 
153      /**
154      * Returns all fonts available in this environment.
155      */
getAllFonts()156     public Font[] getAllFonts() {
157         FontManagerForSGE fm = getFontManagerForSGE();
158         Font[] installedFonts = fm.getAllInstalledFonts();
159         Font[] created = fm.getCreatedFonts();
160         if (created == null || created.length == 0) {
161             return installedFonts;
162         } else {
163             int newlen = installedFonts.length + created.length;
164             Font [] fonts = java.util.Arrays.copyOf(installedFonts, newlen);
165             System.arraycopy(created, 0, fonts,
166                              installedFonts.length, created.length);
167             return fonts;
168         }
169     }
170 
getAvailableFontFamilyNames(Locale requestedLocale)171     public String[] getAvailableFontFamilyNames(Locale requestedLocale) {
172         FontManagerForSGE fm = getFontManagerForSGE();
173         String[] installed = fm.getInstalledFontFamilyNames(requestedLocale);
174         /* Use a new TreeMap as used in getInstalledFontFamilyNames
175          * and insert all the keys in lower case, so that the sort order
176          * is the same as the installed families. This preserves historical
177          * behaviour and inserts new families in the right place.
178          * It would have been marginally more efficient to directly obtain
179          * the tree map and just insert new entries, but not so much as
180          * to justify the extra internal interface.
181          */
182         TreeMap<String, String> map = fm.getCreatedFontFamilyNames();
183         if (map == null || map.size() == 0) {
184             return installed;
185         } else {
186             for (int i=0; i<installed.length; i++) {
187                 map.put(installed[i].toLowerCase(requestedLocale),
188                         installed[i]);
189             }
190             String[] retval =  new String[map.size()];
191             Object [] keyNames = map.keySet().toArray();
192             for (int i=0; i < keyNames.length; i++) {
193                 retval[i] = map.get(keyNames[i]);
194             }
195             return retval;
196         }
197     }
198 
getAvailableFontFamilyNames()199     public String[] getAvailableFontFamilyNames() {
200         return getAvailableFontFamilyNames(Locale.getDefault());
201     }
202 
203     /**
204      * Return the bounds of a GraphicsDevice, less its screen insets.
205      * See also java.awt.GraphicsEnvironment.getUsableBounds();
206      */
getUsableBounds(GraphicsDevice gd)207     public static Rectangle getUsableBounds(GraphicsDevice gd) {
208         GraphicsConfiguration gc = gd.getDefaultConfiguration();
209         Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
210         Rectangle usableBounds = gc.getBounds();
211 
212         usableBounds.x += insets.left;
213         usableBounds.y += insets.top;
214         usableBounds.width -= (insets.left + insets.right);
215         usableBounds.height -= (insets.top + insets.bottom);
216 
217         return usableBounds;
218     }
219 
220     /**
221      * From the DisplayChangedListener interface; called
222      * when the display mode has been changed.
223      */
displayChanged()224     public void displayChanged() {
225         // notify screens in device array to do display update stuff
226         for (GraphicsDevice gd : getScreenDevices()) {
227             if (gd instanceof DisplayChangedListener) {
228                 ((DisplayChangedListener) gd).displayChanged();
229             }
230         }
231 
232         // notify SunDisplayChanger list (e.g. VolatileSurfaceManagers and
233         // SurfaceDataProxies) about the display change event
234         displayChanger.notifyListeners();
235     }
236 
237     /**
238      * Part of the DisplayChangedListener interface:
239      * propagate this event to listeners
240      */
paletteChanged()241     public void paletteChanged() {
242         displayChanger.notifyPaletteChanged();
243     }
244 
245     /**
246      * Returns true when the display is local, false for remote displays.
247      *
248      * @return true when the display is local, false for remote displays
249      */
isDisplayLocal()250     public abstract boolean isDisplayLocal();
251 
252     /*
253      * ----DISPLAY CHANGE SUPPORT----
254      */
255 
256     protected SunDisplayChanger displayChanger = new SunDisplayChanger();
257 
258     /**
259      * Add a DisplayChangeListener to be notified when the display settings
260      * are changed.
261      */
addDisplayChangedListener(DisplayChangedListener client)262     public void addDisplayChangedListener(DisplayChangedListener client) {
263         displayChanger.add(client);
264     }
265 
266     /**
267      * Remove a DisplayChangeListener from Win32GraphicsEnvironment
268      */
removeDisplayChangedListener(DisplayChangedListener client)269     public void removeDisplayChangedListener(DisplayChangedListener client) {
270         displayChanger.remove(client);
271     }
272 
273     /*
274      * ----END DISPLAY CHANGE SUPPORT----
275      */
276 
277     /**
278      * Returns true if FlipBufferStrategy with COPIED buffer contents
279      * is preferred for this peer's GraphicsConfiguration over
280      * BlitBufferStrategy, false otherwise.
281      *
282      * The reason FlipBS could be preferred is that in some configurations
283      * an accelerated copy to the screen is supported (like Direct3D 9)
284      *
285      * @return true if flip strategy should be used, false otherwise
286      */
isFlipStrategyPreferred(ComponentPeer peer)287     public boolean isFlipStrategyPreferred(ComponentPeer peer) {
288         return false;
289     }
290 
isUIScaleEnabled()291     public static boolean isUIScaleEnabled() {
292         return uiScaleEnabled;
293     }
294 
getDebugScale()295     public static double getDebugScale() {
296         return debugScale;
297     }
298 
getScaleFactor(String propertyName)299     public static double getScaleFactor(String propertyName) {
300 
301         String scaleFactor = AccessController.doPrivileged(
302                 new GetPropertyAction(propertyName, "-1"));
303 
304         if (scaleFactor == null || scaleFactor.equals("-1")) {
305             return -1;
306         }
307 
308         try {
309             double units = 1.0;
310 
311             if (scaleFactor.endsWith("x")) {
312                 scaleFactor = scaleFactor.substring(0, scaleFactor.length() - 1);
313             } else if (scaleFactor.endsWith("dpi")) {
314                 units = 96;
315                 scaleFactor = scaleFactor.substring(0, scaleFactor.length() - 3);
316             } else if (scaleFactor.endsWith("%")) {
317                 units = 100;
318                 scaleFactor = scaleFactor.substring(0, scaleFactor.length() - 1);
319             }
320 
321             double scale = Double.parseDouble(scaleFactor);
322             return scale <= 0 ? -1 : scale / units;
323         } catch (NumberFormatException ignored) {
324             return -1;
325         }
326     }
327 
328     /**
329      * Returns the graphics configuration which bounds contain the given point.
330      *
331      * @param  current the default configuration which is checked in the first
332      *         place
333      * @param  x the x coordinate of the given point
334      * @param  y the y coordinate of the given point
335      * @return the graphics configuration
336      */
getGraphicsConfigurationAtPoint( GraphicsConfiguration current, double x, double y)337     public static GraphicsConfiguration getGraphicsConfigurationAtPoint(
338             GraphicsConfiguration current, double x, double y) {
339         if (current.getBounds().contains(x, y)) {
340             return current;
341         }
342         GraphicsEnvironment env = getLocalGraphicsEnvironment();
343         for (GraphicsDevice device : env.getScreenDevices()) {
344             GraphicsConfiguration config = device.getDefaultConfiguration();
345             if (config.getBounds().contains(x, y)) {
346                 return config;
347             }
348         }
349         return current;
350     }
351 
352     /**
353      * Returns the bounds of the graphics configuration in device space.
354      *
355      * @param  config the graphics configuration which bounds are requested
356      * @return the bounds of the area covered by this
357      *         {@code GraphicsConfiguration} in device space (pixels)
358      */
getGCDeviceBounds(GraphicsConfiguration config)359     public static Rectangle getGCDeviceBounds(GraphicsConfiguration config) {
360         AffineTransform tx = config.getDefaultTransform();
361         Rectangle bounds = config.getBounds();
362         bounds.width *= tx.getScaleX();
363         bounds.height *= tx.getScaleY();
364         return bounds;
365     }
366 
367     /**
368      * Converts the size (w, h) from the device space to the user's space using
369      * passed graphics configuration.
370      *
371      * @param  gc the graphics configuration to be used for transformation
372      * @param  w the width in the device space
373      * @param  h the height in the device space
374      * @return the size in the user's space
375      */
toUserSpace(GraphicsConfiguration gc, int w, int h)376     public static Dimension toUserSpace(GraphicsConfiguration gc,
377                                         int w, int h) {
378         AffineTransform tx = gc.getDefaultTransform();
379         return new Dimension(
380                 Region.clipRound(w / tx.getScaleX()),
381                 Region.clipRound(h / tx.getScaleY())
382         );
383     }
384 
385     /**
386      * Converts absolute coordinates from the user's space to the device space
387      * using appropriate device transformation.
388      *
389      * @param  x absolute coordinate in the user's space
390      * @param  y absolute coordinate in the user's space
391      * @return the point which uses device space (pixels)
392      */
toDeviceSpaceAbs(int x, int y)393     public static Point toDeviceSpaceAbs(int x, int y) {
394         GraphicsConfiguration gc = getLocalGraphicsEnvironment()
395                 .getDefaultScreenDevice().getDefaultConfiguration();
396         gc = getGraphicsConfigurationAtPoint(gc, x, y);
397         return toDeviceSpaceAbs(gc, x, y, 0, 0).getLocation();
398     }
399 
400     /**
401      * Converts the rectangle from the user's space to the device space using
402      * appropriate device transformation.
403      *
404      * @param  rect the rectangle in the user's space
405      * @return the rectangle which uses device space (pixels)
406      */
toDeviceSpaceAbs(Rectangle rect)407     public static Rectangle toDeviceSpaceAbs(Rectangle rect) {
408         GraphicsConfiguration gc = getLocalGraphicsEnvironment()
409                 .getDefaultScreenDevice().getDefaultConfiguration();
410         gc = getGraphicsConfigurationAtPoint(gc, rect.x, rect.y);
411         return toDeviceSpaceAbs(gc, rect.x, rect.y, rect.width, rect.height);
412     }
413 
414     /**
415      * Converts absolute coordinates (x, y) and the size (w, h) from the user's
416      * space to the device space using passed graphics configuration.
417      *
418      * @param  gc the graphics configuration to be used for transformation
419      * @param  x absolute coordinate in the user's space
420      * @param  y absolute coordinate in the user's space
421      * @param  w the width in the user's space
422      * @param  h the height in the user's space
423      * @return the rectangle which uses device space (pixels)
424      */
toDeviceSpaceAbs(GraphicsConfiguration gc, int x, int y, int w, int h)425     public static Rectangle toDeviceSpaceAbs(GraphicsConfiguration gc,
426                                              int x, int y, int w, int h) {
427         AffineTransform tx = gc.getDefaultTransform();
428         Rectangle screen = gc.getBounds();
429         return new Rectangle(
430                 screen.x + Region.clipRound((x - screen.x) * tx.getScaleX()),
431                 screen.y + Region.clipRound((y - screen.y) * tx.getScaleY()),
432                 Region.clipRound(w * tx.getScaleX()),
433                 Region.clipRound(h * tx.getScaleY())
434         );
435     }
436 
437     /**
438      * Converts coordinates from the user's space to the device space using
439      * appropriate device transformation.
440      *
441      * @param  x coordinate in the user's space
442      * @param  y coordinate in the user's space
443      * @return the point which uses device space (pixels)
444      */
toDeviceSpace(int x, int y)445     public static Point toDeviceSpace(int x, int y) {
446         GraphicsConfiguration gc = getLocalGraphicsEnvironment()
447                 .getDefaultScreenDevice().getDefaultConfiguration();
448         gc = getGraphicsConfigurationAtPoint(gc, x, y);
449         return toDeviceSpace(gc, x, y, 0, 0).getLocation();
450     }
451 
452     /**
453      * Converts coordinates (x, y) and the size (w, h) from the user's
454      * space to the device space using passed graphics configuration.
455      *
456      * @param  gc the graphics configuration to be used for transformation
457      * @param  x coordinate in the user's space
458      * @param  y coordinate in the user's space
459      * @param  w the width in the user's space
460      * @param  h the height in the user's space
461      * @return the rectangle which uses device space (pixels)
462      */
toDeviceSpace(GraphicsConfiguration gc, int x, int y, int w, int h)463     public static Rectangle toDeviceSpace(GraphicsConfiguration gc,
464                                           int x, int y, int w, int h) {
465         AffineTransform tx = gc.getDefaultTransform();
466         return new Rectangle(
467                 Region.clipRound(x * tx.getScaleX()),
468                 Region.clipRound(y * tx.getScaleY()),
469                 Region.clipRound(w * tx.getScaleX()),
470                 Region.clipRound(h * tx.getScaleY())
471         );
472     }
473 }
474