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      */
  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      */
 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      */
 111     protected abstract GraphicsDevice makeScreenDevice(int screennum);
 112 
 113     /**
 114      * Returns the default screen graphics device.
 115      */
 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      */
 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 
 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
 149     public static void useAlternateFontforJALocales() {
 150         getFontManagerForSGE().useAlternateFontforJALocales();
 151     }
 152 
 153      /**
 154      * Returns all fonts available in this environment.
 155      */
 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 
 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 
 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      */
 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      */
 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      */
 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      */
 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      */
 262     public void addDisplayChangedListener(DisplayChangedListener client) {
 263         displayChanger.add(client);
 264     }
 265 
 266     /**
 267      * Remove a DisplayChangeListener from Win32GraphicsEnvironment
 268      */
 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      */
 287     public boolean isFlipStrategyPreferred(ComponentPeer peer) {
 288         return false;
 289     }
 290 
 291     public static boolean isUIScaleEnabled() {
 292         return uiScaleEnabled;
 293     }
 294 
 295     public static double getDebugScale() {
 296         return debugScale;
 297     }
 298 
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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 absolute coordinates from the user's space to the device space
 439      * using appropriate device transformation.
 440      *
 441      * @param  x absolute coordinate in the user's space
 442      * @param  y absolute coordinate in the user's space
 443      * @return the point which uses device space(pixels)
 444      */
 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      */
 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 }