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