|
1 /* |
|
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 |
|
26 package sun.awt; |
|
27 |
|
28 import java.awt.GraphicsDevice; |
|
29 import java.awt.Point; |
|
30 import java.awt.Rectangle; |
|
31 import java.io.BufferedReader; |
|
32 import java.io.File; |
|
33 import java.io.FileReader; |
|
34 import java.io.FileNotFoundException; |
|
35 import java.io.InputStream; |
|
36 import java.io.IOException; |
|
37 import java.io.StreamTokenizer; |
|
38 import java.net.InetAddress; |
|
39 import java.net.NetworkInterface; |
|
40 import java.net.SocketException; |
|
41 import java.net.UnknownHostException; |
|
42 |
|
43 import java.util.*; |
|
44 import java.util.logging.*; |
|
45 |
|
46 import sun.awt.motif.MFontConfiguration; |
|
47 import sun.font.Font2D; |
|
48 import sun.font.FontManager; |
|
49 import sun.font.NativeFont; |
|
50 import sun.java2d.SunGraphicsEnvironment; |
|
51 |
|
52 /** |
|
53 * This is an implementation of a GraphicsEnvironment object for the |
|
54 * default local GraphicsEnvironment used by the Java Runtime Environment |
|
55 * for X11 environments. |
|
56 * |
|
57 * @see GraphicsDevice |
|
58 * @see GraphicsConfiguration |
|
59 */ |
|
60 public class X11GraphicsEnvironment |
|
61 extends SunGraphicsEnvironment |
|
62 { |
|
63 private static final Logger log = Logger.getLogger("sun.awt.X11GraphicsEnvironment"); |
|
64 private static final Logger screenLog = Logger.getLogger("sun.awt.screen.X11GraphicsEnvironment"); |
|
65 |
|
66 private static Boolean xinerState; |
|
67 |
|
68 /* |
|
69 * This is the set of font directories needed to be on the X font path |
|
70 * to enable AWT heavyweights to find all of the font configuration fonts. |
|
71 * It is populated by : |
|
72 * - awtfontpath entries in the fontconfig.properties |
|
73 * - parent directories of "core" fonts used in the fontconfig.properties |
|
74 * - looking up font dirs in the xFontDirsMap where the key is a fontID |
|
75 * (cut down version of the XLFD read from the font configuration file). |
|
76 * This set is nulled out after use to free heap space. |
|
77 */ |
|
78 private static HashSet<String> fontConfigDirs = null; |
|
79 |
|
80 /* |
|
81 * fontNameMap is a map from a fontID (which is a substring of an XLFD like |
|
82 * "-monotype-arial-bold-r-normal-iso8859-7") |
|
83 * to font file path like |
|
84 * /usr/openwin/lib/locale/iso_8859_7/X11/fonts/TrueType/ArialBoldItalic.ttf |
|
85 * It's used in a couple of methods like |
|
86 * getFileNameFomPlatformName(..) to help locate the font file. |
|
87 * We use this substring of a full XLFD because the font configuration files |
|
88 * define the XLFDs in a way that's easier to make into a request. |
|
89 * E.g., the -0-0-0-0-p-0- reported by X is -*-%d-*-*-p-*- in the font |
|
90 * configuration files. We need to remove that part for comparisons. |
|
91 */ |
|
92 private static Map fontNameMap = new HashMap(); |
|
93 |
|
94 /* xFontDirsMap is also a map from a font ID to a font filepath. |
|
95 * The difference from fontNameMap is just that it does not have |
|
96 * resolved symbolic links. Normally this is not interesting except |
|
97 * that we need to know the directory in which a font was found to |
|
98 * add it to the X font server path, since although the files may |
|
99 * be linked, the fonts.dir is different and specific to the encoding |
|
100 * handled by that directory. This map is nulled out after use to free |
|
101 * heap space. If the optimal path is taken, such that all fonts in |
|
102 * font configuration files are referenced by filename, then the font |
|
103 * dir can be directly derived as its parent directory. |
|
104 * If a font is used by two XLFDs, each corresponding to a different |
|
105 * X11 font directory, then precautions must be taken to include both |
|
106 * directories. |
|
107 */ |
|
108 private static Map xFontDirsMap; |
|
109 |
|
110 /* |
|
111 * xlfdMap is a map from a platform path like |
|
112 * /usr/openwin/lib/locale/ja/X11/fonts/TT/HG-GothicB.ttf to an XLFD like |
|
113 * "-ricoh-hg gothic b-medium-r-normal--0-0-0-0-m-0-jisx0201.1976-0" |
|
114 * Because there may be multiple native names, because the font is used |
|
115 * to support multiple X encodings for example, the value of an entry in |
|
116 * this map is always a vector where we store all the native names. |
|
117 * For fonts which we don't understand the key isn't a pathname, its |
|
118 * the full XLFD string like :- |
|
119 * "-ricoh-hg gothic b-medium-r-normal--0-0-0-0-m-0-jisx0201.1976-0" |
|
120 */ |
|
121 private static Map xlfdMap = new HashMap(); |
|
122 |
|
123 /* |
|
124 * Used to eliminate redundant work. When a font directory is |
|
125 * registered it added to this list. Subsequent registrations for the |
|
126 * same directory can then be skipped by checking this Map. |
|
127 * Access to this map is not synchronised here since creation |
|
128 * of the singleton GE instance is already synchronised and that is |
|
129 * the only code path that accesses this map. |
|
130 */ |
|
131 private static HashMap registeredDirs = new HashMap(); |
|
132 |
|
133 /* Array of directories to be added to the X11 font path. |
|
134 * Used by static method called from Toolkits which use X11 fonts. |
|
135 * Specifically this means MToolkit |
|
136 */ |
|
137 private static String[] fontdirs = null; |
|
138 |
|
139 static { |
|
140 java.security.AccessController.doPrivileged( |
|
141 new java.security.PrivilegedAction() { |
|
142 public Object run() { |
|
143 System.loadLibrary("awt"); |
|
144 |
|
145 /* |
|
146 * Note: The MToolkit object depends on the static initializer |
|
147 * of X11GraphicsEnvironment to initialize the connection to |
|
148 * the X11 server. |
|
149 */ |
|
150 if (!isHeadless()) { |
|
151 // first check the OGL system property |
|
152 boolean glxRequested = false; |
|
153 String prop = System.getProperty("sun.java2d.opengl"); |
|
154 if (prop != null) { |
|
155 if (prop.equals("true") || prop.equals("t")) { |
|
156 glxRequested = true; |
|
157 } else if (prop.equals("True") || prop.equals("T")) { |
|
158 glxRequested = true; |
|
159 glxVerbose = true; |
|
160 } |
|
161 } |
|
162 |
|
163 // initialize the X11 display connection |
|
164 initDisplay(glxRequested); |
|
165 |
|
166 // only attempt to initialize GLX if it was requested |
|
167 if (glxRequested) { |
|
168 glxAvailable = initGLX(); |
|
169 if (glxVerbose && !glxAvailable) { |
|
170 System.out.println( |
|
171 "Could not enable OpenGL " + |
|
172 "pipeline (GLX 1.3 not available)"); |
|
173 } |
|
174 } |
|
175 } |
|
176 |
|
177 return null; |
|
178 } |
|
179 }); |
|
180 } |
|
181 |
|
182 private static boolean glxAvailable; |
|
183 private static boolean glxVerbose; |
|
184 |
|
185 private static native boolean initGLX(); |
|
186 |
|
187 public static boolean isGLXAvailable() { |
|
188 return glxAvailable; |
|
189 } |
|
190 |
|
191 public static boolean isGLXVerbose() { |
|
192 return glxVerbose; |
|
193 } |
|
194 |
|
195 /** |
|
196 * Checks if Shared Memory extension can be used. |
|
197 * Returns: |
|
198 * -1 if server doesn't support MITShm |
|
199 * 1 if server supports it and it can be used |
|
200 * 0 otherwise |
|
201 */ |
|
202 private static native int checkShmExt(); |
|
203 |
|
204 private static native String getDisplayString(); |
|
205 private static Boolean isDisplayLocal; |
|
206 |
|
207 /** |
|
208 * This should only be called from the static initializer, so no need for |
|
209 * the synchronized keyword. |
|
210 */ |
|
211 private static native void initDisplay(boolean glxRequested); |
|
212 |
|
213 public X11GraphicsEnvironment() { |
|
214 } |
|
215 |
|
216 protected native int getNumScreens(); |
|
217 |
|
218 protected GraphicsDevice makeScreenDevice(int screennum) { |
|
219 return new X11GraphicsDevice(screennum); |
|
220 } |
|
221 |
|
222 protected native int getDefaultScreenNum(); |
|
223 /** |
|
224 * Returns the default screen graphics device. |
|
225 */ |
|
226 public GraphicsDevice getDefaultScreenDevice() { |
|
227 return getScreenDevices()[getDefaultScreenNum()]; |
|
228 } |
|
229 |
|
230 public static boolean isDisplayLocal() { |
|
231 if (isDisplayLocal == null) { |
|
232 SunToolkit.awtLock(); |
|
233 try { |
|
234 if (isDisplayLocal == null) { |
|
235 isDisplayLocal = Boolean.valueOf(_isDisplayLocal()); |
|
236 } |
|
237 } finally { |
|
238 SunToolkit.awtUnlock(); |
|
239 } |
|
240 } |
|
241 return isDisplayLocal.booleanValue(); |
|
242 } |
|
243 |
|
244 private static boolean _isDisplayLocal() { |
|
245 if (isHeadless()) { |
|
246 return true; |
|
247 } |
|
248 |
|
249 String isRemote = (String)java.security.AccessController.doPrivileged( |
|
250 new sun.security.action.GetPropertyAction("sun.java2d.remote")); |
|
251 if (isRemote != null) { |
|
252 return isRemote.equals("false"); |
|
253 } |
|
254 |
|
255 int shm = checkShmExt(); |
|
256 if (shm != -1) { |
|
257 return (shm == 1); |
|
258 } |
|
259 |
|
260 // If XServer doesn't support ShMem extension, |
|
261 // try the other way |
|
262 |
|
263 String display = getDisplayString(); |
|
264 int ind = display.indexOf(':'); |
|
265 final String hostName = display.substring(0, ind); |
|
266 if (ind <= 0) { |
|
267 // ':0' case |
|
268 return true; |
|
269 } |
|
270 |
|
271 Boolean result = (Boolean)java.security.AccessController.doPrivileged( |
|
272 new java.security.PrivilegedAction() { |
|
273 public Object run() { |
|
274 InetAddress remAddr[] = null; |
|
275 Enumeration locals = null; |
|
276 Enumeration interfaces = null; |
|
277 try { |
|
278 interfaces = NetworkInterface.getNetworkInterfaces(); |
|
279 remAddr = InetAddress.getAllByName(hostName); |
|
280 if (remAddr == null) { |
|
281 return Boolean.FALSE; |
|
282 } |
|
283 } catch (UnknownHostException e) { |
|
284 System.err.println("Unknown host: " + hostName); |
|
285 return Boolean.FALSE; |
|
286 } catch (SocketException e1) { |
|
287 System.err.println(e1.getMessage()); |
|
288 return Boolean.FALSE; |
|
289 } |
|
290 |
|
291 for (; interfaces.hasMoreElements();) { |
|
292 locals = ((NetworkInterface)interfaces.nextElement()).getInetAddresses(); |
|
293 for (; locals.hasMoreElements();) { |
|
294 for (int i = 0; i < remAddr.length; i++) { |
|
295 if (locals.nextElement().equals(remAddr[i])) { |
|
296 return Boolean.TRUE; |
|
297 } |
|
298 } |
|
299 } |
|
300 } |
|
301 return Boolean.FALSE; |
|
302 }}); |
|
303 return result.booleanValue(); |
|
304 } |
|
305 |
|
306 /* These maps are used on Linux where we reference the Lucida oblique |
|
307 * fonts in fontconfig files even though they aren't in the standard |
|
308 * font directory. This explicitly remaps the XLFDs for these to the |
|
309 * correct base font. This is needed to prevent composite fonts from |
|
310 * defaulting to the Lucida Sans which is a bad substitute for the |
|
311 * monospaced Lucida Sans Typewriter. Also these maps prevent the |
|
312 * JRE from doing wasted work at start up. |
|
313 */ |
|
314 HashMap<String, String> oblmap = null; |
|
315 |
|
316 private String getObliqueLucidaFontID(String fontID) { |
|
317 if (fontID.startsWith("-lucidasans-medium-i-normal") || |
|
318 fontID.startsWith("-lucidasans-bold-i-normal") || |
|
319 fontID.startsWith("-lucidatypewriter-medium-i-normal") || |
|
320 fontID.startsWith("-lucidatypewriter-bold-i-normal")) { |
|
321 return fontID.substring(0, fontID.indexOf("-i-")); |
|
322 } else { |
|
323 return null; |
|
324 } |
|
325 } |
|
326 |
|
327 private void initObliqueLucidaFontMap() { |
|
328 oblmap = new HashMap<String, String>(); |
|
329 oblmap.put("-lucidasans-medium", |
|
330 jreLibDirName+"/fonts/LucidaSansRegular.ttf"); |
|
331 oblmap.put("-lucidasans-bold", |
|
332 jreLibDirName+"/fonts/LucidaSansDemiBold.ttf"); |
|
333 oblmap.put("-lucidatypewriter-medium", |
|
334 jreLibDirName+"/fonts/LucidaTypewriterRegular.ttf"); |
|
335 oblmap.put("-lucidatypewriter-bold", |
|
336 jreLibDirName+"/fonts/LucidaTypewriterBold.ttf"); |
|
337 } |
|
338 |
|
339 /** |
|
340 * Takes family name property in the following format: |
|
341 * "-linotype-helvetica-medium-r-normal-sans-*-%d-*-*-p-*-iso8859-1" |
|
342 * and returns the name of the corresponding physical font. |
|
343 * This code is used to resolve font configuration fonts, and expects |
|
344 * only to get called for these fonts. |
|
345 */ |
|
346 public String getFileNameFromPlatformName(String platName) { |
|
347 String fileName = null; |
|
348 String fontID = specificFontIDForName(platName); |
|
349 |
|
350 /* If the font filename has been explicitly assigned in the |
|
351 * font configuration file, use it. This avoids accessing |
|
352 * the wrong fonts on Linux, where different fonts (some |
|
353 * of which may not be usable by 2D) may share the same |
|
354 * specific font ID. It may also speed up the lookup. |
|
355 */ |
|
356 fileName = super.getFileNameFromPlatformName(platName); |
|
357 if (fileName != null) { |
|
358 if (isHeadless() && fileName.startsWith("-")) { |
|
359 /* if it's headless, no xlfd should be used */ |
|
360 return null; |
|
361 } |
|
362 if (fileName.startsWith("/")) { |
|
363 /* If a path is assigned in the font configuration file, |
|
364 * it is required that the config file also specify using the |
|
365 * new awtfontpath key the X11 font directories |
|
366 * which must be added to the X11 font path to support |
|
367 * AWT access to that font. For that reason we no longer |
|
368 * have code here to add the parent directory to the list |
|
369 * of font config dirs, since the parent directory may not |
|
370 * be sufficient if fonts are symbolically linked to a |
|
371 * different directory. |
|
372 * |
|
373 * Add this XLFD (platform name) to the list of known |
|
374 * ones for this file. |
|
375 */ |
|
376 Vector xVal = (Vector) xlfdMap.get(fileName); |
|
377 if (xVal == null) { |
|
378 /* Try to be robust on Linux distros which move fonts |
|
379 * around by verifying that the fileName represents a |
|
380 * file that exists. If it doesn't, set it to null |
|
381 * to trigger a search. |
|
382 */ |
|
383 if (getFontConfiguration().needToSearchForFile(fileName)) { |
|
384 fileName = null; |
|
385 } |
|
386 if (fileName != null) { |
|
387 xVal = new Vector(); |
|
388 xVal.add(platName); |
|
389 xlfdMap.put(fileName, xVal); |
|
390 } |
|
391 } else { |
|
392 if (!xVal.contains(platName)) { |
|
393 xVal.add(platName); |
|
394 } |
|
395 } |
|
396 } |
|
397 if (fileName != null) { |
|
398 fontNameMap.put(fontID, fileName); |
|
399 return fileName; |
|
400 } |
|
401 } |
|
402 |
|
403 if (fontID != null) { |
|
404 fileName = (String)fontNameMap.get(fontID); |
|
405 /* On Linux check for the Lucida Oblique fonts */ |
|
406 if (fileName == null && isLinux && !isOpenJDK()) { |
|
407 if (oblmap == null) { |
|
408 initObliqueLucidaFontMap(); |
|
409 } |
|
410 String oblkey = getObliqueLucidaFontID(fontID); |
|
411 if (oblkey != null) { |
|
412 fileName = oblmap.get(oblkey); |
|
413 } |
|
414 } |
|
415 if (fontPath == null && |
|
416 (fileName == null || !fileName.startsWith("/"))) { |
|
417 if (debugFonts) { |
|
418 logger.warning("** Registering all font paths because " + |
|
419 "can't find file for " + platName); |
|
420 } |
|
421 fontPath = getPlatformFontPath(noType1Font); |
|
422 registerFontDirs(fontPath); |
|
423 if (debugFonts) { |
|
424 logger.warning("** Finished registering all font paths"); |
|
425 } |
|
426 fileName = (String)fontNameMap.get(fontID); |
|
427 } |
|
428 if (fileName == null && !isHeadless()) { |
|
429 /* Query X11 directly to see if this font is available |
|
430 * as a native font. |
|
431 */ |
|
432 fileName = getX11FontName(platName); |
|
433 } |
|
434 if (fileName == null) { |
|
435 fontID = switchFontIDForName(platName); |
|
436 fileName = (String)fontNameMap.get(fontID); |
|
437 } |
|
438 if (fileName != null) { |
|
439 fontNameMap.put(fontID, fileName); |
|
440 } |
|
441 } |
|
442 return fileName; |
|
443 } |
|
444 |
|
445 private static String getX11FontName(String platName) { |
|
446 String xlfd = platName.replaceAll("%d", "*"); |
|
447 if (NativeFont.fontExists(xlfd)) { |
|
448 return xlfd; |
|
449 } else { |
|
450 return null; |
|
451 } |
|
452 } |
|
453 |
|
454 /** |
|
455 * Returns the face name for the given XLFD. |
|
456 */ |
|
457 public String getFileNameFromXLFD(String name) { |
|
458 String fileName = null; |
|
459 String fontID = specificFontIDForName(name); |
|
460 if (fontID != null) { |
|
461 fileName = (String)fontNameMap.get(fontID); |
|
462 if (fileName == null) { |
|
463 fontID = switchFontIDForName(name); |
|
464 fileName = (String)fontNameMap.get(fontID); |
|
465 } |
|
466 if (fileName == null) { |
|
467 fileName = getDefaultFontFile(); |
|
468 } |
|
469 } |
|
470 return fileName; |
|
471 } |
|
472 |
|
473 // constants identifying XLFD and font ID fields |
|
474 private static final int FOUNDRY_FIELD = 1; |
|
475 private static final int FAMILY_NAME_FIELD = 2; |
|
476 private static final int WEIGHT_NAME_FIELD = 3; |
|
477 private static final int SLANT_FIELD = 4; |
|
478 private static final int SETWIDTH_NAME_FIELD = 5; |
|
479 private static final int ADD_STYLE_NAME_FIELD = 6; |
|
480 private static final int PIXEL_SIZE_FIELD = 7; |
|
481 private static final int POINT_SIZE_FIELD = 8; |
|
482 private static final int RESOLUTION_X_FIELD = 9; |
|
483 private static final int RESOLUTION_Y_FIELD = 10; |
|
484 private static final int SPACING_FIELD = 11; |
|
485 private static final int AVERAGE_WIDTH_FIELD = 12; |
|
486 private static final int CHARSET_REGISTRY_FIELD = 13; |
|
487 private static final int CHARSET_ENCODING_FIELD = 14; |
|
488 |
|
489 private String switchFontIDForName(String name) { |
|
490 |
|
491 int[] hPos = new int[14]; |
|
492 int hyphenCnt = 1; |
|
493 int pos = 1; |
|
494 |
|
495 while (pos != -1 && hyphenCnt < 14) { |
|
496 pos = name.indexOf('-', pos); |
|
497 if (pos != -1) { |
|
498 hPos[hyphenCnt++] = pos; |
|
499 pos++; |
|
500 } |
|
501 } |
|
502 |
|
503 if (hyphenCnt != 14) { |
|
504 if (debugFonts) { |
|
505 logger.severe("Font Configuration Font ID is malformed:" + name); |
|
506 } |
|
507 return name; // what else can we do? |
|
508 } |
|
509 |
|
510 String slant = name.substring(hPos[SLANT_FIELD-1]+1, |
|
511 hPos[SLANT_FIELD]); |
|
512 String family = name.substring(hPos[FAMILY_NAME_FIELD-1]+1, |
|
513 hPos[FAMILY_NAME_FIELD]); |
|
514 String registry = name.substring(hPos[CHARSET_REGISTRY_FIELD-1]+1, |
|
515 hPos[CHARSET_REGISTRY_FIELD]); |
|
516 String encoding = name.substring(hPos[CHARSET_ENCODING_FIELD-1]+1); |
|
517 |
|
518 if (slant.equals("i")) { |
|
519 slant = "o"; |
|
520 } else if (slant.equals("o")) { |
|
521 slant = "i"; |
|
522 } |
|
523 // workaround for #4471000 |
|
524 if (family.equals("itc zapfdingbats") |
|
525 && registry.equals("sun") |
|
526 && encoding.equals("fontspecific")){ |
|
527 registry = "adobe"; |
|
528 } |
|
529 StringBuffer sb = |
|
530 new StringBuffer(name.substring(hPos[FAMILY_NAME_FIELD-1], |
|
531 hPos[SLANT_FIELD-1]+1)); |
|
532 sb.append(slant); |
|
533 sb.append(name.substring(hPos[SLANT_FIELD], |
|
534 hPos[SETWIDTH_NAME_FIELD]+1)); |
|
535 sb.append(registry); |
|
536 sb.append(name.substring(hPos[CHARSET_ENCODING_FIELD-1])); |
|
537 String retval = sb.toString().toLowerCase (Locale.ENGLISH); |
|
538 return retval; |
|
539 } |
|
540 |
|
541 |
|
542 private String specificFontIDForName(String name) { |
|
543 |
|
544 int[] hPos = new int[14]; |
|
545 int hyphenCnt = 1; |
|
546 int pos = 1; |
|
547 |
|
548 while (pos != -1 && hyphenCnt < 14) { |
|
549 pos = name.indexOf('-', pos); |
|
550 if (pos != -1) { |
|
551 hPos[hyphenCnt++] = pos; |
|
552 pos++; |
|
553 } |
|
554 } |
|
555 |
|
556 if (hyphenCnt != 14) { |
|
557 if (debugFonts) { |
|
558 logger.severe("Font Configuration Font ID is malformed:" + name); |
|
559 } |
|
560 return name; // what else can we do? |
|
561 } |
|
562 |
|
563 StringBuffer sb = |
|
564 new StringBuffer(name.substring(hPos[FAMILY_NAME_FIELD-1], |
|
565 hPos[SETWIDTH_NAME_FIELD])); |
|
566 sb.append(name.substring(hPos[CHARSET_REGISTRY_FIELD-1])); |
|
567 String retval = sb.toString().toLowerCase (Locale.ENGLISH); |
|
568 return retval; |
|
569 } |
|
570 |
|
571 protected String[] getNativeNames(String fontFileName, |
|
572 String platformName) { |
|
573 Vector nativeNames; |
|
574 if ((nativeNames=(Vector)xlfdMap.get(fontFileName))==null) { |
|
575 if (platformName == null) { |
|
576 return null; |
|
577 } else { |
|
578 /* back-stop so that at least the name used in the |
|
579 * font configuration file is known as a native name |
|
580 */ |
|
581 String []natNames = new String[1]; |
|
582 natNames[0] = platformName; |
|
583 return natNames; |
|
584 } |
|
585 } else { |
|
586 int len = nativeNames.size(); |
|
587 return (String[])nativeNames.toArray(new String[len]); |
|
588 } |
|
589 } |
|
590 |
|
591 |
|
592 // An X font spec (xlfd) includes an encoding. The same TrueType font file |
|
593 // may be referenced from different X font directories in font.dir files |
|
594 // to support use in multiple encodings by X apps. |
|
595 // So for the purposes of font configuration logical fonts where AWT |
|
596 // heavyweights need to access the font via X APIs we need to ensure that |
|
597 // the directory for precisely the encodings needed by this are added to |
|
598 // the x font path. This requires that we note the platform names |
|
599 // specified in font configuration files and use that to identify the |
|
600 // X font directory that contains a font.dir file for that platform name |
|
601 // and add it to the X font path (if display is local) |
|
602 // Here we make use of an already built map of xlfds to font locations |
|
603 // to add the font location to the set of those required to build the |
|
604 // x font path needed by AWT. |
|
605 // These are added to the x font path later. |
|
606 // All this is necessary because on Solaris the font.dir directories |
|
607 // may contain not real font files, but symbolic links to the actual |
|
608 // location but that location is not suitable for the x font path, since |
|
609 // it probably doesn't have a font.dir at all and certainly not one |
|
610 // with the required encodings |
|
611 // If the fontconfiguration file is properly set up so that all fonts |
|
612 // are mapped to files then we will never trigger initialising |
|
613 // xFontDirsMap (it will be null). In this case the awtfontpath entries |
|
614 // must specify all the X11 directories needed by AWT. |
|
615 protected void addFontToPlatformFontPath(String platformName) { |
|
616 if (xFontDirsMap != null) { |
|
617 String fontID = specificFontIDForName(platformName); |
|
618 String dirName = (String)xFontDirsMap.get(fontID); |
|
619 if (dirName != null) { |
|
620 fontConfigDirs.add(dirName); |
|
621 } |
|
622 } |
|
623 return; |
|
624 } |
|
625 |
|
626 protected void getPlatformFontPathFromFontConfig() { |
|
627 if (fontConfigDirs == null) { |
|
628 fontConfigDirs = getFontConfiguration().getAWTFontPathSet(); |
|
629 if (debugFonts && fontConfigDirs != null) { |
|
630 String[] names = fontConfigDirs.toArray(new String[0]); |
|
631 for (int i=0;i<names.length;i++) { |
|
632 logger.info("awtfontpath : " + names[i]); |
|
633 } |
|
634 } |
|
635 } |
|
636 } |
|
637 |
|
638 protected void registerPlatformFontsUsedByFontConfiguration() { |
|
639 if (fontConfigDirs == null) { |
|
640 return; |
|
641 } |
|
642 if (isLinux) { |
|
643 fontConfigDirs.add(jreLibDirName+File.separator+"oblique-fonts"); |
|
644 } |
|
645 fontdirs = (String[])fontConfigDirs.toArray(new String[0]); |
|
646 } |
|
647 |
|
648 /* Called by MToolkit to set the X11 font path */ |
|
649 public static void setNativeFontPath() { |
|
650 if (fontdirs == null) { |
|
651 return; |
|
652 } |
|
653 |
|
654 // need to register these individually rather than by one call |
|
655 // to ensure that one bad directory doesn't cause all to be rejected |
|
656 for (int i=0; i<fontdirs.length; i++) { |
|
657 if (debugFonts) { |
|
658 logger.info("Add " + fontdirs[i] + " to X11 fontpath"); |
|
659 } |
|
660 FontManager.setNativeFontPath(fontdirs[i]); |
|
661 } |
|
662 } |
|
663 |
|
664 /* Register just the paths, (it doesn't register the fonts). |
|
665 * If a font configuration file has specified a baseFontPath |
|
666 * fontPath is just those directories, unless on usage we |
|
667 * find it doesn't contain what we need for the logical fonts. |
|
668 * Otherwise, we register all the paths on Solaris, because |
|
669 * the fontPath we have here is the complete one from |
|
670 * parsing /var/sadm/install/contents, not just |
|
671 * what's on the X font path (may be this should be |
|
672 * changed). |
|
673 * But for now what it means is that if we didn't do |
|
674 * this then if the font weren't listed anywhere on the |
|
675 * less complete font path we'd trigger loadFonts which |
|
676 * actually registers the fonts. This may actually be |
|
677 * the right thing tho' since that would also set up |
|
678 * the X font path without which we wouldn't be able to |
|
679 * display some "native" fonts. |
|
680 * So something to revisit is that probably fontPath |
|
681 * here ought to be only the X font path + jre font dir. |
|
682 * loadFonts should have a separate native call to |
|
683 * get the rest of the platform font path. |
|
684 * |
|
685 * Registering the directories can now be avoided in the |
|
686 * font configuration initialisation when filename entries |
|
687 * exist in the font configuration file for all fonts. |
|
688 * (Perhaps a little confusingly a filename entry is |
|
689 * actually keyed using the XLFD used in the font entries, |
|
690 * and it maps *to* a real filename). |
|
691 * In the event any are missing, registration of all |
|
692 * directories will be invoked to find the real files. |
|
693 * |
|
694 * But registering the directory performed other |
|
695 * functions such as filling in the map of all native names |
|
696 * for the font. So when this method isn't invoked, they still |
|
697 * must be found. This is mitigated by getNativeNames now |
|
698 * being able to return at least the platform name, but mostly |
|
699 * by ensuring that when a filename key is found, that |
|
700 * xlfd key is stored as one of the set of platform names |
|
701 * for the font. Its a set because typical font configuration |
|
702 * files reference the same CJK font files using multiple |
|
703 * X11 encodings. For the code that adds this to the map |
|
704 * see X11GE.getFileNameFromPlatformName(..) |
|
705 * If you don't get all of these then some code points may |
|
706 * not use the Xserver, and will not get the PCF bitmaps |
|
707 * that are available for some point sizes. |
|
708 * So, in the event that there is such a problem, |
|
709 * unconditionally making this call may be necessary, at |
|
710 * some cost to JRE start-up |
|
711 */ |
|
712 protected void registerFontDirs(String pathName) { |
|
713 |
|
714 StringTokenizer parser = new StringTokenizer(pathName, |
|
715 File.pathSeparator); |
|
716 try { |
|
717 while (parser.hasMoreTokens()) { |
|
718 String dirPath = parser.nextToken(); |
|
719 if (dirPath != null && !registeredDirs.containsKey(dirPath)) { |
|
720 registeredDirs.put(dirPath, null); |
|
721 registerFontDir(dirPath); |
|
722 } |
|
723 } |
|
724 } catch (NoSuchElementException e) { |
|
725 } |
|
726 } |
|
727 |
|
728 /* NOTE: this method needs to be executed in a privileged context. |
|
729 * The superclass constructor which is the primary caller of |
|
730 * this method executes entirely in such a context. Additionally |
|
731 * the loadFonts() method does too. So all should be well. |
|
732 |
|
733 */ |
|
734 protected void registerFontDir(String path) { |
|
735 /* fonts.dir file format looks like :- |
|
736 * 47 |
|
737 * Arial.ttf -monotype-arial-regular-r-normal--0-0-0-0-p-0-iso8859-1 |
|
738 * Arial-Bold.ttf -monotype-arial-bold-r-normal--0-0-0-0-p-0-iso8859-1 |
|
739 * ... |
|
740 */ |
|
741 if (debugFonts) { |
|
742 logger.info("ParseFontDir " + path); |
|
743 } |
|
744 File fontsDotDir = new File(path + File.separator + "fonts.dir"); |
|
745 FileReader fr = null; |
|
746 try { |
|
747 if (fontsDotDir.canRead()) { |
|
748 fr = new FileReader(fontsDotDir); |
|
749 BufferedReader br = new BufferedReader(fr, 8192); |
|
750 StreamTokenizer st = new StreamTokenizer(br); |
|
751 st.eolIsSignificant(true); |
|
752 int ttype = st.nextToken(); |
|
753 if (ttype == StreamTokenizer.TT_NUMBER) { |
|
754 int numEntries = (int)st.nval; |
|
755 ttype = st.nextToken(); |
|
756 if (ttype == StreamTokenizer.TT_EOL) { |
|
757 st.resetSyntax(); |
|
758 st.wordChars(32, 127); |
|
759 st.wordChars(128 + 32, 255); |
|
760 st.whitespaceChars(0, 31); |
|
761 |
|
762 for (int i=0; i < numEntries; i++) { |
|
763 ttype = st.nextToken(); |
|
764 if (ttype == StreamTokenizer.TT_EOF) { |
|
765 break; |
|
766 } |
|
767 if (ttype != StreamTokenizer.TT_WORD) { |
|
768 break; |
|
769 } |
|
770 int breakPos = st.sval.indexOf(' '); |
|
771 if (breakPos <= 0) { |
|
772 /* On TurboLinux 8.0 a fonts.dir file had |
|
773 * a line with integer value "24" which |
|
774 * appeared to be the number of remaining |
|
775 * entries in the file. This didn't add to |
|
776 * the value on the first line of the file. |
|
777 * Seemed like XFree86 didn't like this line |
|
778 * much either. It failed to parse the file. |
|
779 * Ignore lines like this completely, and |
|
780 * don't let them count as an entry. |
|
781 */ |
|
782 numEntries++; |
|
783 ttype = st.nextToken(); |
|
784 if (ttype != StreamTokenizer.TT_EOL) { |
|
785 break; |
|
786 } |
|
787 |
|
788 continue; |
|
789 } |
|
790 if (st.sval.charAt(0) == '!') { |
|
791 /* TurboLinux 8.0 comment line: ignore. |
|
792 * can't use st.commentChar('!') to just |
|
793 * skip because this line mustn't count |
|
794 * against numEntries. |
|
795 */ |
|
796 numEntries++; |
|
797 ttype = st.nextToken(); |
|
798 if (ttype != StreamTokenizer.TT_EOL) { |
|
799 break; |
|
800 } |
|
801 continue; |
|
802 } |
|
803 String fileName = st.sval.substring(0, breakPos); |
|
804 /* TurboLinux 8.0 uses some additional syntax to |
|
805 * indicate algorithmic styling values. |
|
806 * Ignore ':' separated files at the beginning |
|
807 * of the fileName |
|
808 */ |
|
809 int lastColon = fileName.lastIndexOf(':'); |
|
810 if (lastColon > 0) { |
|
811 if (lastColon+1 >= fileName.length()) { |
|
812 continue; |
|
813 } |
|
814 fileName = fileName.substring(lastColon+1); |
|
815 } |
|
816 String fontPart = st.sval.substring(breakPos+1); |
|
817 String fontID = specificFontIDForName(fontPart); |
|
818 String sVal = (String) fontNameMap.get(fontID); |
|
819 |
|
820 if (debugFonts) { |
|
821 logger.info("file=" + fileName + |
|
822 " xlfd=" + fontPart); |
|
823 logger.info("fontID=" + fontID + |
|
824 " sVal=" + sVal); |
|
825 } |
|
826 String fullPath = null; |
|
827 try { |
|
828 File file = new File(path,fileName); |
|
829 /* we may have a resolved symbolic link |
|
830 * this becomes important for an xlfd we |
|
831 * still need to know the location it was |
|
832 * found to update the X server font path |
|
833 * for use by AWT heavyweights - and when 2D |
|
834 * wants to use the native rasteriser. |
|
835 */ |
|
836 if (xFontDirsMap == null) { |
|
837 xFontDirsMap = new HashMap(); |
|
838 } |
|
839 xFontDirsMap.put(fontID, path); |
|
840 fullPath = file.getCanonicalPath(); |
|
841 } catch (IOException e) { |
|
842 fullPath = path + File.separator + fileName; |
|
843 } |
|
844 Vector xVal = (Vector) xlfdMap.get(fullPath); |
|
845 if (debugFonts) { |
|
846 logger.info("fullPath=" + fullPath + |
|
847 " xVal=" + xVal); |
|
848 } |
|
849 if ((xVal == null || !xVal.contains(fontPart)) && |
|
850 (sVal == null) || !sVal.startsWith("/")) { |
|
851 if (debugFonts) { |
|
852 logger.info("Map fontID:"+fontID + |
|
853 "to file:" + fullPath); |
|
854 } |
|
855 fontNameMap.put(fontID, fullPath); |
|
856 if (xVal == null) { |
|
857 xVal = new Vector(); |
|
858 xlfdMap.put (fullPath, xVal); |
|
859 } |
|
860 xVal.add(fontPart); |
|
861 } |
|
862 |
|
863 ttype = st.nextToken(); |
|
864 if (ttype != StreamTokenizer.TT_EOL) { |
|
865 break; |
|
866 } |
|
867 } |
|
868 } |
|
869 } |
|
870 fr.close(); |
|
871 } |
|
872 } catch (IOException ioe1) { |
|
873 } finally { |
|
874 if (fr != null) { |
|
875 try { |
|
876 fr.close(); |
|
877 } catch (IOException ioe2) { |
|
878 } |
|
879 } |
|
880 } |
|
881 } |
|
882 |
|
883 @Override |
|
884 public void loadFonts() { |
|
885 super.loadFonts(); |
|
886 /* These maps are greatly expanded during a loadFonts but |
|
887 * can be reset to their initial state afterwards. |
|
888 * Since preferLocaleFonts() and preferProportionalFonts() will |
|
889 * trigger a partial repopulating from the FontConfiguration |
|
890 * it has to be the inital (empty) state for the latter two, not |
|
891 * simply nulling out. |
|
892 * xFontDirsMap is a special case in that the implementation |
|
893 * will typically not ever need to initialise it so it can be null. |
|
894 */ |
|
895 xFontDirsMap = null; |
|
896 xlfdMap = new HashMap(1); |
|
897 fontNameMap = new HashMap(1); |
|
898 } |
|
899 |
|
900 // Implements SunGraphicsEnvironment.createFontConfiguration. |
|
901 protected FontConfiguration createFontConfiguration() { |
|
902 return new MFontConfiguration(this); |
|
903 } |
|
904 public FontConfiguration |
|
905 createFontConfiguration(boolean preferLocaleFonts, |
|
906 boolean preferPropFonts) { |
|
907 |
|
908 return new MFontConfiguration(this, |
|
909 preferLocaleFonts, preferPropFonts); |
|
910 } |
|
911 |
|
912 /** |
|
913 * Returns face name for default font, or null if |
|
914 * no face names are used for CompositeFontDescriptors |
|
915 * for this platform. |
|
916 */ |
|
917 public String getDefaultFontFaceName() { |
|
918 return null; |
|
919 } |
|
920 |
|
921 private static native boolean pRunningXinerama(); |
|
922 private static native Point getXineramaCenterPoint(); |
|
923 |
|
924 /** |
|
925 * Override for Xinerama case: call new Solaris API for getting the correct |
|
926 * centering point from the windowing system. |
|
927 */ |
|
928 public Point getCenterPoint() { |
|
929 if (runningXinerama()) { |
|
930 Point p = getXineramaCenterPoint(); |
|
931 if (p != null) { |
|
932 return p; |
|
933 } |
|
934 } |
|
935 return super.getCenterPoint(); |
|
936 } |
|
937 |
|
938 /** |
|
939 * Override for Xinerama case |
|
940 */ |
|
941 public Rectangle getMaximumWindowBounds() { |
|
942 if (runningXinerama()) { |
|
943 return getXineramaWindowBounds(); |
|
944 } else { |
|
945 return super.getMaximumWindowBounds(); |
|
946 } |
|
947 } |
|
948 |
|
949 public boolean runningXinerama() { |
|
950 if (xinerState == null) { |
|
951 // pRunningXinerama() simply returns a global boolean variable, |
|
952 // so there is no need to synchronize here |
|
953 xinerState = Boolean.valueOf(pRunningXinerama()); |
|
954 if (screenLog.isLoggable(Level.FINER)) { |
|
955 screenLog.log(Level.FINER, "Running Xinerama: " + xinerState); |
|
956 } |
|
957 } |
|
958 return xinerState.booleanValue(); |
|
959 } |
|
960 |
|
961 /** |
|
962 * Return the bounds for a centered Window on a system running in Xinerama |
|
963 * mode. |
|
964 * |
|
965 * Calculations are based on the assumption of a perfectly rectangular |
|
966 * display area (display edges line up with one another, and displays |
|
967 * have consistent width and/or height). |
|
968 * |
|
969 * The bounds to return depend on the arrangement of displays and on where |
|
970 * Windows are to be centered. There are two common situations: |
|
971 * |
|
972 * 1) The center point lies at the center of the combined area of all the |
|
973 * displays. In this case, the combined area of all displays is |
|
974 * returned. |
|
975 * |
|
976 * 2) The center point lies at the center of a single display. In this case |
|
977 * the user most likely wants centered Windows to be constrained to that |
|
978 * single display. The boundaries of the one display are returned. |
|
979 * |
|
980 * It is possible for the center point to be at both the center of the |
|
981 * entire display space AND at the center of a single monitor (a square of |
|
982 * 9 monitors, for instance). In this case, the entire display area is |
|
983 * returned. |
|
984 * |
|
985 * Because the center point is arbitrarily settable by the user, it could |
|
986 * fit neither of the cases above. The fallback case is to simply return |
|
987 * the combined area for all screens. |
|
988 */ |
|
989 protected Rectangle getXineramaWindowBounds() { |
|
990 Point center = getCenterPoint(); |
|
991 Rectangle unionRect, tempRect; |
|
992 GraphicsDevice[] gds = getScreenDevices(); |
|
993 Rectangle centerMonitorRect = null; |
|
994 int i; |
|
995 |
|
996 // if center point is at the center of all monitors |
|
997 // return union of all bounds |
|
998 // |
|
999 // MM*MM MMM M |
|
1000 // M*M * |
|
1001 // MMM M |
|
1002 |
|
1003 // if center point is at center of a single monitor (but not of all |
|
1004 // monitors) |
|
1005 // return bounds of single monitor |
|
1006 // |
|
1007 // MMM MM |
|
1008 // MM* *M |
|
1009 |
|
1010 // else, center is in some strange spot (such as on the border between |
|
1011 // monitors), and we should just return the union of all monitors |
|
1012 // |
|
1013 // MM MMM |
|
1014 // MM MMM |
|
1015 |
|
1016 unionRect = getUsableBounds(gds[0]); |
|
1017 |
|
1018 for (i = 0; i < gds.length; i++) { |
|
1019 tempRect = getUsableBounds(gds[i]); |
|
1020 if (centerMonitorRect == null && |
|
1021 // add a pixel or two for fudge-factor |
|
1022 (tempRect.width / 2) + tempRect.x > center.x - 1 && |
|
1023 (tempRect.height / 2) + tempRect.y > center.y - 1 && |
|
1024 (tempRect.width / 2) + tempRect.x < center.x + 1 && |
|
1025 (tempRect.height / 2) + tempRect.y < center.y + 1) { |
|
1026 centerMonitorRect = tempRect; |
|
1027 } |
|
1028 unionRect = unionRect.union(tempRect); |
|
1029 } |
|
1030 |
|
1031 // first: check for center of all monitors (video wall) |
|
1032 // add a pixel or two for fudge-factor |
|
1033 if ((unionRect.width / 2) + unionRect.x > center.x - 1 && |
|
1034 (unionRect.height / 2) + unionRect.y > center.y - 1 && |
|
1035 (unionRect.width / 2) + unionRect.x < center.x + 1 && |
|
1036 (unionRect.height / 2) + unionRect.y < center.y + 1) { |
|
1037 |
|
1038 if (screenLog.isLoggable(Level.FINER)) { |
|
1039 screenLog.log(Level.FINER, "Video Wall: center point is at center of all displays."); |
|
1040 } |
|
1041 return unionRect; |
|
1042 } |
|
1043 |
|
1044 // next, check if at center of one monitor |
|
1045 if (centerMonitorRect != null) { |
|
1046 if (screenLog.isLoggable(Level.FINER)) { |
|
1047 screenLog.log(Level.FINER, "Center point at center of a particular " + |
|
1048 "monitor, but not of the entire virtual display."); |
|
1049 } |
|
1050 return centerMonitorRect; |
|
1051 } |
|
1052 |
|
1053 // otherwise, the center is at some weird spot: return unionRect |
|
1054 if (screenLog.isLoggable(Level.FINER)) { |
|
1055 screenLog.log(Level.FINER, "Center point is somewhere strange - return union of all bounds."); |
|
1056 } |
|
1057 return unionRect; |
|
1058 } |
|
1059 |
|
1060 /** |
|
1061 * From the DisplayChangedListener interface; devices do not need |
|
1062 * to react to this event. |
|
1063 */ |
|
1064 @Override |
|
1065 public void paletteChanged() { |
|
1066 } |
|
1067 } |