blob: 63b942bf4358796658190ac885f9bf4e5dfb0121 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.java2d;
27
28import java.awt.Color;
29import java.awt.Font;
30import java.awt.Graphics2D;
31import java.awt.GraphicsConfiguration;
32import java.awt.GraphicsDevice;
33import java.awt.GraphicsEnvironment;
34import java.awt.Insets;
35import java.awt.Rectangle;
36import java.awt.Toolkit;
37import java.awt.font.TextAttribute;
38import java.awt.image.BufferedImage;
39import java.io.BufferedReader;
40import java.io.File;
41import java.io.FileInputStream;
42import java.io.FilenameFilter;
43import java.io.InputStreamReader;
44import java.io.IOException;
45import java.text.AttributedCharacterIterator;
46import java.util.ArrayList;
47import java.util.HashSet;
48import java.util.Iterator;
49import java.util.Locale;
50import java.util.Map;
51import java.util.NoSuchElementException;
52import java.util.Set;
53import java.util.StringTokenizer;
54import java.util.TreeMap;
55import java.util.Vector;
56import java.util.concurrent.ConcurrentHashMap;
57import java.util.logging.Level;
58import java.util.logging.Logger;
59import sun.awt.AppContext;
60import sun.awt.DisplayChangedListener;
61import sun.awt.FontConfiguration;
62import sun.awt.SunDisplayChanger;
63import sun.font.CompositeFontDescriptor;
64import sun.font.Font2D;
65import sun.font.FontManager;
66import sun.font.NativeFont;
67
68/**
69 * This is an implementation of a GraphicsEnvironment object for the
70 * default local GraphicsEnvironment.
71 *
72 * @see GraphicsDevice
73 * @see GraphicsConfiguration
74 */
75
76public abstract class SunGraphicsEnvironment extends GraphicsEnvironment
77 implements FontSupport, DisplayChangedListener {
78
79 public static boolean isLinux;
80 public static boolean isSolaris;
81 public static boolean isWindows;
82 public static boolean noType1Font;
83 private static Font defaultFont;
84 private static String defaultFontFileName;
85 private static String defaultFontName;
86 public static final String lucidaFontName = "Lucida Sans Regular";
87 public static final String lucidaFileName = "LucidaSansRegular.ttf";
88 public static boolean debugFonts = false;
89 protected static Logger logger = null;
90 private static ArrayList badFonts;
91 public static String jreLibDirName;
92 public static String jreFontDirName;
93 private static HashSet<String> missingFontFiles = null;
94
95 private FontConfiguration fontConfig;
96
97 /* fontPath is the location of all fonts on the system, excluding the
98 * JRE's own font directory but including any path specified using the
99 * sun.java2d.fontpath property. Together with that property, it is
100 * initialised by the getPlatformFontPath() method
101 * This call must be followed by a call to registerFontDirs(fontPath)
102 * once any extra debugging path has been appended.
103 */
104 protected String fontPath;
105
106 /* discoveredAllFonts is set to true when all fonts on the font path are
107 * discovered. This usually also implies opening, validating and
108 * registering, but an implementation may be optimized to avold this.
109 * So see also "loadedAllFontFiles"
110 */
111 private boolean discoveredAllFonts = false;
112
113 /* loadedAllFontFiles is set to true when all fonts on the font path are
114 * actually opened, validated and registered. This always implies
115 * discoveredAllFonts is true.
116 */
117 private boolean loadedAllFontFiles = false;
118
119 protected HashSet registeredFontFiles = new HashSet();
120 public static String eudcFontFileName; /* Initialised only on windows */
121
122 private static boolean isOpenJDK;
123 /**
124 * A few things in Java 2D code are different in OpenJDK,
125 * so need a way to tell which implementation this is.
126 * The absence of Lucida Sans Regular is the simplest way for now.
127 */
128 public static boolean isOpenJDK() {
129 return isOpenJDK;
130 }
131
132 static {
133 java.security.AccessController.doPrivileged(
134 new java.security.PrivilegedAction() {
135 public Object run() {
136
137 jreLibDirName = System.getProperty("java.home","") +
138 File.separator + "lib";
139 jreFontDirName = jreLibDirName + File.separator + "fonts";
140 File lucidaFile =
141 new File(jreFontDirName + File.separator + lucidaFileName);
142 isOpenJDK = !lucidaFile.exists();
143
144 String debugLevel =
145 System.getProperty("sun.java2d.debugfonts");
146
147 if (debugLevel != null && !debugLevel.equals("false")) {
148 debugFonts = true;
149 logger = Logger.getLogger("sun.java2d");
150 if (debugLevel.equals("warning")) {
151 logger.setLevel(Level.WARNING);
152 } else if (debugLevel.equals("severe")) {
153 logger.setLevel(Level.SEVERE);
154 }
155 }
156 return null;
157 }
158 });
159 };
160
161 public SunGraphicsEnvironment() {
162 java.security.AccessController.doPrivileged(
163 new java.security.PrivilegedAction() {
164 public Object run() {
165 String osName = System.getProperty("os.name");
166 if ("Linux".equals(osName)) {
167 isLinux = true;
168 } else if ("SunOS".equals(osName)) {
169 isSolaris = true;
170 } else if ("Windows".equals(osName)) {
171 isWindows = true;
172 }
173
174 noType1Font = "true".
175 equals(System.getProperty("sun.java2d.noType1Font"));
176
177 if (isOpenJDK()) {
178 String[] fontInfo = FontManager.getDefaultPlatformFont();
179 defaultFontName = fontInfo[0];
180 defaultFontFileName = fontInfo[1];
181 } else {
182 defaultFontName = lucidaFontName;
183 if (useAbsoluteFontFileNames()) {
184 defaultFontFileName =
185 jreFontDirName + File.separator + lucidaFileName;
186 } else {
187 defaultFontFileName = lucidaFileName;
188 }
189 }
190
191 File badFontFile =
192 new File(jreFontDirName + File.separator + "badfonts.txt");
193 if (badFontFile.exists()) {
194 FileInputStream fis = null;
195 try {
196 badFonts = new ArrayList();
197 fis = new FileInputStream(badFontFile);
198 InputStreamReader isr = new InputStreamReader(fis);
199 BufferedReader br = new BufferedReader(isr);
200 while (true) {
201 String name = br.readLine();
202 if (name == null) {
203 break;
204 } else {
205 if (debugFonts) {
206 logger.warning("read bad font: " + name);
207 }
208 badFonts.add(name);
209 }
210 }
211 } catch (IOException e) {
212 try {
213 if (fis != null) {
214 fis.close();
215 }
216 } catch (IOException ioe) {
217 }
218 }
219 }
220
221 /* Here we get the fonts in jre/lib/fonts and register them
222 * so they are always available and preferred over other fonts.
223 * This needs to be registered before the composite fonts as
224 * otherwise some native font that corresponds may be found
225 * as we don't have a way to handle two fonts of the same
226 * name, so the JRE one must be the first one registered.
227 * Pass "true" to registerFonts method as on-screen these
228 * JRE fonts always go through the T2K rasteriser.
229 */
230 if (isLinux) {
231 /* Linux font configuration uses these fonts */
232 registerFontDir(jreFontDirName);
233 }
234 registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK,
235 true, false);
236
237 /* Register the JRE fonts so that the native platform can
238 * access them. This is used only on Windows so that when
239 * printing the printer driver can access the fonts.
240 */
241 registerJREFontsWithPlatform(jreFontDirName);
242
243 /* Create the font configuration and get any font path
244 * that might be specified.
245 */
246 fontConfig = createFontConfiguration();
247 getPlatformFontPathFromFontConfig();
248
249 String extraFontPath = fontConfig.getExtraFontPath();
250
251 /* In prior releases the debugging font path replaced
252 * all normally located font directories except for the
253 * JRE fonts dir. This directory is still always located and
254 * placed at the head of the path but as an augmentation
255 * to the previous behaviour the
256 * changes below allow you to additionally append to
257 * the font path by starting with append: or prepend by
258 * starting with a prepend: sign. Eg: to append
259 * -Dsun.java2d.fontpath=append:/usr/local/myfonts
260 * and to prepend
261 * -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp
262 *
263 * If there is an appendedfontpath it in the font configuration
264 * it is used instead of searching the system for dirs.
265 * The behaviour of append and prepend is then similar
266 * to the normal case. ie it goes after what
267 * you prepend and * before what you append. If the
268 * sun.java2d.fontpath property is used, but it
269 * neither the append or prepend syntaxes is used then as
270 * except for the JRE dir the path is replaced and it is
271 * up to you to make sure that all the right directories
272 * are located. This is platform and locale-specific so
273 * its almost impossible to get right, so it should be
274 * used with caution.
275 */
276 boolean prependToPath = false;
277 boolean appendToPath = false;
278 String dbgFontPath = System.getProperty("sun.java2d.fontpath");
279
280 if (dbgFontPath != null) {
281 if (dbgFontPath.startsWith("prepend:")) {
282 prependToPath = true;
283 dbgFontPath =
284 dbgFontPath.substring("prepend:".length());
285 } else if (dbgFontPath.startsWith("append:")) {
286 appendToPath = true;
287 dbgFontPath =
288 dbgFontPath.substring("append:".length());
289 }
290 }
291
292 if (debugFonts) {
293 logger.info("JRE font directory: " + jreFontDirName);
294 logger.info("Extra font path: " + extraFontPath);
295 logger.info("Debug font path: " + dbgFontPath);
296 }
297
298 if (dbgFontPath != null) {
299 /* In debugging mode we register all the paths
300 * Caution: this is a very expensive call on Solaris:-
301 */
302 fontPath = getPlatformFontPath(noType1Font);
303
304 if (extraFontPath != null) {
305 fontPath =
306 extraFontPath + File.pathSeparator + fontPath;
307 }
308 if (appendToPath) {
309 fontPath = fontPath + File.pathSeparator + dbgFontPath;
310 } else if (prependToPath) {
311 fontPath = dbgFontPath + File.pathSeparator + fontPath;
312 } else {
313 fontPath = dbgFontPath;
314 }
315 registerFontDirs(fontPath);
316 } else if (extraFontPath != null) {
317 /* If the font configuration contains an "appendedfontpath"
318 * entry, it is interpreted as a set of locations that
319 * should always be registered.
320 * It may be additional to locations normally found for
321 * that place, or it may be locations that need to have
322 * all their paths registered to locate all the needed
323 * platform names.
324 * This is typically when the same .TTF file is referenced
325 * from multiple font.dir files and all of these must be
326 * read to find all the native (XLFD) names for the font,
327 * so that X11 font APIs can be used for as many code
328 * points as possible.
329 */
330 registerFontDirs(extraFontPath);
331 }
332
333 /* On Solaris, we need to register the Japanese TrueType
334 * directory so that we can find the corresponding bitmap
335 * fonts. This could be done by listing the directory in
336 * the font configuration file, but we don't want to
337 * confuse users with this quirk. There are no bitmap fonts
338 * for other writing systems that correspond to TrueType
339 * fonts and have matching XLFDs. We need to register the
340 * bitmap fonts only in environments where they're on the
341 * X font path, i.e., in the Japanese locale.
342 * Note that if the X Toolkit is in use the font path isn't
343 * set up by JDK, but users of a JA locale should have it
344 * set up already by their login environment.
345 */
346 if (isSolaris && Locale.JAPAN.equals(Locale.getDefault())) {
347 registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT");
348 }
349
350 initCompositeFonts(fontConfig, null);
351
352 /* Establish the default font to be used by SG2D etc */
353 defaultFont = new Font(Font.DIALOG, Font.PLAIN, 12);
354
355 return null;
356 }
357 });
358 }
359
360 protected GraphicsDevice[] screens;
361
362 /**
363 * Returns an array of all of the screen devices.
364 */
365 public synchronized GraphicsDevice[] getScreenDevices() {
366 GraphicsDevice[] ret = screens;
367 if (ret == null) {
368 int num = getNumScreens();
369 ret = new GraphicsDevice[num];
370 for (int i = 0; i < num; i++) {
371 ret[i] = makeScreenDevice(i);
372 }
373 screens = ret;
374 }
375 return ret;
376 }
377
378 protected abstract int getNumScreens();
379 protected abstract GraphicsDevice makeScreenDevice(int screennum);
380
381 /**
382 * Returns the default screen graphics device.
383 */
384 public GraphicsDevice getDefaultScreenDevice() {
385 return getScreenDevices()[0];
386 }
387
388 /**
389 * Returns a Graphics2D object for rendering into the
390 * given BufferedImage.
391 * @throws NullPointerException if BufferedImage argument is null
392 */
393 public Graphics2D createGraphics(BufferedImage img) {
394 if (img == null) {
395 throw new NullPointerException("BufferedImage cannot be null");
396 }
397 SurfaceData sd = SurfaceData.getPrimarySurfaceData(img);
398 return new SunGraphics2D(sd, Color.white, Color.black, defaultFont);
399 }
400
401 /* A call to this method should be followed by a call to
402 * registerFontDirs(..)
403 */
404 protected String getPlatformFontPath(boolean noType1Font) {
405 if (fontPath == null) {
406 fontPath = FontManager.getFontPath(noType1Font);
407 }
408 return fontPath;
409 }
410
411 private String[] platformFontDirs;
412 /**
413 * Get all directories which contain installed fonts.
414 */
415 public String[] getPlatformFontDirs() {
416 if (platformFontDirs == null) {
417 String path = getPlatformFontPath(noType1Font);
418 StringTokenizer parser =
419 new StringTokenizer(path, File.pathSeparator);;
420 ArrayList<String> pathList = new ArrayList<String>();
421 try {
422 while (parser.hasMoreTokens()) {
423 pathList.add(parser.nextToken());
424 }
425 } catch (NoSuchElementException e) {
426 }
427 platformFontDirs = pathList.toArray(new String[0]);
428 }
429 return platformFontDirs;
430 }
431
432 /**
433 * Whether registerFontFile expects absolute or relative
434 * font file names.
435 */
436 protected boolean useAbsoluteFontFileNames() {
437 return true;
438 }
439
440 /**
441 * Returns file name for default font, either absolute
442 * or relative as needed by registerFontFile.
443 */
444 public String getDefaultFontFile() {
445 return defaultFontFileName;
446 }
447
448 /**
449 * Returns face name for default font, or null if
450 * no face names are used for CompositeFontDescriptors
451 * for this platform.
452 */
453 public String getDefaultFontFaceName() {
454 return defaultFontName;
455 }
456
457 public void loadFonts() {
458 if (discoveredAllFonts) {
459 return;
460 }
461 /* Use lock specific to the font system */
462 synchronized (lucidaFontName) {
463 if (debugFonts) {
464 Thread.dumpStack();
465 logger.info("SunGraphicsEnvironment.loadFonts() called");
466 }
467 FontManager.initialiseDeferredFonts();
468
469 java.security.AccessController.doPrivileged(
470 new java.security.PrivilegedAction() {
471 public Object run() {
472 if (fontPath == null) {
473 fontPath = getPlatformFontPath(noType1Font);
474 registerFontDirs(fontPath);
475 }
476 if (fontPath != null) {
477 // this will find all fonts including those already
478 // registered. But we have checks in place to prevent
479 // double registration.
480 if (!FontManager.gotFontsFromPlatform()) {
481 registerFontsOnPath(fontPath, false,
482 Font2D.UNKNOWN_RANK,
483 false, true);
484 loadedAllFontFiles = true;
485 }
486 }
487 FontManager.registerOtherFontFiles(registeredFontFiles);
488 discoveredAllFonts = true;
489 return null;
490 }
491 });
492 }
493 }
494
495
496 public void loadFontFiles() {
497 loadFonts();
498 if (loadedAllFontFiles) {
499 return;
500 }
501 /* Use lock specific to the font system */
502 synchronized (lucidaFontName) {
503 if (debugFonts) {
504 Thread.dumpStack();
505 logger.info("loadAllFontFiles() called");
506 }
507 java.security.AccessController.doPrivileged(
508 new java.security.PrivilegedAction() {
509 public Object run() {
510 if (fontPath == null) {
511 fontPath = getPlatformFontPath(noType1Font);
512 }
513 if (fontPath != null) {
514 // this will find all fonts including those already
515 // registered. But we have checks in place to prevent
516 // double registration.
517 registerFontsOnPath(fontPath, false,
518 Font2D.UNKNOWN_RANK,
519 false, true);
520 }
521 loadedAllFontFiles = true;
522 return null;
523 }
524 });
525 }
526 }
527
528 /*
529 * This is for use only within getAllFonts().
530 * Fonts listed in the fontconfig files for windows were all
531 * on the "deferred" initialisation list. They were registered
532 * either in the course of the application, or in the call to
533 * loadFonts() within getAllFonts(). The fontconfig file specifies
534 * the names of the fonts using the English names. If there's a
535 * different name in the execution locale, then the platform will
536 * report that, and we will construct the font with both names, and
537 * thereby enumerate it twice. This happens for Japanese fonts listed
538 * in the windows fontconfig, when run in the JA locale. The solution
539 * is to rely (in this case) on the platform's font->file mapping to
540 * determine that this name corresponds to a file we already registered.
541 * This works because
542 * - we know when we get here all deferred fonts are already initialised
543 * - when we register a font file, we register all fonts in it.
544 * - we know the fontconfig fonts are all in the windows registry
545 */
546 private boolean isNameForRegisteredFile(String fontName) {
547 String fileName = FontManager.getFileNameForFontName(fontName);
548 if (fileName == null) {
549 return false;
550 }
551 return registeredFontFiles.contains(fileName);
552 }
553
554 private Font[] allFonts;
555
556 /**
557 * Returns all fonts installed in this environment.
558 */
559 public Font[] getAllInstalledFonts() {
560 if (allFonts == null) {
561 loadFonts();
562 TreeMap fontMapNames = new TreeMap();
563 /* warning: the number of composite fonts could change dynamically
564 * if applications are allowed to create them. "allfonts" could
565 * then be stale.
566 */
567
568 Font2D[] allfonts = FontManager.getRegisteredFonts();
569 for (int i=0; i < allfonts.length; i++) {
570 if (!(allfonts[i] instanceof NativeFont)) {
571 fontMapNames.put(allfonts[i].getFontName(null),
572 allfonts[i]);
573 }
574 }
575
576 String[] platformNames = FontManager.getFontNamesFromPlatform();
577 if (platformNames != null) {
578 for (int i=0; i<platformNames.length; i++) {
579 if (!isNameForRegisteredFile(platformNames[i])) {
580 fontMapNames.put(platformNames[i], null);
581 }
582 }
583 }
584
585 String[] fontNames = null;
586 if (fontMapNames.size() > 0) {
587 fontNames = new String[fontMapNames.size()];
588 Object [] keyNames = fontMapNames.keySet().toArray();
589 for (int i=0; i < keyNames.length; i++) {
590 fontNames[i] = (String)keyNames[i];
591 }
592 }
593 Font[] fonts = new Font[fontNames.length];
594 for (int i=0; i < fontNames.length; i++) {
595 fonts[i] = new Font(fontNames[i], Font.PLAIN, 1);
596 Font2D f2d = (Font2D)fontMapNames.get(fontNames[i]);
597 if (f2d != null) {
598 FontManager.setFont2D(fonts[i], f2d.handle);
599 }
600 }
601 allFonts = fonts;
602 }
603
604 Font []copyFonts = new Font[allFonts.length];
605 System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length);
606 return copyFonts;
607 }
608
609 /**
610 * Returns all fonts available in this environment.
611 */
612 public Font[] getAllFonts() {
613 Font[] installedFonts = getAllInstalledFonts();
614 Font[] created = FontManager.getCreatedFonts();
615 if (created == null || created.length == 0) {
616 return installedFonts;
617 } else {
618 int newlen = installedFonts.length + created.length;
619 Font [] fonts = java.util.Arrays.copyOf(installedFonts, newlen);
620 System.arraycopy(created, 0, fonts,
621 installedFonts.length, created.length);
622 return fonts;
623 }
624 }
625
626 /**
627 * Default locale can be changed but we need to know the initial locale
628 * as that is what is used by native code. Changing Java default locale
629 * doesn't affect that.
630 * Returns the locale in use when using native code to communicate
631 * with platform APIs. On windows this is known as the "system" locale,
632 * and it is usually the same as the platform locale, but not always,
633 * so this method also checks an implementation property used only
634 * on windows and uses that if set.
635 */
636 private static Locale systemLocale = null;
637 public static Locale getSystemStartupLocale() {
638 if (systemLocale == null) {
639 systemLocale = (Locale)
640 java.security.AccessController.doPrivileged(
641 new java.security.PrivilegedAction() {
642 public Object run() {
643 /* On windows the system locale may be different than the
644 * user locale. This is an unsupported configuration, but
645 * in that case we want to return a dummy locale that will
646 * never cause a match in the usage of this API. This is
647 * important because Windows documents that the family
648 * names of fonts are enumerated using the language of
649 * the system locale. BY returning a dummy locale in that
650 * case we do not use the platform API which would not
651 * return us the names we want.
652 */
653 String fileEncoding = System.getProperty("file.encoding", "");
654 String sysEncoding = System.getProperty("sun.jnu.encoding");
655 if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) {
656 return Locale.ROOT;
657 }
658
659 String language = System.getProperty("user.language", "en");
660 String country = System.getProperty("user.country","");
661 String variant = System.getProperty("user.variant","");
662 return new Locale(language, country, variant);
663 }
664 });
665 }
666 return systemLocale;
667 }
668
669 /* Really we need only the JRE fonts family names, but there's little
670 * overhead in doing this the easy way by adding all the currently
671 * known fonts.
672 */
673 protected void getJREFontFamilyNames(TreeMap<String,String> familyNames,
674 Locale requestedLocale) {
675 FontManager.registerDeferredJREFonts(jreFontDirName);
676 Font2D[] physicalfonts = FontManager.getPhysicalFonts();
677 for (int i=0; i < physicalfonts.length; i++) {
678 if (!(physicalfonts[i] instanceof NativeFont)) {
679 String name =
680 physicalfonts[i].getFamilyName(requestedLocale);
681 familyNames.put(name.toLowerCase(requestedLocale), name);
682 }
683 }
684 }
685
686 private String[] allFamilies; // cache for default locale only
687 private Locale lastDefaultLocale;
688
689 public String[] getInstalledFontFamilyNames(Locale requestedLocale) {
690 if (requestedLocale == null) {
691 requestedLocale = Locale.getDefault();
692 }
693 if (allFamilies != null && lastDefaultLocale != null &&
694 requestedLocale.equals(lastDefaultLocale)) {
695 String[] copyFamilies = new String[allFamilies.length];
696 System.arraycopy(allFamilies, 0, copyFamilies,
697 0, allFamilies.length);
698 return copyFamilies;
699 }
700
701 TreeMap<String,String> familyNames = new TreeMap<String,String>();
702 // these names are always there and aren't localised
703 String str;
704 str = Font.SERIF; familyNames.put(str.toLowerCase(), str);
705 str = Font.SANS_SERIF; familyNames.put(str.toLowerCase(), str);
706 str = Font.MONOSPACED; familyNames.put(str.toLowerCase(), str);
707 str = Font.DIALOG; familyNames.put(str.toLowerCase(), str);
708 str = Font.DIALOG_INPUT; familyNames.put(str.toLowerCase(), str);
709
710 /* Platform APIs may be used to get the set of available family
711 * names for the current default locale so long as it is the same
712 * as the start-up system locale, rather than loading all fonts.
713 */
714 if (requestedLocale.equals(getSystemStartupLocale()) &&
715 FontManager.getFamilyNamesFromPlatform(familyNames,
716 requestedLocale)) {
717 /* Augment platform names with JRE font family names */
718 getJREFontFamilyNames(familyNames, requestedLocale);
719 } else {
720 loadFontFiles();
721 Font2D[] physicalfonts = FontManager.getPhysicalFonts();
722 for (int i=0; i < physicalfonts.length; i++) {
723 if (!(physicalfonts[i] instanceof NativeFont)) {
724 String name =
725 physicalfonts[i].getFamilyName(requestedLocale);
726 familyNames.put(name.toLowerCase(requestedLocale), name);
727 }
728 }
729 }
730
731 String[] retval = new String[familyNames.size()];
732 Object [] keyNames = familyNames.keySet().toArray();
733 for (int i=0; i < keyNames.length; i++) {
734 retval[i] = (String)familyNames.get(keyNames[i]);
735 }
736 if (requestedLocale.equals(Locale.getDefault())) {
737 lastDefaultLocale = requestedLocale;
738 allFamilies = new String[retval.length];
739 System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length);
740 }
741 return retval;
742 }
743
744 public String[] getAvailableFontFamilyNames(Locale requestedLocale) {
745 String[] installed = getInstalledFontFamilyNames(requestedLocale);
746 /* Use a new TreeMap as used in getInstalledFontFamilyNames
747 * and insert all the keys in lower case, so that the sort order
748 * is the same as the installed families. This preserves historical
749 * behaviour and inserts new families in the right place.
750 * It would have been marginally more efficient to directly obtain
751 * the tree map and just insert new entries, but not so much as
752 * to justify the extra internal interface.
753 */
754 TreeMap<String, String> map = FontManager.getCreatedFontFamilyNames();
755 if (map == null || map.size() == 0) {
756 return installed;
757 } else {
758 for (int i=0; i<installed.length; i++) {
759 map.put(installed[i].toLowerCase(requestedLocale),
760 installed[i]);
761 }
762 String[] retval = new String[map.size()];
763 Object [] keyNames = map.keySet().toArray();
764 for (int i=0; i < keyNames.length; i++) {
765 retval[i] = (String)map.get(keyNames[i]);
766 }
767 return retval;
768 }
769 }
770
771 public String[] getAvailableFontFamilyNames() {
772 return getAvailableFontFamilyNames(Locale.getDefault());
773 }
774
775 /**
776 * Returns a file name for the physical font represented by this platform
777 * font name. The default implementation tries to obtain the file name
778 * from the font configuration.
779 * Subclasses may override to provide information from other sources.
780 */
781 protected String getFileNameFromPlatformName(String platformFontName) {
782 return fontConfig.getFileNameFromPlatformName(platformFontName);
783 }
784
785 public static class TTFilter implements FilenameFilter {
786 public boolean accept(File dir,String name) {
787 /* all conveniently have the same suffix length */
788 int offset = name.length()-4;
789 if (offset <= 0) { /* must be at least A.ttf */
790 return false;
791 } else {
792 return(name.startsWith(".ttf", offset) ||
793 name.startsWith(".TTF", offset) ||
794 name.startsWith(".ttc", offset) ||
795 name.startsWith(".TTC", offset));
796 }
797 }
798 }
799
800 public static class T1Filter implements FilenameFilter {
801 public boolean accept(File dir,String name) {
802 if (noType1Font) {
803 return false;
804 }
805 /* all conveniently have the same suffix length */
806 int offset = name.length()-4;
807 if (offset <= 0) { /* must be at least A.pfa */
808 return false;
809 } else {
810 return(name.startsWith(".pfa", offset) ||
811 name.startsWith(".pfb", offset) ||
812 name.startsWith(".PFA", offset) ||
813 name.startsWith(".PFB", offset));
814 }
815 }
816 }
817
818 public static class TTorT1Filter implements FilenameFilter {
819 public boolean accept(File dir, String name) {
820
821 /* all conveniently have the same suffix length */
822 int offset = name.length()-4;
823 if (offset <= 0) { /* must be at least A.ttf or A.pfa */
824 return false;
825 } else {
826 boolean isTT =
827 name.startsWith(".ttf", offset) ||
828 name.startsWith(".TTF", offset) ||
829 name.startsWith(".ttc", offset) ||
830 name.startsWith(".TTC", offset);
831 if (isTT) {
832 return true;
833 } else if (noType1Font) {
834 return false;
835 } else {
836 return(name.startsWith(".pfa", offset) ||
837 name.startsWith(".pfb", offset) ||
838 name.startsWith(".PFA", offset) ||
839 name.startsWith(".PFB", offset));
840 }
841 }
842 }
843 }
844
845 /* No need to keep consing up new instances - reuse a singleton.
846 * The trade-off is that these objects don't get GC'd.
847 */
848 public static final TTFilter ttFilter = new TTFilter();
849 public static final T1Filter t1Filter = new T1Filter();
850
851 /* The majority of the register functions in this class are
852 * registering platform fonts in the JRE's font maps.
853 * The next one is opposite in function as it registers the JRE
854 * fonts as platform fonts. If subsequent to calling this
855 * your implementation enumerates platform fonts in a way that
856 * would return these fonts too you may get duplicates.
857 * This function is primarily used to install the JRE fonts
858 * so that the native platform can access them.
859 * It is intended to be overridden by platform subclasses
860 * Currently minimal use is made of this as generally
861 * Java 2D doesn't need the platform to be able to
862 * use its fonts and platforms which already have matching
863 * fonts registered (possibly even from other different JRE
864 * versions) generally can't be guaranteed to use the
865 * one registered by this JRE version in response to
866 * requests from this JRE.
867 */
868 protected void registerJREFontsWithPlatform(String pathName) {
869 return;
870 }
871
872 /* Called from FontManager - has Solaris specific implementation */
873 public void register1dot0Fonts() {
874 java.security.AccessController.doPrivileged(
875 new java.security.PrivilegedAction() {
876 public Object run() {
877 String type1Dir = "/usr/openwin/lib/X11/fonts/Type1";
878 registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK,
879 false, false);
880 return null;
881 }
882 });
883 }
884
885 protected void registerFontDirs(String pathName) {
886 return;
887 }
888
889 /* Called to register fall back fonts */
890 public void registerFontsInDir(String dirName) {
891 registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false);
892 }
893
894 private void registerFontsInDir(String dirName, boolean useJavaRasterizer,
895 int fontRank,
896 boolean defer, boolean resolveSymLinks) {
897 File pathFile = new File(dirName);
898 addDirFonts(dirName, pathFile, ttFilter,
899 FontManager.FONTFORMAT_TRUETYPE, useJavaRasterizer,
900 fontRank==Font2D.UNKNOWN_RANK ?
901 Font2D.TTF_RANK : fontRank,
902 defer, resolveSymLinks);
903 addDirFonts(dirName, pathFile, t1Filter,
904 FontManager.FONTFORMAT_TYPE1, useJavaRasterizer,
905 fontRank==Font2D.UNKNOWN_RANK ?
906 Font2D.TYPE1_RANK : fontRank,
907 defer, resolveSymLinks);
908 }
909
910 private void registerFontsOnPath(String pathName,
911 boolean useJavaRasterizer, int fontRank,
912 boolean defer, boolean resolveSymLinks) {
913
914 StringTokenizer parser = new StringTokenizer(pathName,
915 File.pathSeparator);
916 try {
917 while (parser.hasMoreTokens()) {
918 registerFontsInDir(parser.nextToken(),
919 useJavaRasterizer, fontRank,
920 defer, resolveSymLinks);
921 }
922 } catch (NoSuchElementException e) {
923 }
924 }
925
926 protected void registerFontFile(String fontFileName, String[] nativeNames,
927 int fontRank, boolean defer) {
928 // REMIND: case compare depends on platform
929 if (registeredFontFiles.contains(fontFileName)) {
930 return;
931 }
932 int fontFormat;
933 if (ttFilter.accept(null, fontFileName)) {
934 fontFormat = FontManager.FONTFORMAT_TRUETYPE;
935 } else if (t1Filter.accept(null, fontFileName)) {
936 fontFormat = FontManager.FONTFORMAT_TYPE1;
937 } else {
938 fontFormat = FontManager.FONTFORMAT_NATIVE;
939 }
940 registeredFontFiles.add(fontFileName);
941 if (defer) {
942 FontManager.registerDeferredFont(fontFileName,
943 fontFileName, nativeNames,
944 fontFormat, false, fontRank);
945 } else {
946 FontManager.registerFontFile(fontFileName, nativeNames,
947 fontFormat, false, fontRank);
948 }
949 }
950
951 protected void registerFontDir(String path) {
952 }
953
954 protected String[] getNativeNames(String fontFileName,
955 String platformName) {
956 return null;
957 }
958
959 /*
960 * helper function for registerFonts
961 */
962 private void addDirFonts(String dirName, File dirFile,
963 FilenameFilter filter,
964 int fontFormat, boolean useJavaRasterizer,
965 int fontRank,
966 boolean defer, boolean resolveSymLinks) {
967 String[] ls = dirFile.list(filter);
968 if (ls == null || ls.length == 0) {
969 return;
970 }
971 String[] fontNames = new String[ls.length];
972 String[][] nativeNames = new String[ls.length][];
973 int fontCount = 0;
974
975 for (int i=0; i < ls.length; i++ ) {
976 File theFile = new File(dirFile, ls[i]);
977 String fullName = null;
978 if (resolveSymLinks) {
979 try {
980 fullName = theFile.getCanonicalPath();
981 } catch (IOException e) {
982 }
983 }
984 if (fullName == null) {
985 fullName = dirName + File.separator + ls[i];
986 }
987
988 // REMIND: case compare depends on platform
989 if (registeredFontFiles.contains(fullName)) {
990 continue;
991 }
992
993 if (badFonts != null && badFonts.contains(fullName)) {
994 if (debugFonts) {
995 logger.warning("skip bad font " + fullName);
996 }
997 continue; // skip this font file.
998 }
999
1000 registeredFontFiles.add(fullName);
1001
1002 if (debugFonts && logger.isLoggable(Level.INFO)) {
1003 String message = "Registering font " + fullName;
1004 String[] natNames = getNativeNames(fullName, null);
1005 if (natNames == null) {
1006 message += " with no native name";
1007 } else {
1008 message += " with native name(s) " + natNames[0];
1009 for (int nn = 1; nn < natNames.length; nn++) {
1010 message += ", " + natNames[nn];
1011 }
1012 }
1013 logger.info(message);
1014 }
1015 fontNames[fontCount] = fullName;
1016 nativeNames[fontCount++] = getNativeNames(fullName, null);
1017 }
1018 FontManager.registerFonts(fontNames, nativeNames, fontCount,
1019 fontFormat, useJavaRasterizer, fontRank,
1020 defer);
1021 return;
1022 }
1023
1024 /*
1025 * A GE may verify whether a font file used in a fontconfiguration
1026 * exists. If it doesn't then either we may substitute the default
1027 * font, or perhaps elide it altogether from the composite font.
1028 * This makes some sense on windows where the font file is only
1029 * likely to be in one place. But on other OSes, eg Linux, the file
1030 * can move around depending. So there we probably don't want to assume
1031 * its missing and so won't add it to this list.
1032 * If this list - missingFontFiles - is non-null then the composite
1033 * font initialisation logic tests to see if a font file is in that
1034 * set.
1035 * Only one thread should be able to add to this set so we don't
1036 * synchronize.
1037 */
1038 protected void addToMissingFontFileList(String fileName) {
1039 if (missingFontFiles == null) {
1040 missingFontFiles = new HashSet<String>();
1041 }
1042 missingFontFiles.add(fileName);
1043 }
1044
1045 /**
1046 * Creates this environment's FontConfiguration.
1047 */
1048 protected abstract FontConfiguration createFontConfiguration();
1049
1050 public abstract FontConfiguration
1051 createFontConfiguration(boolean preferLocaleFonts,
1052 boolean preferPropFonts);
1053
1054 /*
1055 * This method asks the font configuration API for all platform names
1056 * used as components of composite/logical fonts and iterates over these
1057 * looking up their corresponding file name and registers these fonts.
1058 * It also ensures that the fonts are accessible via platform APIs.
1059 * The composites themselves are then registered.
1060 */
1061 private void
1062 initCompositeFonts(FontConfiguration fontConfig,
1063 ConcurrentHashMap<String, Font2D> altNameCache) {
1064
1065 int numCoreFonts = fontConfig.getNumberCoreFonts();
1066 String[] fcFonts = fontConfig.getPlatformFontNames();
1067 for (int f=0; f<fcFonts.length; f++) {
1068 String platformFontName = fcFonts[f];
1069 String fontFileName =
1070 getFileNameFromPlatformName(platformFontName);
1071 String[] nativeNames = null;
1072 if (fontFileName == null) {
1073 /* No file located, so register using the platform name,
1074 * i.e. as a native font.
1075 */
1076 fontFileName = platformFontName;
1077 } else {
1078 if (f < numCoreFonts) {
1079 /* If platform APIs also need to access the font, add it
1080 * to a set to be registered with the platform too.
1081 * This may be used to add the parent directory to the X11
1082 * font path if its not already there. See the docs for the
1083 * subclass implementation.
1084 * This is now mainly for the benefit of X11-based AWT
1085 * But for historical reasons, 2D initialisation code
1086 * makes these calls.
1087 * If the fontconfiguration file is properly set up
1088 * so that all fonts are mapped to files and all their
1089 * appropriate directories are specified, then this
1090 * method will be low cost as it will return after
1091 * a test that finds a null lookup map.
1092 */
1093 addFontToPlatformFontPath(platformFontName);
1094 }
1095 nativeNames = getNativeNames(fontFileName, platformFontName);
1096 }
1097 /* Uncomment these two lines to "generate" the XLFD->filename
1098 * mappings needed to speed start-up on Solaris.
1099 * Augment this with the appendedpathname and the mappings
1100 * for native (F3) fonts
1101 */
1102 //String platName = platformFontName.replaceAll(" ", "_");
1103 //System.out.println("filename."+platName+"="+fontFileName);
1104 registerFontFile(fontFileName, nativeNames,
1105 Font2D.FONT_CONFIG_RANK, true);
1106
1107
1108 }
1109 /* This registers accumulated paths from the calls to
1110 * addFontToPlatformFontPath(..) and any specified by
1111 * the font configuration. Rather than registering
1112 * the fonts it puts them in a place and form suitable for
1113 * the Toolkit to pick up and use if a toolkit is initialised,
1114 * and if it uses X11 fonts.
1115 */
1116 registerPlatformFontsUsedByFontConfiguration();
1117
1118 CompositeFontDescriptor[] compositeFontInfo
1119 = fontConfig.get2DCompositeFontInfo();
1120 for (int i = 0; i < compositeFontInfo.length; i++) {
1121 CompositeFontDescriptor descriptor = compositeFontInfo[i];
1122 String[] componentFileNames = descriptor.getComponentFileNames();
1123 String[] componentFaceNames = descriptor.getComponentFaceNames();
1124
1125 /* It would be better eventually to handle this in the
1126 * FontConfiguration code which should also remove duplicate slots
1127 */
1128 if (missingFontFiles != null) {
1129 for (int ii=0; ii<componentFileNames.length; ii++) {
1130 if (missingFontFiles.contains(componentFileNames[ii])) {
1131 componentFileNames[ii] = getDefaultFontFile();
1132 componentFaceNames[ii] = getDefaultFontFaceName();
1133 }
1134 }
1135 }
1136
1137 /* FontConfiguration needs to convey how many fonts it has added
1138 * as fallback component fonts which should not affect metrics.
1139 * The core component count will be the number of metrics slots.
1140 * This does not preclude other mechanisms for adding
1141 * fall back component fonts to the composite.
1142 */
1143 if (altNameCache != null) {
1144 FontManager.registerCompositeFont(
1145 descriptor.getFaceName(),
1146 componentFileNames, componentFaceNames,
1147 descriptor.getCoreComponentCount(),
1148 descriptor.getExclusionRanges(),
1149 descriptor.getExclusionRangeLimits(),
1150 true,
1151 altNameCache);
1152 } else {
1153 FontManager.registerCompositeFont(
1154 descriptor.getFaceName(),
1155 componentFileNames, componentFaceNames,
1156 descriptor.getCoreComponentCount(),
1157 descriptor.getExclusionRanges(),
1158 descriptor.getExclusionRangeLimits(),
1159 true);
1160 }
1161 if (debugFonts) {
1162 logger.info("registered " + descriptor.getFaceName());
1163 }
1164 }
1165 }
1166
1167 /**
1168 * Notifies graphics environment that the logical font configuration
1169 * uses the given platform font name. The graphics environment may
1170 * use this for platform specific initialization.
1171 */
1172 protected void addFontToPlatformFontPath(String platformFontName) {
1173 }
1174
1175 protected void registerPlatformFontsUsedByFontConfiguration() {
1176 }
1177
1178 /**
1179 * Determines whether the given font is a logical font.
1180 */
1181 public static boolean isLogicalFont(Font f) {
1182 return FontConfiguration.isLogicalFontFamilyName(f.getFamily());
1183 }
1184
1185 /**
1186 * Return the default font configuration.
1187 */
1188 public FontConfiguration getFontConfiguration() {
1189 return fontConfig;
1190 }
1191
1192 /**
1193 * Return the bounds of a GraphicsDevice, less its screen insets.
1194 * See also java.awt.GraphicsEnvironment.getUsableBounds();
1195 */
1196 public static Rectangle getUsableBounds(GraphicsDevice gd) {
1197 GraphicsConfiguration gc = gd.getDefaultConfiguration();
1198 Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
1199 Rectangle usableBounds = gc.getBounds();
1200
1201 usableBounds.x += insets.left;
1202 usableBounds.y += insets.top;
1203 usableBounds.width -= (insets.left + insets.right);
1204 usableBounds.height -= (insets.top + insets.bottom);
1205
1206 return usableBounds;
1207 }
1208
1209 /**
1210 * This method is provided for internal and exclusive use by Swing.
1211 * This method should no longer be called, instead directly call
1212 * FontManager.fontSupportsDefaultEncoding(Font).
1213 * This method will be removed once Swing is updated to no longer
1214 * call it.
1215 */
1216 public static boolean fontSupportsDefaultEncoding(Font font) {
1217 return FontManager.fontSupportsDefaultEncoding(font);
1218 }
1219
1220 public static void useAlternateFontforJALocales() {
1221 FontManager.useAlternateFontforJALocales();
1222 }
1223
1224 /*
1225 * This invocation is not in a privileged block because
1226 * all privileged operations (reading files and properties)
1227 * was conducted on the creation of the GE
1228 */
1229 public void
1230 createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache,
1231 boolean preferLocale,
1232 boolean preferProportional) {
1233
1234 FontConfiguration fontConfig =
1235 createFontConfiguration(preferLocale, preferProportional);
1236 initCompositeFonts(fontConfig, altNameCache);
1237 }
1238
1239 /* If (as we do on X11) need to set a platform font path,
1240 * then the needed path may be specified by the platform
1241 * specific FontConfiguration class & data file. Such platforms
1242 * (ie X11) need to override this method to retrieve this information
1243 * into suitable data structures.
1244 */
1245 protected void getPlatformFontPathFromFontConfig() {
1246 }
1247
1248 /**
1249 * From the DisplayChangedListener interface; called
1250 * when the display mode has been changed.
1251 */
1252 public void displayChanged() {
1253 // notify screens in device array to do display update stuff
1254 for (GraphicsDevice gd : getScreenDevices()) {
1255 if (gd instanceof DisplayChangedListener) {
1256 ((DisplayChangedListener) gd).displayChanged();
1257 }
1258 }
1259
1260 // notify SunDisplayChanger list (e.g. VolatileSurfaceManagers and
1261 // SurfaceDataProxies) about the display change event
1262 displayChanger.notifyListeners();
1263 }
1264
1265 /**
1266 * Part of the DisplayChangedListener interface:
1267 * propagate this event to listeners
1268 */
1269 public void paletteChanged() {
1270 displayChanger.notifyPaletteChanged();
1271 }
1272
1273 /*
1274 * ----DISPLAY CHANGE SUPPORT----
1275 */
1276
1277 protected SunDisplayChanger displayChanger = new SunDisplayChanger();
1278
1279 /**
1280 * Add a DisplayChangeListener to be notified when the display settings
1281 * are changed.
1282 */
1283 public void addDisplayChangedListener(DisplayChangedListener client) {
1284 displayChanger.add(client);
1285 }
1286
1287 /**
1288 * Remove a DisplayChangeListener from Win32GraphicsEnvironment
1289 */
1290 public void removeDisplayChangedListener(DisplayChangedListener client) {
1291 displayChanger.remove(client);
1292 }
1293
1294 /*
1295 * ----END DISPLAY CHANGE SUPPORT----
1296 */
1297}