blob: 820c72728d3b75803a627c56f44d2a84b504af07 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003-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.font;
27
28import java.awt.Font;
29import java.awt.GraphicsEnvironment;
30import java.awt.FontFormatException;
31import java.io.File;
32import java.io.FilenameFilter;
33import java.security.AccessController;
34import java.security.PrivilegedAction;
35import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.HashSet;
38import java.util.Hashtable;
39import java.util.Iterator;
40import java.util.Locale;
41import java.util.Map;
42import java.util.NoSuchElementException;
43import java.util.StringTokenizer;
44import java.util.TreeMap;
45import java.util.Vector;
46import java.util.concurrent.ConcurrentHashMap;
47import java.util.logging.Level;
48import java.util.logging.Logger;
49import javax.swing.plaf.FontUIResource;
50
51import sun.awt.AppContext;
52import sun.awt.FontConfiguration;
53import sun.awt.SunHints;
54import sun.awt.SunToolkit;
55import sun.java2d.HeadlessGraphicsEnvironment;
56import sun.java2d.SunGraphicsEnvironment;
57
58import java.awt.geom.GeneralPath;
59import java.awt.geom.Point2D;
60import java.awt.geom.Rectangle2D;
61
62import java.lang.reflect.Constructor;
63
64import sun.java2d.Disposer;
65
66/*
67 * Interface between Java Fonts (java.awt.Font) and the underlying
68 * font files/native font resources and the Java and native font scalers.
69 */
70public final class FontManager {
71
72 public static final int FONTFORMAT_NONE = -1;
73 public static final int FONTFORMAT_TRUETYPE = 0;
74 public static final int FONTFORMAT_TYPE1 = 1;
75 public static final int FONTFORMAT_T2K = 2;
76 public static final int FONTFORMAT_TTC = 3;
77 public static final int FONTFORMAT_COMPOSITE = 4;
78 public static final int FONTFORMAT_NATIVE = 5;
79
80 public static final int NO_FALLBACK = 0;
81 public static final int PHYSICAL_FALLBACK = 1;
82 public static final int LOGICAL_FALLBACK = 2;
83
84 public static final int QUADPATHTYPE = 1;
85 public static final int CUBICPATHTYPE = 2;
86
87 /* Pool of 20 font file channels chosen because some UTF-8 locale
88 * composite fonts can use up to 16 platform fonts (including the
89 * Lucida fall back). This should prevent channel thrashing when
90 * dealing with one of these fonts.
91 * The pool array stores the fonts, rather than directly referencing
92 * the channels, as the font needs to do the open/close work.
93 */
94 private static final int CHANNELPOOLSIZE = 20;
95 private static int lastPoolIndex = 0;
96 private static int poolSize = 0;
97 private static FileFont fontFileCache[] = new FileFont[CHANNELPOOLSIZE];
98
99 /* Need to implement a simple linked list scheme for fast
100 * traversal and lookup.
101 * Also want to "fast path" dialog so there's minimal overhead.
102 */
103 /* There are at exactly 20 composite fonts: 5 faces (but some are not
104 * usually different), in 4 styles. The array may be auto-expanded
105 * later if more are needed, eg for user-defined composites or locale
106 * variants.
107 */
108 private static int maxCompFont = 0;
109 private static CompositeFont [] compFonts = new CompositeFont[20];
110 private static ConcurrentHashMap<String, CompositeFont>
111 compositeFonts = new ConcurrentHashMap<String, CompositeFont>();
112 private static ConcurrentHashMap<String, PhysicalFont>
113 physicalFonts = new ConcurrentHashMap<String, PhysicalFont>();
114 private static ConcurrentHashMap<String, PhysicalFont>
115 registeredFontFiles = new ConcurrentHashMap<String, PhysicalFont>();
116
117 /* given a full name find the Font. Remind: there's duplication
118 * here in that this contains the content of compositeFonts +
119 * physicalFonts.
120 */
121 private static ConcurrentHashMap<String, Font2D>
122 fullNameToFont = new ConcurrentHashMap<String, Font2D>();
123
124 /* TrueType fonts have localised names. Support searching all
125 * of these before giving up on a name.
126 */
127 private static HashMap<String, TrueTypeFont> localeFullNamesToFont;
128
129 private static PhysicalFont defaultPhysicalFont;
130
131 /* deprecated, unsupported hack - actually invokes a bug! */
132 private static boolean usePlatformFontMetrics = false;
133
134 public static Logger logger = null;
135 public static boolean logging;
136 static boolean longAddresses;
137 static String osName;
138 static boolean useT2K;
139 static boolean isWindows;
140 static boolean isSolaris;
141 public static boolean isSolaris8; // needed to check for JA wavedash fix.
142 public static boolean isSolaris9; // needed to check for songti font usage.
143 private static boolean loaded1dot0Fonts = false;
144 static SunGraphicsEnvironment sgEnv;
145 static boolean loadedAllFonts = false;
146 static boolean loadedAllFontFiles = false;
147 static TrueTypeFont eudcFont;
148 static HashMap<String,String> jreFontMap;
149 static HashSet<String> jreLucidaFontFiles;
150 static String[] jreOtherFontFiles;
151 static boolean noOtherJREFontFiles = false; // initial assumption.
152
153 /* Used to indicate required return type from toArray(..); */
154 private static String[] STR_ARRAY = new String[0];
155
156 private static void initJREFontMap() {
157
158 /* Key is familyname+style value as an int.
159 * Value is filename containing the font.
160 * If no mapping exists, it means there is no font file for the style
161 * If the mapping exists but the file doesn't exist in the deferred
162 * list then it means its not installed.
163 * This looks like a lot of code and strings but if it saves even
164 * a single file being opened at JRE start-up there's a big payoff.
165 * Lucida Sans is probably the only important case as the others
166 * are rarely used. Consider removing the other mappings if there's
167 * no evidence they are useful in practice.
168 */
169 jreFontMap = new HashMap<String,String>();
170 jreLucidaFontFiles = new HashSet<String>();
171 if (SunGraphicsEnvironment.isOpenJDK()) {
172 return;
173 }
174 /* Lucida Sans Family */
175 jreFontMap.put("lucida sans0", "LucidaSansRegular.ttf");
176 jreFontMap.put("lucida sans1", "LucidaSansDemiBold.ttf");
177 /* Lucida Sans full names (map Bold and DemiBold to same file) */
178 jreFontMap.put("lucida sans regular0", "LucidaSansRegular.ttf");
179 jreFontMap.put("lucida sans regular1", "LucidaSansDemiBold.ttf");
180 jreFontMap.put("lucida sans bold1", "LucidaSansDemiBold.ttf");
181 jreFontMap.put("lucida sans demibold1", "LucidaSansDemiBold.ttf");
182
183 /* Lucida Sans Typewriter Family */
184 jreFontMap.put("lucida sans typewriter0",
185 "LucidaTypewriterRegular.ttf");
186 jreFontMap.put("lucida sans typewriter1", "LucidaTypewriterBold.ttf");
187 /* Typewriter full names (map Bold and DemiBold to same file) */
188 jreFontMap.put("lucida sans typewriter regular0",
189 "LucidaTypewriter.ttf");
190 jreFontMap.put("lucida sans typewriter regular1",
191 "LucidaTypewriterBold.ttf");
192 jreFontMap.put("lucida sans typewriter bold1",
193 "LucidaTypewriterBold.ttf");
194 jreFontMap.put("lucida sans typewriter demibold1",
195 "LucidaTypewriterBold.ttf");
196
197 /* Lucida Bright Family */
198 jreFontMap.put("lucida bright0", "LucidaBrightRegular.ttf");
199 jreFontMap.put("lucida bright1", "LucidaBrightDemiBold.ttf");
200 jreFontMap.put("lucida bright2", "LucidaBrightItalic.ttf");
201 jreFontMap.put("lucida bright3", "LucidaBrightDemiItalic.ttf");
202 /* Lucida Bright full names (map Bold and DemiBold to same file) */
203 jreFontMap.put("lucida bright regular0", "LucidaBrightRegular.ttf");
204 jreFontMap.put("lucida bright regular1", "LucidaBrightDemiBold.ttf");
205 jreFontMap.put("lucida bright regular2", "LucidaBrightItalic.ttf");
206 jreFontMap.put("lucida bright regular3", "LucidaBrightDemiItalic.ttf");
207 jreFontMap.put("lucida bright bold1", "LucidaBrightDemiBold.ttf");
208 jreFontMap.put("lucida bright bold3", "LucidaBrightDemiItalic.ttf");
209 jreFontMap.put("lucida bright demibold1", "LucidaBrightDemiBold.ttf");
210 jreFontMap.put("lucida bright demibold3","LucidaBrightDemiItalic.ttf");
211 jreFontMap.put("lucida bright italic2", "LucidaBrightItalic.ttf");
212 jreFontMap.put("lucida bright italic3", "LucidaBrightDemiItalic.ttf");
213 jreFontMap.put("lucida bright bold italic3",
214 "LucidaBrightDemiItalic.ttf");
215 jreFontMap.put("lucida bright demibold italic3",
216 "LucidaBrightDemiItalic.ttf");
217 for (String ffile : jreFontMap.values()) {
218 jreLucidaFontFiles.add(ffile);
219 }
220 }
221
222 static {
223
224 if (SunGraphicsEnvironment.debugFonts) {
225 logger = Logger.getLogger("sun.java2d", null);
226 logging = logger.getLevel() != Level.OFF;
227 }
228 initJREFontMap();
229
230 java.security.AccessController.doPrivileged(
231 new java.security.PrivilegedAction() {
232 public Object run() {
233 FontManagerNativeLibrary.load();
234
235 // JNI throws an exception if a class/method/field is not found,
236 // so there's no need to do anything explicit here.
237 initIDs();
238
239 switch (StrikeCache.nativeAddressSize) {
240 case 8: longAddresses = true; break;
241 case 4: longAddresses = false; break;
242 default: throw new RuntimeException("Unexpected address size");
243 }
244
245 osName = System.getProperty("os.name", "unknownOS");
246 isSolaris = osName.startsWith("SunOS");
247
248 if (isSolaris) {
249 String t2kStr= System.getProperty("sun.java2d.font.scaler");
250 useT2K = "t2k".equals(t2kStr);
251 String version = System.getProperty("os.version", "unk");
252 isSolaris8 = version.equals("5.8");
253 isSolaris9 = version.equals("5.9");
254 } else {
255 isWindows = osName.startsWith("Windows");
256 if (isWindows) {
257 String eudcFile =
258 SunGraphicsEnvironment.eudcFontFileName;
259 if (eudcFile != null) {
260 try {
261 eudcFont = new TrueTypeFont(eudcFile, null, 0,
262 true);
263 } catch (FontFormatException e) {
264 }
265 }
266 String prop =
267 System.getProperty("java2d.font.usePlatformFont");
268 if (("true".equals(prop) || getPlatformFontVar())) {
269 usePlatformFontMetrics = true;
270 System.out.println("Enabling platform font metrics for win32. This is an unsupported option.");
271 System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases.");
272 System.out.println("It is appropriate only for use by applications which do not use any Java 2");
273 System.out.println("functionality. This property will be removed in a later release.");
274 }
275 }
276 }
277 return null;
278 }
279 });
280 }
281
282 /* Initialise ptrs used by JNI methods */
283 private static native void initIDs();
284
285 public static void addToPool(FileFont font) {
286 boolean added = false;
287 synchronized (fontFileCache) {
288 /* use poolSize to quickly detect if there's any free slots.
289 * This is a performance tweak based on the assumption that
290 * if this is executed at all often, its because there are many
291 * fonts being used and the pool will be full, and we will save
292 * a fruitless iteration
293 */
294 if (poolSize < CHANNELPOOLSIZE) {
295 for (int i=0; i<CHANNELPOOLSIZE; i++) {
296 if (fontFileCache[i] == null) {
297 fontFileCache[i] = font;
298 poolSize++;
299 added = true;
300 break;
301 }
302 }
303 assert added;
304 } else {
305 // is it possible for this to be the same font?
306 assert fontFileCache[lastPoolIndex] != font;
307 /* replace with new font, poolSize is unchanged. */
308 fontFileCache[lastPoolIndex].close();
309 fontFileCache[lastPoolIndex] = font;
310 /* lastPoolIndex is updated so that the least recently opened
311 * file will be closed next.
312 */
313 lastPoolIndex = (lastPoolIndex+1) % CHANNELPOOLSIZE;
314 }
315 }
316 }
317
318 /*
319 * In the normal course of events, the pool of fonts can remain open
320 * ready for quick access to their contents. The pool is sized so
321 * that it is not an excessive consumer of system resources whilst
322 * facilitating performance by providing ready access to the most
323 * recently used set of font files.
324 * The only reason to call removeFromPool(..) is for a Font that
325 * you want to to have GC'd. Currently this would apply only to fonts
326 * created with java.awt.Font.createFont(..).
327 * In this case, the caller is expected to have arranged for the file
328 * to be closed.
329 * REMIND: consider how to know when a createFont created font should
330 * be closed.
331 */
332 public static void removeFromPool(FileFont font) {
333 synchronized (fontFileCache) {
334 for (int i=0; i<CHANNELPOOLSIZE; i++) {
335 if (fontFileCache[i] == font) {
336 fontFileCache[i] = null;
337 poolSize--;
338 }
339 }
340 }
341 }
342
343 /**
344 * This method is provided for internal and exclusive use by Swing.
345 *
346 * @param font representing a physical font.
347 * @return true if the underlying font is a TrueType or OpenType font
348 * that claims to support the Microsoft Windows encoding corresponding to
349 * the default file.encoding property of this JRE instance.
350 * This narrow value is useful for Swing to decide if the font is useful
351 * for the the Windows Look and Feel, or, if a composite font should be
352 * used instead.
353 * The information used to make the decision is obtained from
354 * the ulCodePageRange fields in the font.
355 * A caller can use isLogicalFont(Font) in this class before calling
356 * this method and would not need to call this method if that
357 * returns true.
358 */
359// static boolean fontSupportsDefaultEncoding(Font font) {
360// String encoding =
361// (String) java.security.AccessController.doPrivileged(
362// new sun.security.action.GetPropertyAction("file.encoding"));
363
364// if (encoding == null || font == null) {
365// return false;
366// }
367
368// encoding = encoding.toLowerCase(Locale.ENGLISH);
369
370// return FontManager.fontSupportsEncoding(font, encoding);
371// }
372
373 /* Revise the implementation to in fact mean "font is a composite font.
374 * This ensures that Swing components will always benefit from the
375 * fall back fonts
376 */
377 public static boolean fontSupportsDefaultEncoding(Font font) {
378 return getFont2D(font) instanceof CompositeFont;
379 }
380
381 /**
382 * This method is provided for internal and exclusive use by Swing.
383 *
384 * It may be used in conjunction with fontSupportsDefaultEncoding(Font)
385 * In the event that a desktop properties font doesn't directly
386 * support the default encoding, (ie because the host OS supports
387 * adding support for the current locale automatically for native apps),
388 * then Swing calls this method to get a font which uses the specified
389 * font for the code points it covers, but also supports this locale
390 * just as the standard composite fonts do.
391 * Note: this will over-ride any setting where an application
392 * specifies it prefers locale specific composite fonts.
393 * The logic for this, is that this method is used only where the user or
394 * application has specified that the native L&F be used, and that
395 * we should honour that request to use the same font as native apps use.
396 *
397 * The behaviour of this method is to construct a new composite
398 * Font object that uses the specified physical font as its first
399 * component, and adds all the components of "dialog" as fall back
400 * components.
401 * The method currently assumes that only the size and style attributes
402 * are set on the specified font. It doesn't copy the font transform or
403 * other attributes because they aren't set on a font created from
404 * the desktop. This will need to be fixed if use is broadened.
405 *
406 * Operations such as Font.deriveFont will work properly on the
407 * font returned by this method for deriving a different point size.
408 * Additionally it tries to support a different style by calling
409 * getNewComposite() below. That also supports replacing slot zero
410 * with a different physical font but that is expected to be "rare".
411 * Deriving with a different style is needed because its been shown
412 * that some applications try to do this for Swing FontUIResources.
413 * Also operations such as new Font(font.getFontName(..), Font.PLAIN, 14);
414 * will NOT yield the same result, as the new underlying CompositeFont
415 * cannot be "looked up" in the font registry.
416 * This returns a FontUIResource as that is the Font sub-class needed
417 * by Swing.
418 * Suggested usage is something like :
419 * FontUIResource fuir;
420 * Font desktopFont = getDesktopFont(..);
421 * // NOTE even if fontSupportsDefaultEncoding returns true because
422 * // you get Tahoma and are running in an English locale, you may
423 * // still want to just call getCompositeFontUIResource() anyway
424 * // as only then will you get fallback fonts - eg for CJK.
425 * if (FontManager.fontSupportsDefaultEncoding(desktopFont)) {
426 * fuir = new FontUIResource(..);
427 * } else {
428 * fuir = FontManager.getCompositeFontUIResource(desktopFont);
429 * }
430 * return fuir;
431 */
432 public static FontUIResource getCompositeFontUIResource(Font font) {
433
434 FontUIResource fuir =
435 new FontUIResource(font.getName(),font.getStyle(),font.getSize());
436 Font2D font2D = getFont2D(font);
437
438 if (!(font2D instanceof PhysicalFont)) {
439 /* Swing should only be calling this when a font is obtained
440 * from desktop properties, so should generally be a physical font,
441 * an exception might be for names like "MS Serif" which are
442 * automatically mapped to "Serif", so there's no need to do
443 * anything special in that case. But note that suggested usage
444 * is first to call fontSupportsDefaultEncoding(Font) and this
445 * method should not be called if that were to return true.
446 */
447 return fuir;
448 }
449
450 CompositeFont dialog2D =
451 (CompositeFont)findFont2D("dialog", font.getStyle(), NO_FALLBACK);
452 if (dialog2D == null) { /* shouldn't happen */
453 return fuir;
454 }
455 PhysicalFont physicalFont = (PhysicalFont)font2D;
456 CompositeFont compFont = new CompositeFont(physicalFont, dialog2D);
457 setFont2D(fuir, compFont.handle);
458 /* marking this as a created font is needed as only created fonts
459 * copy their creator's handles.
460 */
461 setCreatedFont(fuir);
462 return fuir;
463 }
464
465 public static Font2DHandle getNewComposite(String family, int style,
466 Font2DHandle handle) {
467
468 if (!(handle.font2D instanceof CompositeFont)) {
469 return handle;
470 }
471
472 CompositeFont oldComp = (CompositeFont)handle.font2D;
473 PhysicalFont oldFont = oldComp.getSlotFont(0);
474
475 if (family == null) {
476 family = oldFont.getFamilyName(null);
477 }
478 if (style == -1) {
479 style = oldComp.getStyle();
480 }
481
482 Font2D newFont = findFont2D(family, style, NO_FALLBACK);
483 if (!(newFont instanceof PhysicalFont)) {
484 newFont = oldFont;
485 }
486 PhysicalFont physicalFont = (PhysicalFont)newFont;
487 CompositeFont dialog2D =
488 (CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
489 if (dialog2D == null) { /* shouldn't happen */
490 return handle;
491 }
492 CompositeFont compFont = new CompositeFont(physicalFont, dialog2D);
493 Font2DHandle newHandle = new Font2DHandle(compFont);
494 return newHandle;
495 }
496
497 public static native void setFont2D(Font font, Font2DHandle font2DHandle);
498
499 private static native boolean isCreatedFont(Font font);
500 private static native void setCreatedFont(Font font);
501
502 public static void registerCompositeFont(String compositeName,
503 String[] componentFileNames,
504 String[] componentNames,
505 int numMetricsSlots,
506 int[] exclusionRanges,
507 int[] exclusionMaxIndex,
508 boolean defer) {
509
510 CompositeFont cf = new CompositeFont(compositeName,
511 componentFileNames,
512 componentNames,
513 numMetricsSlots,
514 exclusionRanges,
515 exclusionMaxIndex, defer);
516 addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK);
517 synchronized (compFonts) {
518 compFonts[maxCompFont++] = cf;
519 }
520 }
521
522 /* This variant is used only when the application specifies
523 * a variant of composite fonts which prefers locale specific or
524 * proportional fonts.
525 */
526 public static void registerCompositeFont(String compositeName,
527 String[] componentFileNames,
528 String[] componentNames,
529 int numMetricsSlots,
530 int[] exclusionRanges,
531 int[] exclusionMaxIndex,
532 boolean defer,
533 ConcurrentHashMap<String, Font2D>
534 altNameCache) {
535
536 CompositeFont cf = new CompositeFont(compositeName,
537 componentFileNames,
538 componentNames,
539 numMetricsSlots,
540 exclusionRanges,
541 exclusionMaxIndex, defer);
542 /* if the cache has an existing composite for this case, make
543 * its handle point to this new font.
544 * This ensures that when the altNameCache that is passed in
545 * is the global mapNameCache - ie we are running as an application -
546 * that any statically created java.awt.Font instances which already
547 * have a Font2D instance will have that re-directed to the new Font
548 * on subsequent uses. This is particularly important for "the"
549 * default font instance, or similar cases where a UI toolkit (eg
550 * Swing) has cached a java.awt.Font. Note that if Swing is using
551 * a custom composite APIs which update the standard composites have
552 * no effect - this is typically the case only when using the Windows
553 * L&F where these APIs would conflict with that L&F anyway.
554 */
555 Font2D oldFont = (Font2D)
556 altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH));
557 if (oldFont instanceof CompositeFont) {
558 oldFont.handle.font2D = cf;
559 }
560 altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf);
561 }
562
563 private static void addCompositeToFontList(CompositeFont f, int rank) {
564
565 if (logging) {
566 logger.info("Add to Family "+ f.familyName +
567 ", Font " + f.fullName + " rank="+rank);
568 }
569 f.setRank(rank);
570 compositeFonts.put(f.fullName, f);
571 fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f);
572
573 FontFamily family = FontFamily.getFamily(f.familyName);
574 if (family == null) {
575 family = new FontFamily(f.familyName, true, rank);
576 }
577 family.setFont(f, f.style);
578 }
579
580 /*
581 * Systems may have fonts with the same name.
582 * We want to register only one of such fonts (at least until
583 * such time as there might be APIs which can accommodate > 1).
584 * Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts,
585 * 4) Type1 fonts, 5) native fonts.
586 *
587 * If the new font has the same name as the old font, the higher
588 * ranked font gets added, replacing the lower ranked one.
589 * If the fonts are of equal rank, then make a special case of
590 * font configuration rank fonts, which are on closer inspection,
591 * OT/TT fonts such that the larger font is registered. This is
592 * a heuristic since a font may be "larger" in the sense of more
593 * code points, or be a larger "file" because it has more bitmaps.
594 * So it is possible that using filesize may lead to less glyphs, and
595 * using glyphs may lead to lower quality display. Probably number
596 * of glyphs is the ideal, but filesize is information we already
597 * have and is good enough for the known cases.
598 * Also don't want to register fonts that match JRE font families
599 * but are coming from a source other than the JRE.
600 * This will ensure that we will algorithmically style the JRE
601 * plain font and get the same set of glyphs for all styles.
602 *
603 * Note that this method returns a value
604 * if it returns the same object as its argument that means this
605 * font was newly registered.
606 * If it returns a different object it means this font already exists,
607 * and you should use that one.
608 * If it returns null means this font was not registered and none
609 * in that name is registered. The caller must find a substitute
610 */
611 private static PhysicalFont addToFontList(PhysicalFont f, int rank) {
612
613 String fontName = f.fullName;
614 String familyName = f.familyName;
615 if (fontName == null || "".equals(fontName)) {
616 return null;
617 }
618 if (compositeFonts.containsKey(fontName)) {
619 /* Don't register any font that has the same name as a composite */
620 return null;
621 }
622 f.setRank(rank);
623 if (!physicalFonts.containsKey(fontName)) {
624 if (logging) {
625 logger.info("Add to Family "+familyName +
626 ", Font " + fontName + " rank="+rank);
627 }
628 physicalFonts.put(fontName, f);
629 FontFamily family = FontFamily.getFamily(familyName);
630 if (family == null) {
631 family = new FontFamily(familyName, false, rank);
632 family.setFont(f, f.style);
633 } else if (family.getRank() >= rank) {
634 family.setFont(f, f.style);
635 }
636 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f);
637 return f;
638 } else {
639 PhysicalFont newFont = f;
640 PhysicalFont oldFont = physicalFonts.get(fontName);
641 if (oldFont == null) {
642 return null;
643 }
644 /* If the new font is of an equal or higher rank, it is a
645 * candidate to replace the current one, subject to further tests.
646 */
647 if (oldFont.getRank() >= rank) {
648
649 /* All fonts initialise their mapper when first
650 * used. If the mapper is non-null then this font
651 * has been accessed at least once. In that case
652 * do not replace it. This may be overly stringent,
653 * but its probably better not to replace a font that
654 * someone is already using without a compelling reason.
655 * Additionally the primary case where it is known
656 * this behaviour is important is in certain composite
657 * fonts, and since all the components of a given
658 * composite are usually initialised together this
659 * is unlikely. For this to be a problem, there would
660 * have to be a case where two different composites used
661 * different versions of the same-named font, and they
662 * were initialised and used at separate times.
663 * In that case we continue on and allow the new font to
664 * be installed, but replaceFont will continue to allow
665 * the original font to be used in Composite fonts.
666 */
667 if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) {
668 return oldFont;
669 }
670
671 /* Normally we require a higher rank to replace a font,
672 * but as a special case, if the two fonts are the same rank,
673 * and are instances of TrueTypeFont we want the
674 * more complete (larger) one.
675 */
676 if (oldFont.getRank() == rank) {
677 if (oldFont instanceof TrueTypeFont &&
678 newFont instanceof TrueTypeFont) {
679 TrueTypeFont oldTTFont = (TrueTypeFont)oldFont;
680 TrueTypeFont newTTFont = (TrueTypeFont)newFont;
681 if (oldTTFont.fileSize >= newTTFont.fileSize) {
682 return oldFont;
683 }
684 } else {
685 return oldFont;
686 }
687 }
688 /* Don't replace ever JRE fonts.
689 * This test is in case a font configuration references
690 * a Lucida font, which has been mapped to a Lucida
691 * from the host O/S. The assumption here is that any
692 * such font configuration file is probably incorrect, or
693 * the host O/S version is for the use of AWT.
694 * In other words if we reach here, there's a possible
695 * problem with our choice of font configuration fonts.
696 */
697 if (oldFont.platName.startsWith(
698 SunGraphicsEnvironment.jreFontDirName)) {
699 if (logging) {
700 logger.warning("Unexpected attempt to replace a JRE " +
701 " font " + fontName + " from " +
702 oldFont.platName +
703 " with " + newFont.platName);
704 }
705 return oldFont;
706 }
707
708 if (logging) {
709 logger.info("Replace in Family " + familyName +
710 ",Font " + fontName + " new rank="+rank +
711 " from " + oldFont.platName +
712 " with " + newFont.platName);
713 }
714 replaceFont(oldFont, newFont);
715 physicalFonts.put(fontName, newFont);
716 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH),
717 newFont);
718
719 FontFamily family = FontFamily.getFamily(familyName);
720 if (family == null) {
721 family = new FontFamily(familyName, false, rank);
722 family.setFont(newFont, newFont.style);
723 } else if (family.getRank() >= rank) {
724 family.setFont(newFont, newFont.style);
725 }
726 return newFont;
727 } else {
728 return oldFont;
729 }
730 }
731 }
732
733 public static Font2D[] getRegisteredFonts() {
734 PhysicalFont[] physFonts = getPhysicalFonts();
735 int mcf = maxCompFont; /* for MT-safety */
736 Font2D[] regFonts = new Font2D[physFonts.length+mcf];
737 System.arraycopy(compFonts, 0, regFonts, 0, mcf);
738 System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length);
739 return regFonts;
740 }
741
742 public static PhysicalFont[] getPhysicalFonts() {
743 return physicalFonts.values().toArray(new PhysicalFont[0]);
744 }
745
746
747 /* The class FontRegistrationInfo is used when a client says not
748 * to register a font immediately. This mechanism is used to defer
749 * initialisation of all the components of composite fonts at JRE
750 * start-up. The CompositeFont class is "aware" of this and when it
751 * is first used it asks for the registration of its components.
752 * Also in the event that any physical font is requested the
753 * deferred fonts are initialised before triggering a search of the
754 * system.
755 * Two maps are used. One to track the deferred fonts. The
756 * other to track the fonts that have been initialised through this
757 * mechanism.
758 */
759
760 private static final class FontRegistrationInfo {
761
762 String fontFilePath;
763 String[] nativeNames;
764 int fontFormat;
765 boolean javaRasterizer;
766 int fontRank;
767
768 FontRegistrationInfo(String fontPath, String[] names, int format,
769 boolean useJavaRasterizer, int rank) {
770 this.fontFilePath = fontPath;
771 this.nativeNames = names;
772 this.fontFormat = format;
773 this.javaRasterizer = useJavaRasterizer;
774 this.fontRank = rank;
775 }
776 }
777
778 private static final ConcurrentHashMap<String, FontRegistrationInfo>
779 deferredFontFiles =
780 new ConcurrentHashMap<String, FontRegistrationInfo>();
781 private static final ConcurrentHashMap<String, Font2DHandle>
782 initialisedFonts = new ConcurrentHashMap<String, Font2DHandle>();
783
784 /* Remind: possibly enhance initialiseDeferredFonts() to be
785 * optionally given a name and a style and it could stop when it
786 * finds that font - but this would be a problem if two of the
787 * fonts reference the same font face name (cf the Solaris
788 * euro fonts).
789 */
790 public static synchronized void initialiseDeferredFonts() {
791 for (String fileName : deferredFontFiles.keySet()) {
792 initialiseDeferredFont(fileName);
793 }
794 }
795
796 public static synchronized void registerDeferredJREFonts(String jreDir) {
797 for (FontRegistrationInfo info : deferredFontFiles.values()) {
798 if (info.fontFilePath != null &&
799 info.fontFilePath.startsWith(jreDir)) {
800 initialiseDeferredFont(info.fontFilePath);
801 }
802 }
803 }
804
805 /* We keep a map of the files which contain the Lucida fonts so we
806 * don't need to search for them.
807 * But since we know what fonts these files contain, we can also avoid
808 * opening them to look for a font name we don't recognise - see
809 * findDeferredFont().
810 * For typical cases where the font isn't a JRE one the overhead is
811 * this method call, HashMap.get() and null reference test, then
812 * a boolean test of noOtherJREFontFiles.
813 */
814 private static PhysicalFont findJREDeferredFont(String name, int style) {
815
816 PhysicalFont physicalFont;
817 String nameAndStyle = name.toLowerCase(Locale.ENGLISH) + style;
818 String fileName = jreFontMap.get(nameAndStyle);
819 if (fileName != null) {
820 initSGEnv(); /* ensure jreFontDirName is initialised */
821 fileName = SunGraphicsEnvironment.jreFontDirName +
822 File.separator + fileName;
823 if (deferredFontFiles.get(fileName) != null) {
824 physicalFont = initialiseDeferredFont(fileName);
825 if (physicalFont != null &&
826 (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
827 physicalFont.getFamilyName(null).equalsIgnoreCase(name))
828 && physicalFont.style == style) {
829 return physicalFont;
830 }
831 }
832 }
833
834 /* Iterate over the deferred font files looking for any in the
835 * jre directory that we didn't recognise, open each of these.
836 * In almost all installations this will quickly fall through
837 * because only the Lucidas will be present and jreOtherFontFiles
838 * will be empty.
839 * noOtherJREFontFiles is used so we can skip this block as soon
840 * as its determined that its not needed - almost always after the
841 * very first time through.
842 */
843 if (noOtherJREFontFiles) {
844 return null;
845 }
846 synchronized (jreLucidaFontFiles) {
847 if (jreOtherFontFiles == null) {
848 HashSet<String> otherFontFiles = new HashSet<String>();
849 for (String deferredFile : deferredFontFiles.keySet()) {
850 File file = new File(deferredFile);
851 String dir = file.getParent();
852 String fname = file.getName();
853 /* skip names which aren't absolute, aren't in the JRE
854 * directory, or are known Lucida fonts.
855 */
856 if (dir == null ||
857 !dir.equals(SunGraphicsEnvironment.jreFontDirName) ||
858 jreLucidaFontFiles.contains(fname)) {
859 continue;
860 }
861 otherFontFiles.add(deferredFile);
862 }
863 jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY);
864 if (jreOtherFontFiles.length == 0) {
865 noOtherJREFontFiles = true;
866 }
867 }
868
869 for (int i=0; i<jreOtherFontFiles.length;i++) {
870 fileName = jreOtherFontFiles[i];
871 if (fileName == null) {
872 continue;
873 }
874 jreOtherFontFiles[i] = null;
875 physicalFont = initialiseDeferredFont(fileName);
876 if (physicalFont != null &&
877 (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
878 physicalFont.getFamilyName(null).equalsIgnoreCase(name))
879 && physicalFont.style == style) {
880 return physicalFont;
881 }
882 }
883 }
884
885 return null;
886 }
887
888 /* This skips JRE installed fonts. */
889 private static PhysicalFont findOtherDeferredFont(String name, int style) {
890 for (String fileName : deferredFontFiles.keySet()) {
891 File file = new File(fileName);
892 String dir = file.getParent();
893 String fname = file.getName();
894 if (dir != null &&
895 dir.equals(SunGraphicsEnvironment.jreFontDirName) &&
896 jreLucidaFontFiles.contains(fname)) {
897 continue;
898 }
899 PhysicalFont physicalFont = initialiseDeferredFont(fileName);
900 if (physicalFont != null &&
901 (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
902 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) &&
903 physicalFont.style == style) {
904 return physicalFont;
905 }
906 }
907 return null;
908 }
909
910 private static PhysicalFont findDeferredFont(String name, int style) {
911
912 PhysicalFont physicalFont = findJREDeferredFont(name, style);
913 if (physicalFont != null) {
914 return physicalFont;
915 } else {
916 return findOtherDeferredFont(name, style);
917 }
918 }
919
920 public static void registerDeferredFont(String fileNameKey,
921 String fullPathName,
922 String[] nativeNames,
923 int fontFormat,
924 boolean useJavaRasterizer,
925 int fontRank) {
926 FontRegistrationInfo regInfo =
927 new FontRegistrationInfo(fullPathName, nativeNames, fontFormat,
928 useJavaRasterizer, fontRank);
929 deferredFontFiles.put(fileNameKey, regInfo);
930 }
931
932
933 public static synchronized
934 PhysicalFont initialiseDeferredFont(String fileNameKey) {
935
936 if (fileNameKey == null) {
937 return null;
938 }
939 if (logging) {
940 logger.info("Opening deferred font file " + fileNameKey);
941 }
942
943 PhysicalFont physicalFont;
944 FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey);
945 if (regInfo != null) {
946 deferredFontFiles.remove(fileNameKey);
947 physicalFont = registerFontFile(regInfo.fontFilePath,
948 regInfo.nativeNames,
949 regInfo.fontFormat,
950 regInfo.javaRasterizer,
951 regInfo.fontRank);
952
953
954 if (physicalFont != null) {
955 /* Store the handle, so that if a font is bad, we
956 * retrieve the substituted font.
957 */
958 initialisedFonts.put(fileNameKey, physicalFont.handle);
959 } else {
960 initialisedFonts.put(fileNameKey,
961 getDefaultPhysicalFont().handle);
962 }
963 } else {
964 Font2DHandle handle = initialisedFonts.get(fileNameKey);
965 if (handle == null) {
966 /* Probably shouldn't happen, but just in case */
967 physicalFont = getDefaultPhysicalFont();
968 } else {
969 physicalFont = (PhysicalFont)(handle.font2D);
970 }
971 }
972 return physicalFont;
973 }
974
975 /* Note that the return value from this method is not always
976 * derived from this file, and may be null. See addToFontList for
977 * some explanation of this.
978 */
979 public static PhysicalFont registerFontFile(String fileName,
980 String[] nativeNames,
981 int fontFormat,
982 boolean useJavaRasterizer,
983 int fontRank) {
984
985 PhysicalFont regFont = registeredFontFiles.get(fileName);
986 if (regFont != null) {
987 return regFont;
988 }
989
990 PhysicalFont physicalFont = null;
991 try {
992 String name;
993
994 switch (fontFormat) {
995
996 case FontManager.FONTFORMAT_TRUETYPE:
997 int fn = 0;
998 TrueTypeFont ttf;
999 do {
1000 ttf = new TrueTypeFont(fileName, nativeNames, fn++,
1001 useJavaRasterizer);
1002 PhysicalFont pf = addToFontList(ttf, fontRank);
1003 if (physicalFont == null) {
1004 physicalFont = pf;
1005 }
1006 }
1007 while (fn < ttf.getFontCount());
1008 break;
1009
1010 case FontManager.FONTFORMAT_TYPE1:
1011 Type1Font t1f = new Type1Font(fileName, nativeNames);
1012 physicalFont = addToFontList(t1f, fontRank);
1013 break;
1014
1015 case FontManager.FONTFORMAT_NATIVE:
1016 NativeFont nf = new NativeFont(fileName, false);
1017 physicalFont = addToFontList(nf, fontRank);
1018 default:
1019
1020 }
1021 if (logging) {
1022 logger.info("Registered file " + fileName + " as font " +
1023 physicalFont + " rank=" + fontRank);
1024 }
1025 } catch (FontFormatException ffe) {
1026 if (logging) {
1027 logger.warning("Unusable font: " +
1028 fileName + " " + ffe.toString());
1029 }
1030 }
1031 if (physicalFont != null &&
1032 fontFormat != FontManager.FONTFORMAT_NATIVE) {
1033 registeredFontFiles.put(fileName, physicalFont);
1034 }
1035 return physicalFont;
1036 }
1037
1038 public static void registerFonts(String[] fileNames,
1039 String[][] nativeNames,
1040 int fontCount,
1041 int fontFormat,
1042 boolean useJavaRasterizer,
1043 int fontRank, boolean defer) {
1044
1045 for (int i=0; i < fontCount; i++) {
1046 if (defer) {
1047 registerDeferredFont(fileNames[i],fileNames[i], nativeNames[i],
1048 fontFormat, useJavaRasterizer, fontRank);
1049 } else {
1050 registerFontFile(fileNames[i], nativeNames[i],
1051 fontFormat, useJavaRasterizer, fontRank);
1052 }
1053 }
1054 }
1055
1056 /*
1057 * This is the Physical font used when some other font on the system
1058 * can't be located. There has to be at least one font or the font
1059 * system is not useful and the graphics environment cannot sustain
1060 * the Java platform.
1061 */
1062 public static PhysicalFont getDefaultPhysicalFont() {
1063 if (defaultPhysicalFont == null) {
1064 /* findFont2D will load all fonts before giving up the search.
1065 * If the JRE Lucida isn't found (eg because the JRE fonts
1066 * directory is missing), it could find another version of Lucida
1067 * from the host system. This is OK because at that point we are
1068 * trying to gracefully handle/recover from a system
1069 * misconfiguration and this is probably a reasonable substitution.
1070 */
1071 defaultPhysicalFont = (PhysicalFont)
1072 findFont2D("Lucida Sans Regular", Font.PLAIN, NO_FALLBACK);
1073 if (defaultPhysicalFont == null) {
1074 defaultPhysicalFont = (PhysicalFont)
1075 findFont2D("Arial", Font.PLAIN, NO_FALLBACK);
1076 }
1077 if (defaultPhysicalFont == null) {
1078 /* Because of the findFont2D call above, if we reach here, we
1079 * know all fonts have already been loaded, just accept any
1080 * match at this point. If this fails we are in real trouble
1081 * and I don't know how to recover from there being absolutely
1082 * no fonts anywhere on the system.
1083 */
1084 Iterator i = physicalFonts.values().iterator();
1085 if (i.hasNext()) {
1086 defaultPhysicalFont = (PhysicalFont)i.next();
1087 } else {
1088 throw new Error("Probable fatal error:No fonts found.");
1089 }
1090 }
1091 }
1092 return defaultPhysicalFont;
1093 }
1094
1095 public static CompositeFont getDefaultLogicalFont(int style) {
1096 return (CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
1097 }
1098
1099 /*
1100 * return String representation of style prepended with "."
1101 * This is useful for performance to avoid unnecessary string operations.
1102 */
1103 private static String dotStyleStr(int num) {
1104 switch(num){
1105 case Font.BOLD:
1106 return ".bold";
1107 case Font.ITALIC:
1108 return ".italic";
1109 case Font.ITALIC | Font.BOLD:
1110 return ".bolditalic";
1111 default:
1112 return ".plain";
1113 }
1114 }
1115
1116 static void initSGEnv() {
1117 if (sgEnv == null) {
1118 GraphicsEnvironment ge =
1119 GraphicsEnvironment.getLocalGraphicsEnvironment();
1120 if (ge instanceof HeadlessGraphicsEnvironment) {
1121 HeadlessGraphicsEnvironment hgEnv =
1122 (HeadlessGraphicsEnvironment)ge;
1123 sgEnv = (SunGraphicsEnvironment)
1124 hgEnv.getSunGraphicsEnvironment();
1125 } else {
1126 sgEnv = (SunGraphicsEnvironment)ge;
1127 }
1128 }
1129 }
1130
1131 /* This is implemented only on windows and is called from code that
1132 * executes only on windows. This isn't pretty but its not a precedent
1133 * in this file. This very probably should be cleaned up at some point.
1134 */
1135 private static native void
1136 populateFontFileNameMap(HashMap<String,String> fontToFileMap,
1137 HashMap<String,String> fontToFamilyNameMap,
1138 HashMap<String,ArrayList<String>>
1139 familyToFontListMap,
1140 Locale locale);
1141
1142 /* Obtained from Platform APIs (windows only)
1143 * Map from lower-case font full name to basename of font file.
1144 * Eg "arial bold" -> ARIALBD.TTF.
1145 * For TTC files, there is a mapping for each font in the file.
1146 */
1147 private static HashMap<String,String> fontToFileMap = null;
1148
1149 /* Obtained from Platform APIs (windows only)
1150 * Map from lower-case font full name to the name of its font family
1151 * Eg "arial bold" -> "Arial"
1152 */
1153 private static HashMap<String,String> fontToFamilyNameMap = null;
1154
1155 /* Obtained from Platform APIs (windows only)
1156 * Map from a lower-case family name to a list of full names of
1157 * the member fonts, eg:
1158 * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"]
1159 */
1160 private static HashMap<String,ArrayList<String>> familyToFontListMap= null;
1161
1162 /* The directories which contain platform fonts */
1163 private static String[] pathDirs = null;
1164
1165 private static boolean haveCheckedUnreferencedFontFiles;
1166
1167 private static String[] getFontFilesFromPath(boolean noType1) {
1168 final FilenameFilter filter;
1169 if (noType1) {
1170 filter = SunGraphicsEnvironment.ttFilter;
1171 } else {
1172 filter = new SunGraphicsEnvironment.TTorT1Filter();
1173 }
1174 return (String[])AccessController.doPrivileged(new PrivilegedAction() {
1175 public Object run() {
1176 if (pathDirs.length == 1) {
1177 File dir = new File(pathDirs[0]);
1178 String[] files = dir.list(filter);
1179 if (files == null) {
1180 return new String[0];
1181 }
1182 for (int f=0; f<files.length; f++) {
1183 files[f] = files[f].toLowerCase();
1184 }
1185 return files;
1186 } else {
1187 ArrayList<String> fileList = new ArrayList<String>();
1188 for (int i = 0; i< pathDirs.length; i++) {
1189 File dir = new File(pathDirs[i]);
1190 String[] files = dir.list(filter);
1191 if (files == null) {
1192 continue;
1193 }
1194 for (int f=0; f<files.length ; f++) {
1195 fileList.add(files[f].toLowerCase());
1196 }
1197 }
1198 return fileList.toArray(STR_ARRAY);
1199 }
1200 }
1201 });
1202 }
1203
1204 /* This is needed since some windows registry names don't match
1205 * the font names.
1206 * - UPC styled font names have a double space, but the
1207 * registry entry mapping to a file doesn't.
1208 * - Marlett is in a hidden file not listed in the registry
1209 * - The registry advertises that the file david.ttf contains a
1210 * font with the full name "David Regular" when in fact its
1211 * just "David".
1212 * Directly fix up these known cases as this is faster.
1213 * If a font which doesn't match these known cases has no file,
1214 * it may be a font that has been temporarily added to the known set
1215 * or it may be an installed font with a missing registry entry.
1216 * Installed fonts are those in the windows font directories.
1217 * Make a best effort attempt to locate these.
1218 * We obtain the list of TrueType fonts in these directories and
1219 * filter out all the font files we already know about from the registry.
1220 * What remains may be "bad" fonts, duplicate fonts, or perhaps the
1221 * missing font(s) we are looking for.
1222 * Open each of these files to find out.
1223 */
1224 private static void resolveWindowsFonts() {
1225
1226 ArrayList<String> unmappedFontNames = null;
1227 for (String font : fontToFamilyNameMap.keySet()) {
1228 String file = fontToFileMap.get(font);
1229 if (file == null) {
1230 if (font.indexOf(" ") > 0) {
1231 String newName = font.replaceFirst(" ", " ");
1232 file = fontToFileMap.get(newName);
1233 /* If this name exists and isn't for a valid name
1234 * replace the mapping to the file with this font
1235 */
1236 if (file != null &&
1237 !fontToFamilyNameMap.containsKey(newName)) {
1238 fontToFileMap.remove(newName);
1239 fontToFileMap.put(font, file);
1240 }
1241 } else if (font.equals("marlett")) {
1242 fontToFileMap.put(font, "marlett.ttf");
1243 } else if (font.equals("david")) {
1244 file = fontToFileMap.get("david regular");
1245 if (file != null) {
1246 fontToFileMap.remove("david regular");
1247 fontToFileMap.put("david", file);
1248 }
1249 } else {
1250 if (unmappedFontNames == null) {
1251 unmappedFontNames = new ArrayList<String>();
1252 }
1253 unmappedFontNames.add(font);
1254 }
1255 }
1256 }
1257
1258 if (unmappedFontNames != null) {
1259 HashSet<String> unmappedFontFiles = new HashSet<String>();
1260
1261 /* Every font key in fontToFileMap ought to correspond to a
1262 * font key in fontToFamilyNameMap. Entries that don't seem
1263 * to correspond are likely fonts that were named differently
1264 * by GDI than in the registry. One known cause of this is when
1265 * Windows has had its regional settings changed so that from
1266 * GDI we get a localised (eg Chinese or Japanese) name for the
1267 * font, but the registry retains the English version of the name
1268 * that corresponded to the "install" locale for windows.
1269 * Since we are in this code block because there are unmapped
1270 * font names, we can look to find unused font->file mappings
1271 * and then open the files to read the names. We don't generally
1272 * want to open font files, as its a performance hit, but this
1273 * occurs only for a small number of fonts on specific system
1274 * configs - ie is believed that a "true" Japanese windows would
1275 * have JA names in the registry too.
1276 * Clone fontToFileMap and remove from the clone all keys which
1277 * match a fontToFamilyNameMap key. What remains maps to the
1278 * files we want to open to find the fonts GDI returned.
1279 * A font in such a file is added to the fontToFileMap after
1280 * checking its one of the unmappedFontNames we are looking for.
1281 * The original name that didn't map is removed from fontToFileMap
1282 * so essentially this "fixes up" fontToFileMap to use the same
1283 * name as GDI.
1284 * Also note that typically the fonts for which this occurs in
1285 * CJK locales are TTC fonts and not all fonts in a TTC may have
1286 * localised names. Eg MSGOTHIC.TTC contains 3 fonts and one of
1287 * them "MS UI Gothic" has no JA name whereas the other two do.
1288 * So not every font in these files is unmapped or new.
1289 */
1290 HashMap<String,String> ffmapCopy =
1291 (HashMap<String,String>)(fontToFileMap.clone());
1292 for (String key : fontToFamilyNameMap.keySet()) {
1293 ffmapCopy.remove(key);
1294 }
1295 for (String key : ffmapCopy.keySet()) {
1296 unmappedFontFiles.add(ffmapCopy.get(key));
1297 fontToFileMap.remove(key);
1298 }
1299
1300 resolveFontFiles(unmappedFontFiles, unmappedFontNames);
1301
1302 /* If there are still unmapped font names, this means there's
1303 * something that wasn't in the registry. We need to get all
1304 * the font files directly and look at the ones that weren't
1305 * found in the registry.
1306 */
1307 if (unmappedFontNames.size() > 0) {
1308
1309 /* getFontFilesFromPath() returns all lower case names.
1310 * To compare we also need lower case
1311 * versions of the names from the registry.
1312 */
1313 ArrayList<String> registryFiles = new ArrayList<String>();
1314
1315 for (String regFile : fontToFileMap.values()) {
1316 registryFiles.add(regFile.toLowerCase());
1317 }
1318 /* We don't look for Type1 files here as windows will
1319 * not enumerate these, so aren't useful in reconciling
1320 * GDI's unmapped files. We do find these later when
1321 * we enumerate all fonts.
1322 */
1323 for (String pathFile : getFontFilesFromPath(true)) {
1324 if (!registryFiles.contains(pathFile)) {
1325 unmappedFontFiles.add(pathFile);
1326 }
1327 }
1328
1329 resolveFontFiles(unmappedFontFiles, unmappedFontNames);
1330 }
1331
1332 /* remove from the set of names that will be returned to the
1333 * user any fonts that can't be mapped to files.
1334 */
1335 if (unmappedFontNames.size() > 0) {
1336 int sz = unmappedFontNames.size();
1337 for (int i=0; i<sz; i++) {
1338 String name = unmappedFontNames.get(i);
1339 String familyName = fontToFamilyNameMap.get(name);
1340 if (familyName != null) {
1341 ArrayList family = familyToFontListMap.get(familyName);
1342 if (family != null) {
1343 if (family.size() <= 1) {
1344 familyToFontListMap.remove(familyName);
1345 }
1346 }
1347 }
1348 fontToFamilyNameMap.remove(name);
1349 if (logging) {
1350 logger.info("No file for font:" + name);
1351 }
1352 }
1353 }
1354 }
1355 }
1356
1357 /**
1358 * In some cases windows may have fonts in the fonts folder that
1359 * don't show up in the registry or in the GDI calls to enumerate fonts.
1360 * The only way to find these is to list the directory. We invoke this
1361 * only in getAllFonts/Families, so most searches for a specific
1362 * font that is satisfied by the GDI/registry calls don't take the
1363 * additional hit of listing the directory. This hit is small enough
1364 * that its not significant in these 'enumerate all the fonts' cases.
1365 * The basic approach is to cross-reference the files windows found
1366 * with the ones in the directory listing approach, and for each
1367 * in the latter list that is missing from the former list, register it.
1368 */
1369 private static synchronized void checkForUnreferencedFontFiles() {
1370 if (haveCheckedUnreferencedFontFiles) {
1371 return;
1372 }
1373 haveCheckedUnreferencedFontFiles = true;
1374 if (!isWindows) {
1375 return;
1376 }
1377 /* getFontFilesFromPath() returns all lower case names.
1378 * To compare we also need lower case
1379 * versions of the names from the registry.
1380 */
1381 ArrayList<String> registryFiles = new ArrayList<String>();
1382 for (String regFile : fontToFileMap.values()) {
1383 registryFiles.add(regFile.toLowerCase());
1384 }
1385
1386 /* To avoid any issues with concurrent modification, create
1387 * copies of the existing maps, add the new fonts into these
1388 * and then replace the references to the old ones with the
1389 * new maps. ConcurrentHashmap is another option but its a lot
1390 * more changes and with this exception, these maps are intended
1391 * to be static.
1392 */
1393 HashMap<String,String> fontToFileMap2 = null;
1394 HashMap<String,String> fontToFamilyNameMap2 = null;
1395 HashMap<String,ArrayList<String>> familyToFontListMap2 = null;;
1396
1397 for (String pathFile : getFontFilesFromPath(false)) {
1398 if (!registryFiles.contains(pathFile)) {
1399 if (logging) {
1400 logger.info("Found non-registry file : " + pathFile);
1401 }
1402 PhysicalFont f = registerFontFile(getPathName(pathFile));
1403 if (f == null) {
1404 continue;
1405 }
1406 if (fontToFileMap2 == null) {
1407 fontToFileMap2 = new HashMap<String,String>(fontToFileMap);
1408 fontToFamilyNameMap2 =
1409 new HashMap<String,String>(fontToFamilyNameMap);
1410 familyToFontListMap2 = new
1411 HashMap<String,ArrayList<String>>(familyToFontListMap);
1412 }
1413 String fontName = f.getFontName(null);
1414 String family = f.getFamilyName(null);
1415 String familyLC = family.toLowerCase();
1416 fontToFamilyNameMap2.put(fontName, family);
1417 fontToFileMap2.put(fontName, pathFile);
1418 ArrayList<String> fonts = familyToFontListMap2.get(familyLC);
1419 if (fonts == null) {
1420 fonts = new ArrayList<String>();
1421 } else {
1422 fonts = new ArrayList<String>(fonts);
1423 }
1424 fonts.add(fontName);
1425 familyToFontListMap2.put(familyLC, fonts);
1426 }
1427 }
1428 if (fontToFileMap2 != null) {
1429 fontToFileMap = fontToFileMap2;
1430 familyToFontListMap = familyToFontListMap2;
1431 fontToFamilyNameMap = fontToFamilyNameMap2;
1432 }
1433 }
1434
1435 private static void resolveFontFiles(HashSet<String> unmappedFiles,
1436 ArrayList<String> unmappedFonts) {
1437
1438 Locale l = SunToolkit.getStartupLocale();
1439
1440 for (String file : unmappedFiles) {
1441 try {
1442 int fn = 0;
1443 TrueTypeFont ttf;
1444 String fullPath = getPathName(file);
1445 if (logging) {
1446 logger.info("Trying to resolve file " + fullPath);
1447 }
1448 do {
1449 ttf = new TrueTypeFont(fullPath, null, fn++, true);
1450 // prefer the font's locale name.
1451 String fontName = ttf.getFontName(l).toLowerCase();
1452 if (unmappedFonts.contains(fontName)) {
1453 fontToFileMap.put(fontName, file);
1454 unmappedFonts.remove(fontName);
1455 if (logging) {
1456 logger.info("Resolved absent registry entry for " +
1457 fontName + " located in " + fullPath);
1458 }
1459 }
1460 }
1461 while (fn < ttf.getFontCount());
1462 } catch (Exception e) {
1463 }
1464 }
1465 }
1466
1467 private static synchronized HashMap<String,String> getFullNameToFileMap() {
1468 if (fontToFileMap == null) {
1469
1470 initSGEnv();
1471 pathDirs = sgEnv.getPlatformFontDirs();
1472
1473 fontToFileMap = new HashMap<String,String>(100);
1474 fontToFamilyNameMap = new HashMap<String,String>(100);
1475 familyToFontListMap = new HashMap<String,ArrayList<String>>(50);
1476 populateFontFileNameMap(fontToFileMap,
1477 fontToFamilyNameMap,
1478 familyToFontListMap,
1479 Locale.ENGLISH);
1480 if (isWindows) {
1481 resolveWindowsFonts();
1482 }
1483 if (logging) {
1484 logPlatformFontInfo();
1485 }
1486 }
1487 return fontToFileMap;
1488 }
1489
1490 private static void logPlatformFontInfo() {
1491 for (int i=0; i< pathDirs.length;i++) {
1492 logger.info("fontdir="+pathDirs[i]);
1493 }
1494 for (String keyName : fontToFileMap.keySet()) {
1495 logger.info("font="+keyName+" file="+ fontToFileMap.get(keyName));
1496 }
1497 for (String keyName : fontToFamilyNameMap.keySet()) {
1498 logger.info("font="+keyName+" family="+
1499 fontToFamilyNameMap.get(keyName));
1500 }
1501 for (String keyName : familyToFontListMap.keySet()) {
1502 logger.info("family="+keyName+ " fonts="+
1503 familyToFontListMap.get(keyName));
1504 }
1505 }
1506
1507 /* Note this return list excludes logical fonts and JRE fonts */
1508 public static String[] getFontNamesFromPlatform() {
1509 if (getFullNameToFileMap().size() == 0) {
1510 return null;
1511 }
1512 checkForUnreferencedFontFiles();
1513 /* This odd code with TreeMap is used to preserve a historical
1514 * behaviour wrt the sorting order .. */
1515 ArrayList<String> fontNames = new ArrayList<String>();
1516 for (ArrayList<String> a : familyToFontListMap.values()) {
1517 for (String s : a) {
1518 fontNames.add(s);
1519 }
1520 }
1521 return fontNames.toArray(STR_ARRAY);
1522 }
1523
1524 public static boolean gotFontsFromPlatform() {
1525 return getFullNameToFileMap().size() != 0;
1526 }
1527
1528 public static String getFileNameForFontName(String fontName) {
1529 String fontNameLC = fontName.toLowerCase(Locale.ENGLISH);
1530 return fontToFileMap.get(fontNameLC);
1531 }
1532
1533 private static PhysicalFont registerFontFile(String file) {
1534 if (new File(file).isAbsolute() &&
1535 !registeredFontFiles.contains(file)) {
1536 int fontFormat = FONTFORMAT_NONE;
1537 int fontRank = Font2D.UNKNOWN_RANK;
1538 if (SunGraphicsEnvironment.ttFilter.accept(null, file)) {
1539 fontFormat = FONTFORMAT_TRUETYPE;
1540 fontRank = Font2D.TTF_RANK;
1541 } else if
1542 (SunGraphicsEnvironment.t1Filter.accept(null, file)) {
1543 fontFormat = FONTFORMAT_TYPE1;
1544 fontRank = Font2D.TYPE1_RANK;
1545 }
1546 if (fontFormat == FONTFORMAT_NONE) {
1547 return null;
1548 }
1549 return registerFontFile(file, null, fontFormat, false, fontRank);
1550 }
1551 return null;
1552 }
1553
1554 /* Used to register any font files that are found by platform APIs
1555 * that weren't previously found in the standard font locations.
1556 * the isAbsolute() check is needed since that's whats stored in the
1557 * set, and on windows, the fonts in the system font directory that
1558 * are in the fontToFileMap are just basenames. We don't want to try
1559 * to register those again, but we do want to register other registry
1560 * installed fonts.
1561 */
1562 public static void registerOtherFontFiles(HashSet registeredFontFiles) {
1563 if (getFullNameToFileMap().size() == 0) {
1564 return;
1565 }
1566 for (String file : fontToFileMap.values()) {
1567 registerFontFile(file);
1568 }
1569 }
1570
1571 public static boolean
1572 getFamilyNamesFromPlatform(TreeMap<String,String> familyNames,
1573 Locale requestedLocale) {
1574 if (getFullNameToFileMap().size() == 0) {
1575 return false;
1576 }
1577 checkForUnreferencedFontFiles();
1578 for (String name : fontToFamilyNameMap.values()) {
1579 familyNames.put(name.toLowerCase(requestedLocale), name);
1580 }
1581 return true;
1582 }
1583
1584 /* Path may be absolute or a base file name relative to one of
1585 * the platform font directories
1586 */
1587 private static String getPathName(String s) {
1588 File f = new File(s);
1589 if (f.isAbsolute()) {
1590 return s;
1591 } else if (pathDirs.length==1) {
1592 return pathDirs[0] + File.separator + s;
1593 } else {
1594 for (int p=0; p<pathDirs.length; p++) {
1595 f = new File(pathDirs[p] + File.separator + s);
1596 if (f.exists()) {
1597 return f.getAbsolutePath();
1598 }
1599 }
1600 }
1601 return s; // shouldn't happen, but harmless
1602 }
1603
1604 /* lcName is required to be lower case for use as a key.
1605 * lcName may be a full name, or a family name, and style may
1606 * be specified in addition to either of these. So be sure to
1607 * get the right one. Since an app *could* ask for "Foo Regular"
1608 * and later ask for "Foo Italic", if we don't register all the
1609 * styles, then logic in findFont2D may try to style the original
1610 * so we register the entire family if we get a match here.
1611 * This is still a big win because this code is invoked where
1612 * otherwise we would register all fonts.
1613 * It's also useful for the case where "Foo Bold" was specified with
1614 * style Font.ITALIC, as we would want in that case to try to return
1615 * "Foo Bold Italic" if it exists, and it is only by locating "Foo Bold"
1616 * and opening it that we really "know" it's Bold, and can look for
1617 * a font that supports that and the italic style.
1618 * The code in here is not overtly windows-specific but in fact it
1619 * is unlikely to be useful as is on other platforms. It is maintained
1620 * in this shared source file to be close to its sole client and
1621 * because so much of the logic is intertwined with the logic in
1622 * findFont2D.
1623 */
1624 private static Font2D findFontFromPlatform(String lcName, int style) {
1625 if (getFullNameToFileMap().size() == 0) {
1626 return null;
1627 }
1628
1629 ArrayList<String> family = null;
1630 String fontFile = null;
1631 String familyName = fontToFamilyNameMap.get(lcName);
1632 if (familyName != null) {
1633 fontFile = fontToFileMap.get(lcName);
1634 family = familyToFontListMap.get
1635 (familyName.toLowerCase(Locale.ENGLISH));
1636 } else {
1637 family = familyToFontListMap.get(lcName); // is lcName is a family?
1638 if (family != null && family.size() > 0) {
1639 String lcFontName = family.get(0).toLowerCase(Locale.ENGLISH);
1640 if (lcFontName != null) {
1641 familyName = fontToFamilyNameMap.get(lcFontName);
1642 }
1643 }
1644 }
1645 if (family == null || familyName == null) {
1646 return null;
1647 }
1648 String [] fontList = (String[])family.toArray(STR_ARRAY);
1649 if (fontList.length == 0) {
1650 return null;
1651 }
1652
1653 /* first check that for every font in this family we can find
1654 * a font file. The specific reason for doing this is that
1655 * in at least one case on Windows a font has the face name "David"
1656 * but the registry entry is "David Regular". That is the "unique"
1657 * name of the font but in other cases the registry contains the
1658 * "full" name. See the specifications of name ids 3 and 4 in the
1659 * TrueType 'name' table.
1660 * In general this could cause a problem that we fail to register
1661 * if we all members of a family that we may end up mapping to
1662 * the wrong font member: eg return Bold when Plain is needed.
1663 */
1664 for (int f=0;f<fontList.length;f++) {
1665 String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
1666 String fileName = fontToFileMap.get(fontNameLC);
1667 if (fileName == null) {
1668 if (logging) {
1669 logger.info("Platform lookup : No file for font " +
1670 fontList[f] + " in family " +familyName);
1671 }
1672 return null;
1673 }
1674 }
1675
1676 /* Currently this code only looks for TrueType fonts, so format
1677 * and rank can be specified without looking at the filename.
1678 */
1679 PhysicalFont physicalFont = null;
1680 if (fontFile != null) {
1681 physicalFont = registerFontFile(getPathName(fontFile), null,
1682 FONTFORMAT_TRUETYPE, false,
1683 Font2D.TTF_RANK);
1684 }
1685 /* Register all fonts in this family. */
1686 for (int f=0;f<fontList.length;f++) {
1687 String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
1688 String fileName = fontToFileMap.get(fontNameLC);
1689 if (fontFile != null && fontFile.equals(fileName)) {
1690 continue;
1691 }
1692 /* Currently this code only looks for TrueType fonts, so format
1693 * and rank can be specified without looking at the filename.
1694 */
1695 registerFontFile(getPathName(fileName), null,
1696 FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK);
1697 }
1698
1699 Font2D font = null;
1700 FontFamily fontFamily = FontFamily.getFamily(familyName);
1701 /* Handle case where request "MyFont Bold", style=Font.ITALIC */
1702 if (physicalFont != null) {
1703 style |= physicalFont.style;
1704 }
1705 if (fontFamily != null) {
1706 font = fontFamily.getFont(style);
1707 if (font == null) {
1708 font = fontFamily.getClosestStyle(style);
1709 }
1710 }
1711 return font;
1712 }
1713
1714 private static ConcurrentHashMap<String, Font2D> fontNameCache =
1715 new ConcurrentHashMap<String, Font2D>();
1716
1717 /*
1718 * The client supplies a name and a style.
1719 * The name could be a family name, or a full name.
1720 * A font may exist with the specified style, or it may
1721 * exist only in some other style. For non-native fonts the scaler
1722 * may be able to emulate the required style.
1723 */
1724 public static Font2D findFont2D(String name, int style, int fallback) {
1725 String lowerCaseName = name.toLowerCase(Locale.ENGLISH);
1726 String mapName = lowerCaseName + dotStyleStr(style);
1727 Font2D font;
1728
1729 /* If preferLocaleFonts() or preferProportionalFonts() has been
1730 * called we may be using an alternate set of composite fonts in this
1731 * app context. The presence of a pre-built name map indicates whether
1732 * this is so, and gives access to the alternate composite for the
1733 * name.
1734 */
1735 if (usingPerAppContextComposites) {
1736 ConcurrentHashMap<String, Font2D> altNameCache =
1737 (ConcurrentHashMap<String, Font2D>)
1738 AppContext.getAppContext().get(CompositeFont.class);
1739 if (altNameCache != null) {
1740 font = (Font2D)altNameCache.get(mapName);
1741 } else {
1742 font = null;
1743 }
1744 } else {
1745 font = fontNameCache.get(mapName);
1746 }
1747 if (font != null) {
1748 return font;
1749 }
1750
1751 if (logging) {
1752 logger.info("Search for font: " + name);
1753 }
1754
1755 // The check below is just so that the bitmap fonts being set by
1756 // AWT and Swing thru the desktop properties do not trigger the
1757 // the load fonts case. The two bitmap fonts are now mapped to
1758 // appropriate equivalents for serif and sansserif.
1759 // Note that the cost of this comparison is only for the first
1760 // call until the map is filled.
1761 if (isWindows) {
1762 if (lowerCaseName.equals("ms sans serif")) {
1763 name = "sansserif";
1764 } else if (lowerCaseName.equals("ms serif")) {
1765 name = "serif";
1766 }
1767 }
1768
1769 /* This isn't intended to support a client passing in the
1770 * string default, but if a client passes in null for the name
1771 * the java.awt.Font class internally substitutes this name.
1772 * So we need to recognise it here to prevent a loadFonts
1773 * on the unrecognised name. The only potential problem with
1774 * this is it would hide any real font called "default"!
1775 * But that seems like a potential problem we can ignore for now.
1776 */
1777 if (lowerCaseName.equals("default")) {
1778 name = "dialog";
1779 }
1780
1781 /* First see if its a family name. */
1782 FontFamily family = FontFamily.getFamily(name);
1783 if (family != null) {
1784 font = family.getFontWithExactStyleMatch(style);
1785 if (font == null) {
1786 font = findDeferredFont(name, style);
1787 }
1788 if (font == null) {
1789 font = family.getFont(style);
1790 }
1791 if (font == null) {
1792 font = family.getClosestStyle(style);
1793 }
1794 if (font != null) {
1795 fontNameCache.put(mapName, font);
1796 return font;
1797 }
1798 }
1799
1800 /* If it wasn't a family name, it should be a full name of
1801 * either a composite, or a physical font
1802 */
1803 font = fullNameToFont.get(lowerCaseName);
1804 if (font != null) {
1805 /* Check that the requested style matches the matched font's style.
1806 * But also match style automatically if the requested style is
1807 * "plain". This because the existing behaviour is that the fonts
1808 * listed via getAllFonts etc always list their style as PLAIN.
1809 * This does lead to non-commutative behaviours where you might
1810 * start with "Lucida Sans Regular" and ask for a BOLD version
1811 * and get "Lucida Sans DemiBold" but if you ask for the PLAIN
1812 * style of "Lucida Sans DemiBold" you get "Lucida Sans DemiBold".
1813 * This consistent however with what happens if you have a bold
1814 * version of a font and no plain version exists - alg. styling
1815 * doesn't "unbolden" the font.
1816 */
1817 if (font.style == style || style == Font.PLAIN) {
1818 fontNameCache.put(mapName, font);
1819 return font;
1820 } else {
1821 /* If it was a full name like "Lucida Sans Regular", but
1822 * the style requested is "bold", then we want to see if
1823 * there's the appropriate match against another font in
1824 * that family before trying to load all fonts, or applying a
1825 * algorithmic styling
1826 */
1827 family = FontFamily.getFamily(font.getFamilyName(null));
1828 if (family != null) {
1829 Font2D familyFont = family.getFont(style|font.style);
1830 /* We exactly matched the requested style, use it! */
1831 if (familyFont != null) {
1832 fontNameCache.put(mapName, familyFont);
1833 return familyFont;
1834 } else {
1835 /* This next call is designed to support the case
1836 * where bold italic is requested, and if we must
1837 * style, then base it on either bold or italic -
1838 * not on plain!
1839 */
1840 familyFont = family.getClosestStyle(style|font.style);
1841 if (familyFont != null) {
1842 /* The next check is perhaps one
1843 * that shouldn't be done. ie if we get this
1844 * far we have probably as close a match as we
1845 * are going to get. We could load all fonts to
1846 * see if somehow some parts of the family are
1847 * loaded but not all of it.
1848 */
1849 if (familyFont.canDoStyle(style|font.style)) {
1850 fontNameCache.put(mapName, familyFont);
1851 return familyFont;
1852 }
1853 }
1854 }
1855 }
1856 }
1857 }
1858
1859 /* If reach here its possible that this is in a client which never
1860 * loaded the GraphicsEnvironment, so we haven't even loaded ANY of
1861 * the fonts from the environment. Do so now and recurse.
1862 */
1863 if (sgEnv == null) {
1864 initSGEnv();
1865 return findFont2D(name, style, fallback);
1866 }
1867
1868 if (isWindows) {
1869 /* Don't want Windows to return a Lucida Sans font from
1870 * C:\Windows\Fonts
1871 */
1872 if (deferredFontFiles.size() > 0) {
1873 font = findJREDeferredFont(lowerCaseName, style);
1874 if (font != null) {
1875 fontNameCache.put(mapName, font);
1876 return font;
1877 }
1878 }
1879 font = findFontFromPlatform(lowerCaseName, style);
1880 if (font != null) {
1881 if (logging) {
1882 logger.info("Found font via platform API for request:\"" +
1883 name + "\":, style="+style+
1884 " found font: " + font);
1885 }
1886 fontNameCache.put(mapName, font);
1887 return font;
1888 }
1889 }
1890
1891 /* If reach here and no match has been located, then if there are
1892 * uninitialised deferred fonts, load as many of those as needed
1893 * to find the deferred font. If none is found through that
1894 * search continue on.
1895 * There is possibly a minor issue when more than one
1896 * deferred font implements the same font face. Since deferred
1897 * fonts are only those in font configuration files, this is a
1898 * controlled situation, the known case being Solaris euro_fonts
1899 * versions of Arial, Times New Roman, Courier New. However
1900 * the larger font will transparently replace the smaller one
1901 * - see addToFontList() - when it is needed by the composite font.
1902 */
1903 if (deferredFontFiles.size() > 0) {
1904 font = findDeferredFont(name, style);
1905 if (font != null) {
1906 fontNameCache.put(mapName, font);
1907 return font;
1908 }
1909 }
1910
1911 /* Some apps use deprecated 1.0 names such as helvetica and courier. On
1912 * Solaris these are Type1 fonts in /usr/openwin/lib/X11/fonts/Type1.
1913 * If running on Solaris will register all the fonts in this
1914 * directory.
1915 * May as well register the whole directory without actually testing
1916 * the font name is one of the deprecated names as the next step would
1917 * load all fonts which are in this directory anyway.
1918 * In the event that this lookup is successful it potentially "hides"
1919 * TrueType versions of such fonts that are elsewhere but since they
1920 * do not exist on Solaris this is not a problem.
1921 * Set a flag to indicate we've done this registration to avoid
1922 * repetition and more seriously, to avoid recursion.
1923 */
1924 if (isSolaris&&!loaded1dot0Fonts) {
1925 /* "timesroman" is a special case since that's not the
1926 * name of any known font on Solaris or elsewhere.
1927 */
1928 if (lowerCaseName.equals("timesroman")) {
1929 font = findFont2D("serif", style, fallback);
1930 fontNameCache.put(mapName, font);
1931 }
1932 sgEnv.register1dot0Fonts();
1933 loaded1dot0Fonts = true;
1934 Font2D ff = findFont2D(name, style, fallback);
1935 return ff;
1936 }
1937
1938 /* We check for application registered fonts before
1939 * explicitly loading all fonts as if necessary the registration
1940 * code will have done so anyway. And we don't want to needlessly
1941 * load the actual files for all fonts.
1942 * Just as for installed fonts we check for family before fullname.
1943 * We do not add these fonts to fontNameCache for the
1944 * app context case which eliminates the overhead of a per context
1945 * cache for these.
1946 */
1947
1948 if (fontsAreRegistered || fontsAreRegisteredPerAppContext) {
1949 Hashtable<String, FontFamily> familyTable = null;
1950 Hashtable<String, Font2D> nameTable;
1951
1952 if (fontsAreRegistered) {
1953 familyTable = createdByFamilyName;
1954 nameTable = createdByFullName;
1955 } else {
1956 AppContext appContext = AppContext.getAppContext();
1957 familyTable =
1958 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
1959 nameTable =
1960 (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
1961 }
1962
1963 family = familyTable.get(lowerCaseName);
1964 if (family != null) {
1965 font = family.getFontWithExactStyleMatch(style);
1966 if (font == null) {
1967 font = family.getFont(style);
1968 }
1969 if (font == null) {
1970 font = family.getClosestStyle(style);
1971 }
1972 if (font != null) {
1973 if (fontsAreRegistered) {
1974 fontNameCache.put(mapName, font);
1975 }
1976 return font;
1977 }
1978 }
1979 font = nameTable.get(lowerCaseName);
1980 if (font != null) {
1981 if (fontsAreRegistered) {
1982 fontNameCache.put(mapName, font);
1983 }
1984 return font;
1985 }
1986 }
1987
1988 /* If reach here and no match has been located, then if all fonts
1989 * are not yet loaded, do so, and then recurse.
1990 */
1991 if (!loadedAllFonts) {
1992 if (logging) {
1993 logger.info("Load fonts looking for:" + name);
1994 }
1995 sgEnv.loadFonts();
1996 loadedAllFonts = true;
1997 return findFont2D(name, style, fallback);
1998 }
1999
2000 if (!loadedAllFontFiles) {
2001 if (logging) {
2002 logger.info("Load font files looking for:" + name);
2003 }
2004 sgEnv.loadFontFiles();
2005 loadedAllFontFiles = true;
2006 return findFont2D(name, style, fallback);
2007 }
2008
2009 /* The primary name is the locale default - ie not US/English but
2010 * whatever is the default in this locale. This is the way it always
2011 * has been but may be surprising to some developers if "Arial Regular"
2012 * were hard-coded in their app and yet "Arial Regular" was not the
2013 * default name. Fortunately for them, as a consequence of the JDK
2014 * supporting returning names and family names for arbitrary locales,
2015 * we also need to support searching all localised names for a match.
2016 * But because this case of the name used to reference a font is not
2017 * the same as the default for this locale is rare, it makes sense to
2018 * search a much shorter list of default locale names and only go to
2019 * a longer list of names in the event that no match was found.
2020 * So add here code which searches localised names too.
2021 * As in 1.4.x this happens only after loading all fonts, which
2022 * is probably the right order.
2023 */
2024 if ((font = findFont2DAllLocales(name, style)) != null) {
2025 fontNameCache.put(mapName, font);
2026 return font;
2027 }
2028
2029 /* Perhaps its a "compatibility" name - timesroman, helvetica,
2030 * or courier, which 1.0 apps used for logical fonts.
2031 * We look for these "late" after a loadFonts as we must not
2032 * hide real fonts of these names.
2033 * Map these appropriately:
2034 * On windows this means according to the rules specified by the
2035 * FontConfiguration : do it only for encoding==Cp1252
2036 *
2037 * REMIND: this is something we plan to remove.
2038 */
2039 if (isWindows) {
2040 String compatName =
2041 sgEnv.getFontConfiguration().getFallbackFamilyName(name, null);
2042 if (compatName != null) {
2043 font = findFont2D(compatName, style, fallback);
2044 fontNameCache.put(mapName, font);
2045 return font;
2046 }
2047 } else if (lowerCaseName.equals("timesroman")) {
2048 font = findFont2D("serif", style, fallback);
2049 fontNameCache.put(mapName, font);
2050 return font;
2051 } else if (lowerCaseName.equals("helvetica")) {
2052 font = findFont2D("sansserif", style, fallback);
2053 fontNameCache.put(mapName, font);
2054 return font;
2055 } else if (lowerCaseName.equals("courier")) {
2056 font = findFont2D("monospaced", style, fallback);
2057 fontNameCache.put(mapName, font);
2058 return font;
2059 }
2060
2061 if (logging) {
2062 logger.info("No font found for:" + name);
2063 }
2064
2065 switch (fallback) {
2066 case PHYSICAL_FALLBACK: return getDefaultPhysicalFont();
2067 case LOGICAL_FALLBACK: return getDefaultLogicalFont(style);
2068 default: return null;
2069 }
2070 }
2071
2072 /* This method can be more efficient as it will only need to
2073 * do the lookup once, and subsequent calls on the java.awt.Font
2074 * instance can utilise the cached Font2D on that object.
2075 * Its unfortunate it needs to be a native method, but the font2D
2076 * variable has to be private.
2077 */
2078 public static native Font2D getFont2D(Font font);
2079
2080 /* Stuff below was in NativeFontWrapper and needed a new home */
2081
2082 /*
2083 * Workaround for apps which are dependent on a font metrics bug
2084 * in JDK 1.1. This is an unsupported win32 private setting.
2085 */
2086 public static boolean usePlatformFontMetrics() {
2087 return usePlatformFontMetrics;
2088 }
2089
2090 static native boolean getPlatformFontVar();
2091
2092 private static final short US_LCID = 0x0409; // US English - default
2093 private static Map<String, Short> lcidMap;
2094
2095 // Return a Microsoft LCID from the given Locale.
2096 // Used when getting localized font data.
2097
2098 public static short getLCIDFromLocale(Locale locale) {
2099 // optimize for common case
2100 if (locale.equals(Locale.US)) {
2101 return US_LCID;
2102 }
2103
2104 if (lcidMap == null) {
2105 createLCIDMap();
2106 }
2107
2108 String key = locale.toString();
2109 while (!"".equals(key)) {
2110 Short lcidObject = (Short) lcidMap.get(key);
2111 if (lcidObject != null) {
2112 return lcidObject.shortValue();
2113 }
2114 int pos = key.lastIndexOf('_');
2115 if (pos < 1) {
2116 return US_LCID;
2117 }
2118 key = key.substring(0, pos);
2119 }
2120
2121 return US_LCID;
2122 }
2123
2124
2125 private static void addLCIDMapEntry(Map<String, Short> map,
2126 String key, short value) {
2127 map.put(key, new Short(value));
2128 }
2129
2130 private static synchronized void createLCIDMap() {
2131 if (lcidMap != null) {
2132 return;
2133 }
2134
2135 Map<String, Short> map = new HashMap<String, Short>(200);
2136
2137 // the following statements are derived from the langIDMap
2138 // in src/windows/native/java/lang/java_props_md.c using the following
2139 // awk script:
2140 // $1~/\/\*/ { next}
2141 // $3~/\?\?/ { next }
2142 // $3!~/_/ { next }
2143 // $1~/0x0409/ { next }
2144 // $1~/0x0c0a/ { next }
2145 // $1~/0x042c/ { next }
2146 // $1~/0x0443/ { next }
2147 // $1~/0x0812/ { next }
2148 // $1~/0x04/ { print " addLCIDMapEntry(map, " substr($3, 0, 3) "\", (short) " substr($1, 0, 6) ");" ; next }
2149 // $3~/,/ { print " addLCIDMapEntry(map, " $3 " (short) " substr($1, 0, 6) ");" ; next }
2150 // { print " addLCIDMapEntry(map, " $3 ", (short) " substr($1, 0, 6) ");" ; next }
2151 // The lines of this script:
2152 // - eliminate comments
2153 // - eliminate questionable locales
2154 // - eliminate language-only locales
2155 // - eliminate the default LCID value
2156 // - eliminate a few other unneeded LCID values
2157 // - print language-only locale entries for x04* LCID values
2158 // (apparently Microsoft doesn't use language-only LCID values -
2159 // see http://www.microsoft.com/OpenType/otspec/name.htm
2160 // - print complete entries for all other LCID values
2161 // Run
2162 // awk -f awk-script langIDMap > statements
2163 addLCIDMapEntry(map, "ar", (short) 0x0401);
2164 addLCIDMapEntry(map, "bg", (short) 0x0402);
2165 addLCIDMapEntry(map, "ca", (short) 0x0403);
2166 addLCIDMapEntry(map, "zh", (short) 0x0404);
2167 addLCIDMapEntry(map, "cs", (short) 0x0405);
2168 addLCIDMapEntry(map, "da", (short) 0x0406);
2169 addLCIDMapEntry(map, "de", (short) 0x0407);
2170 addLCIDMapEntry(map, "el", (short) 0x0408);
2171 addLCIDMapEntry(map, "es", (short) 0x040a);
2172 addLCIDMapEntry(map, "fi", (short) 0x040b);
2173 addLCIDMapEntry(map, "fr", (short) 0x040c);
2174 addLCIDMapEntry(map, "iw", (short) 0x040d);
2175 addLCIDMapEntry(map, "hu", (short) 0x040e);
2176 addLCIDMapEntry(map, "is", (short) 0x040f);
2177 addLCIDMapEntry(map, "it", (short) 0x0410);
2178 addLCIDMapEntry(map, "ja", (short) 0x0411);
2179 addLCIDMapEntry(map, "ko", (short) 0x0412);
2180 addLCIDMapEntry(map, "nl", (short) 0x0413);
2181 addLCIDMapEntry(map, "no", (short) 0x0414);
2182 addLCIDMapEntry(map, "pl", (short) 0x0415);
2183 addLCIDMapEntry(map, "pt", (short) 0x0416);
2184 addLCIDMapEntry(map, "rm", (short) 0x0417);
2185 addLCIDMapEntry(map, "ro", (short) 0x0418);
2186 addLCIDMapEntry(map, "ru", (short) 0x0419);
2187 addLCIDMapEntry(map, "hr", (short) 0x041a);
2188 addLCIDMapEntry(map, "sk", (short) 0x041b);
2189 addLCIDMapEntry(map, "sq", (short) 0x041c);
2190 addLCIDMapEntry(map, "sv", (short) 0x041d);
2191 addLCIDMapEntry(map, "th", (short) 0x041e);
2192 addLCIDMapEntry(map, "tr", (short) 0x041f);
2193 addLCIDMapEntry(map, "ur", (short) 0x0420);
2194 addLCIDMapEntry(map, "in", (short) 0x0421);
2195 addLCIDMapEntry(map, "uk", (short) 0x0422);
2196 addLCIDMapEntry(map, "be", (short) 0x0423);
2197 addLCIDMapEntry(map, "sl", (short) 0x0424);
2198 addLCIDMapEntry(map, "et", (short) 0x0425);
2199 addLCIDMapEntry(map, "lv", (short) 0x0426);
2200 addLCIDMapEntry(map, "lt", (short) 0x0427);
2201 addLCIDMapEntry(map, "fa", (short) 0x0429);
2202 addLCIDMapEntry(map, "vi", (short) 0x042a);
2203 addLCIDMapEntry(map, "hy", (short) 0x042b);
2204 addLCIDMapEntry(map, "eu", (short) 0x042d);
2205 addLCIDMapEntry(map, "mk", (short) 0x042f);
2206 addLCIDMapEntry(map, "tn", (short) 0x0432);
2207 addLCIDMapEntry(map, "xh", (short) 0x0434);
2208 addLCIDMapEntry(map, "zu", (short) 0x0435);
2209 addLCIDMapEntry(map, "af", (short) 0x0436);
2210 addLCIDMapEntry(map, "ka", (short) 0x0437);
2211 addLCIDMapEntry(map, "fo", (short) 0x0438);
2212 addLCIDMapEntry(map, "hi", (short) 0x0439);
2213 addLCIDMapEntry(map, "mt", (short) 0x043a);
2214 addLCIDMapEntry(map, "se", (short) 0x043b);
2215 addLCIDMapEntry(map, "gd", (short) 0x043c);
2216 addLCIDMapEntry(map, "ms", (short) 0x043e);
2217 addLCIDMapEntry(map, "kk", (short) 0x043f);
2218 addLCIDMapEntry(map, "ky", (short) 0x0440);
2219 addLCIDMapEntry(map, "sw", (short) 0x0441);
2220 addLCIDMapEntry(map, "tt", (short) 0x0444);
2221 addLCIDMapEntry(map, "bn", (short) 0x0445);
2222 addLCIDMapEntry(map, "pa", (short) 0x0446);
2223 addLCIDMapEntry(map, "gu", (short) 0x0447);
2224 addLCIDMapEntry(map, "ta", (short) 0x0449);
2225 addLCIDMapEntry(map, "te", (short) 0x044a);
2226 addLCIDMapEntry(map, "kn", (short) 0x044b);
2227 addLCIDMapEntry(map, "ml", (short) 0x044c);
2228 addLCIDMapEntry(map, "mr", (short) 0x044e);
2229 addLCIDMapEntry(map, "sa", (short) 0x044f);
2230 addLCIDMapEntry(map, "mn", (short) 0x0450);
2231 addLCIDMapEntry(map, "cy", (short) 0x0452);
2232 addLCIDMapEntry(map, "gl", (short) 0x0456);
2233 addLCIDMapEntry(map, "dv", (short) 0x0465);
2234 addLCIDMapEntry(map, "qu", (short) 0x046b);
2235 addLCIDMapEntry(map, "mi", (short) 0x0481);
2236 addLCIDMapEntry(map, "ar_IQ", (short) 0x0801);
2237 addLCIDMapEntry(map, "zh_CN", (short) 0x0804);
2238 addLCIDMapEntry(map, "de_CH", (short) 0x0807);
2239 addLCIDMapEntry(map, "en_GB", (short) 0x0809);
2240 addLCIDMapEntry(map, "es_MX", (short) 0x080a);
2241 addLCIDMapEntry(map, "fr_BE", (short) 0x080c);
2242 addLCIDMapEntry(map, "it_CH", (short) 0x0810);
2243 addLCIDMapEntry(map, "nl_BE", (short) 0x0813);
2244 addLCIDMapEntry(map, "no_NO_NY", (short) 0x0814);
2245 addLCIDMapEntry(map, "pt_PT", (short) 0x0816);
2246 addLCIDMapEntry(map, "ro_MD", (short) 0x0818);
2247 addLCIDMapEntry(map, "ru_MD", (short) 0x0819);
2248 addLCIDMapEntry(map, "sr_CS", (short) 0x081a);
2249 addLCIDMapEntry(map, "sv_FI", (short) 0x081d);
2250 addLCIDMapEntry(map, "az_AZ", (short) 0x082c);
2251 addLCIDMapEntry(map, "se_SE", (short) 0x083b);
2252 addLCIDMapEntry(map, "ga_IE", (short) 0x083c);
2253 addLCIDMapEntry(map, "ms_BN", (short) 0x083e);
2254 addLCIDMapEntry(map, "uz_UZ", (short) 0x0843);
2255 addLCIDMapEntry(map, "qu_EC", (short) 0x086b);
2256 addLCIDMapEntry(map, "ar_EG", (short) 0x0c01);
2257 addLCIDMapEntry(map, "zh_HK", (short) 0x0c04);
2258 addLCIDMapEntry(map, "de_AT", (short) 0x0c07);
2259 addLCIDMapEntry(map, "en_AU", (short) 0x0c09);
2260 addLCIDMapEntry(map, "fr_CA", (short) 0x0c0c);
2261 addLCIDMapEntry(map, "sr_CS", (short) 0x0c1a);
2262 addLCIDMapEntry(map, "se_FI", (short) 0x0c3b);
2263 addLCIDMapEntry(map, "qu_PE", (short) 0x0c6b);
2264 addLCIDMapEntry(map, "ar_LY", (short) 0x1001);
2265 addLCIDMapEntry(map, "zh_SG", (short) 0x1004);
2266 addLCIDMapEntry(map, "de_LU", (short) 0x1007);
2267 addLCIDMapEntry(map, "en_CA", (short) 0x1009);
2268 addLCIDMapEntry(map, "es_GT", (short) 0x100a);
2269 addLCIDMapEntry(map, "fr_CH", (short) 0x100c);
2270 addLCIDMapEntry(map, "hr_BA", (short) 0x101a);
2271 addLCIDMapEntry(map, "ar_DZ", (short) 0x1401);
2272 addLCIDMapEntry(map, "zh_MO", (short) 0x1404);
2273 addLCIDMapEntry(map, "de_LI", (short) 0x1407);
2274 addLCIDMapEntry(map, "en_NZ", (short) 0x1409);
2275 addLCIDMapEntry(map, "es_CR", (short) 0x140a);
2276 addLCIDMapEntry(map, "fr_LU", (short) 0x140c);
2277 addLCIDMapEntry(map, "bs_BA", (short) 0x141a);
2278 addLCIDMapEntry(map, "ar_MA", (short) 0x1801);
2279 addLCIDMapEntry(map, "en_IE", (short) 0x1809);
2280 addLCIDMapEntry(map, "es_PA", (short) 0x180a);
2281 addLCIDMapEntry(map, "fr_MC", (short) 0x180c);
2282 addLCIDMapEntry(map, "sr_BA", (short) 0x181a);
2283 addLCIDMapEntry(map, "ar_TN", (short) 0x1c01);
2284 addLCIDMapEntry(map, "en_ZA", (short) 0x1c09);
2285 addLCIDMapEntry(map, "es_DO", (short) 0x1c0a);
2286 addLCIDMapEntry(map, "sr_BA", (short) 0x1c1a);
2287 addLCIDMapEntry(map, "ar_OM", (short) 0x2001);
2288 addLCIDMapEntry(map, "en_JM", (short) 0x2009);
2289 addLCIDMapEntry(map, "es_VE", (short) 0x200a);
2290 addLCIDMapEntry(map, "ar_YE", (short) 0x2401);
2291 addLCIDMapEntry(map, "es_CO", (short) 0x240a);
2292 addLCIDMapEntry(map, "ar_SY", (short) 0x2801);
2293 addLCIDMapEntry(map, "en_BZ", (short) 0x2809);
2294 addLCIDMapEntry(map, "es_PE", (short) 0x280a);
2295 addLCIDMapEntry(map, "ar_JO", (short) 0x2c01);
2296 addLCIDMapEntry(map, "en_TT", (short) 0x2c09);
2297 addLCIDMapEntry(map, "es_AR", (short) 0x2c0a);
2298 addLCIDMapEntry(map, "ar_LB", (short) 0x3001);
2299 addLCIDMapEntry(map, "en_ZW", (short) 0x3009);
2300 addLCIDMapEntry(map, "es_EC", (short) 0x300a);
2301 addLCIDMapEntry(map, "ar_KW", (short) 0x3401);
2302 addLCIDMapEntry(map, "en_PH", (short) 0x3409);
2303 addLCIDMapEntry(map, "es_CL", (short) 0x340a);
2304 addLCIDMapEntry(map, "ar_AE", (short) 0x3801);
2305 addLCIDMapEntry(map, "es_UY", (short) 0x380a);
2306 addLCIDMapEntry(map, "ar_BH", (short) 0x3c01);
2307 addLCIDMapEntry(map, "es_PY", (short) 0x3c0a);
2308 addLCIDMapEntry(map, "ar_QA", (short) 0x4001);
2309 addLCIDMapEntry(map, "es_BO", (short) 0x400a);
2310 addLCIDMapEntry(map, "es_SV", (short) 0x440a);
2311 addLCIDMapEntry(map, "es_HN", (short) 0x480a);
2312 addLCIDMapEntry(map, "es_NI", (short) 0x4c0a);
2313 addLCIDMapEntry(map, "es_PR", (short) 0x500a);
2314
2315 lcidMap = map;
2316 }
2317
2318 public static int getNumFonts() {
2319 return physicalFonts.size()+maxCompFont;
2320 }
2321
2322 private static boolean fontSupportsEncoding(Font font, String encoding) {
2323 return getFont2D(font).supportsEncoding(encoding);
2324 }
2325
2326 public synchronized static native String getFontPath(boolean noType1Fonts);
2327 public synchronized static native void setNativeFontPath(String fontPath);
2328
2329
2330 private static Thread fileCloser = null;
2331 static Vector<File> tmpFontFiles = null;
2332
2333 public static Font2D createFont2D(File fontFile, int fontFormat,
2334 boolean isCopy)
2335 throws FontFormatException {
2336
2337 String fontFilePath = fontFile.getPath();
2338 FileFont font2D = null;
2339 final File fFile = fontFile;
2340 try {
2341 switch (fontFormat) {
2342 case Font.TRUETYPE_FONT:
2343 font2D = new TrueTypeFont(fontFilePath, null, 0, true);
2344 break;
2345 case Font.TYPE1_FONT:
2346 font2D = new Type1Font(fontFilePath, null);
2347 break;
2348 default:
2349 throw new FontFormatException("Unrecognised Font Format");
2350 }
2351 } catch (FontFormatException e) {
2352 if (isCopy) {
2353 java.security.AccessController.doPrivileged(
2354 new java.security.PrivilegedAction() {
2355 public Object run() {
2356 fFile.delete();
2357 return null;
2358 }
2359 });
2360 }
2361 throw(e);
2362 }
2363 if (isCopy) {
2364 font2D.setFileToRemove(fontFile);
2365 synchronized (FontManager.class) {
2366
2367 if (tmpFontFiles == null) {
2368 tmpFontFiles = new Vector<File>();
2369 }
2370 tmpFontFiles.add(fontFile);
2371
2372 if (fileCloser == null) {
2373 final Runnable fileCloserRunnable = new Runnable() {
2374 public void run() {
2375 java.security.AccessController.doPrivileged(
2376 new java.security.PrivilegedAction() {
2377 public Object run() {
2378
2379 for (int i=0;i<CHANNELPOOLSIZE;i++) {
2380 if (fontFileCache[i] != null) {
2381 try {
2382 fontFileCache[i].close();
2383 } catch (Exception e) {
2384 }
2385 }
2386 }
2387 if (tmpFontFiles != null) {
2388 File[] files = new File[tmpFontFiles.size()];
2389 files = tmpFontFiles.toArray(files);
2390 for (int f=0; f<files.length;f++) {
2391 try {
2392 files[f].delete();
2393 } catch (Exception e) {
2394 }
2395 }
2396 }
2397
2398 return null;
2399 }
2400
2401 });
2402 }
2403 };
2404 java.security.AccessController.doPrivileged(
2405 new java.security.PrivilegedAction() {
2406 public Object run() {
2407 /* The thread must be a member of a thread group
2408 * which will not get GCed before VM exit.
2409 * Make its parent the top-level thread group.
2410 */
2411 ThreadGroup tg =
2412 Thread.currentThread().getThreadGroup();
2413 for (ThreadGroup tgn = tg;
2414 tgn != null;
2415 tg = tgn, tgn = tg.getParent());
2416 fileCloser = new Thread(tg, fileCloserRunnable);
2417 Runtime.getRuntime().addShutdownHook(fileCloser);
2418 return null;
2419 }
2420 });
2421 }
2422 }
2423 }
2424 return font2D;
2425 }
2426
2427 /* remind: used in X11GraphicsEnvironment and called often enough
2428 * that we ought to obsolete this code
2429 */
2430 public synchronized static String getFullNameByFileName(String fileName) {
2431 PhysicalFont[] physFonts = getPhysicalFonts();
2432 for (int i=0;i<physFonts.length;i++) {
2433 if (physFonts[i].platName.equals(fileName)) {
2434 return (physFonts[i].getFontName(null));
2435 }
2436 }
2437 return null;
2438 }
2439
2440 /*
2441 * This is called when font is determined to be invalid/bad.
2442 * It designed to be called (for example) by the font scaler
2443 * when in processing a font file it is discovered to be incorrect.
2444 * This is different than the case where fonts are discovered to
2445 * be incorrect during initial verification, as such fonts are
2446 * never registered.
2447 * Handles to this font held are re-directed to a default font.
2448 * This default may not be an ideal substitute buts it better than
2449 * crashing This code assumes a PhysicalFont parameter as it doesn't
2450 * make sense for a Composite to be "bad".
2451 */
2452 public static synchronized void deRegisterBadFont(Font2D font2D) {
2453 if (!(font2D instanceof PhysicalFont)) {
2454 /* We should never reach here, but just in case */
2455 return;
2456 } else {
2457 if (logging) {
2458 logger.severe("Deregister bad font: " + font2D);
2459 }
2460 replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont());
2461 }
2462 }
2463
2464 /*
2465 * This encapsulates all the work that needs to be done when a
2466 * Font2D is replaced by a different Font2D.
2467 */
2468 public static synchronized void replaceFont(PhysicalFont oldFont,
2469 PhysicalFont newFont) {
2470
2471 if (oldFont.handle.font2D != oldFont) {
2472 /* already done */
2473 return;
2474 }
2475
2476 /* If we try to replace the font with itself, that won't work,
2477 * so pick any alternative physical font
2478 */
2479 if (oldFont == newFont) {
2480 if (logging) {
2481 logger.severe("Can't replace bad font with itself " + oldFont);
2482 }
2483 PhysicalFont[] physFonts = getPhysicalFonts();
2484 for (int i=0; i<physFonts.length;i++) {
2485 if (physFonts[i] != newFont) {
2486 newFont = physFonts[i];
2487 break;
2488 }
2489 }
2490 if (oldFont == newFont) {
2491 if (logging) {
2492 logger.severe("This is bad. No good physicalFonts found.");
2493 }
2494 return;
2495 }
2496 }
2497
2498 /* eliminate references to this font, so it won't be located
2499 * by future callers, and will be eligible for GC when all
2500 * references are removed
2501 */
2502 oldFont.handle.font2D = newFont;
2503 physicalFonts.remove(oldFont.fullName);
2504 fullNameToFont.remove(oldFont.fullName.toLowerCase(Locale.ENGLISH));
2505 FontFamily.remove(oldFont);
2506
2507 if (localeFullNamesToFont != null) {
2508 Map.Entry[] mapEntries =
2509 (Map.Entry[])localeFullNamesToFont.entrySet().
2510 toArray(new Map.Entry[0]);
2511 /* Should I be replacing these, or just I just remove
2512 * the names from the map?
2513 */
2514 for (int i=0; i<mapEntries.length;i++) {
2515 if (mapEntries[i].getValue() == oldFont) {
2516 try {
2517 mapEntries[i].setValue(newFont);
2518 } catch (Exception e) {
2519 /* some maps don't support this operation.
2520 * In this case just give up and remove the entry.
2521 */
2522 localeFullNamesToFont.remove(mapEntries[i].getKey());
2523 }
2524 }
2525 }
2526 }
2527
2528 for (int i=0; i<maxCompFont; i++) {
2529 /* Deferred initialization of composites shouldn't be
2530 * a problem for this case, since a font must have been
2531 * initialised to be discovered to be bad.
2532 * Some JRE composites on Solaris use two versions of the same
2533 * font. The replaced font isn't bad, just "smaller" so there's
2534 * no need to make the slot point to the new font.
2535 * Since composites have a direct reference to the Font2D (not
2536 * via a handle) making this substitution is not safe and could
2537 * cause an additional problem and so this substitution is
2538 * warranted only when a font is truly "bad" and could cause
2539 * a crash. So we now replace it only if its being substituted
2540 * with some font other than a fontconfig rank font
2541 * Since in practice a substitution will have the same rank
2542 * this may never happen, but the code is safer even if its
2543 * also now a no-op.
2544 * The only obvious "glitch" from this stems from the current
2545 * implementation that when asked for the number of glyphs in a
2546 * composite it lies and returns the number in slot 0 because
2547 * composite glyphs aren't contiguous. Since we live with that
2548 * we can live with the glitch that depending on how it was
2549 * initialised a composite may return different values for this.
2550 * Fixing the issues with composite glyph ids is tricky as
2551 * there are exclusion ranges and unlike other fonts even the
2552 * true "numGlyphs" isn't a contiguous range. Likely the only
2553 * solution is an API that returns an array of glyph ranges
2554 * which takes precedence over the existing API. That might
2555 * also need to address excluding ranges which represent a
2556 * code point supported by an earlier component.
2557 */
2558 if (newFont.getRank() > Font2D.FONT_CONFIG_RANK) {
2559 compFonts[i].replaceComponentFont(oldFont, newFont);
2560 }
2561 }
2562 }
2563
2564 private static synchronized void loadLocaleNames() {
2565 if (localeFullNamesToFont != null) {
2566 return;
2567 }
2568 localeFullNamesToFont = new HashMap<String, TrueTypeFont>();
2569 Font2D[] fonts = getRegisteredFonts();
2570 for (int i=0; i<fonts.length; i++) {
2571 if (fonts[i] instanceof TrueTypeFont) {
2572 TrueTypeFont ttf = (TrueTypeFont)fonts[i];
2573 String[] fullNames = ttf.getAllFullNames();
2574 for (int n=0; n<fullNames.length; n++) {
2575 localeFullNamesToFont.put(fullNames[n], ttf);
2576 }
2577 FontFamily family = FontFamily.getFamily(ttf.familyName);
2578 if (family != null) {
2579 FontFamily.addLocaleNames(family, ttf.getAllFamilyNames());
2580 }
2581 }
2582 }
2583 }
2584
2585 /* This replicate the core logic of findFont2D but operates on
2586 * all the locale names. This hasn't been merged into findFont2D to
2587 * keep the logic simpler and reduce overhead, since this case is
2588 * almost never used. The main case in which it is called is when
2589 * a bogus font name is used and we need to check all possible names
2590 * before returning the default case.
2591 */
2592 private static Font2D findFont2DAllLocales(String name, int style) {
2593
2594 if (logging) {
2595 logger.info("Searching localised font names for:" + name);
2596 }
2597
2598 /* If reach here and no match has been located, then if we have
2599 * not yet built the map of localeFullNamesToFont for TT fonts, do so
2600 * now. This method must be called after all fonts have been loaded.
2601 */
2602 if (localeFullNamesToFont == null) {
2603 loadLocaleNames();
2604 }
2605 String lowerCaseName = name.toLowerCase();
2606 Font2D font = null;
2607
2608 /* First see if its a family name. */
2609 FontFamily family = FontFamily.getLocaleFamily(lowerCaseName);
2610 if (family != null) {
2611 font = family.getFont(style);
2612 if (font == null) {
2613 font = family.getClosestStyle(style);
2614 }
2615 if (font != null) {
2616 return font;
2617 }
2618 }
2619
2620 /* If it wasn't a family name, it should be a full name. */
2621 synchronized (FontManager.class) {
2622 font = localeFullNamesToFont.get(name);
2623 }
2624 if (font != null) {
2625 if (font.style == style || style == Font.PLAIN) {
2626 return font;
2627 } else {
2628 family = FontFamily.getFamily(font.getFamilyName(null));
2629 if (family != null) {
2630 Font2D familyFont = family.getFont(style);
2631 /* We exactly matched the requested style, use it! */
2632 if (familyFont != null) {
2633 return familyFont;
2634 } else {
2635 familyFont = family.getClosestStyle(style);
2636 if (familyFont != null) {
2637 /* The next check is perhaps one
2638 * that shouldn't be done. ie if we get this
2639 * far we have probably as close a match as we
2640 * are going to get. We could load all fonts to
2641 * see if somehow some parts of the family are
2642 * loaded but not all of it.
2643 * This check is commented out for now.
2644 */
2645 if (!familyFont.canDoStyle(style)) {
2646 familyFont = null;
2647 }
2648 return familyFont;
2649 }
2650 }
2651 }
2652 }
2653 }
2654 return font;
2655 }
2656
2657 /* Supporting "alternate" composite fonts on 2D graphics objects
2658 * is accessed by the application by calling methods on the local
2659 * GraphicsEnvironment. The overall implementation is described
2660 * in one place, here, since otherwise the implementation is spread
2661 * around it may be difficult to track.
2662 * The methods below call into SunGraphicsEnvironment which creates a
2663 * new FontConfiguration instance. The FontConfiguration class,
2664 * and its platform sub-classes are updated to take parameters requesting
2665 * these behaviours. This is then used to create new composite font
2666 * instances. Since this calls the initCompositeFont method in
2667 * SunGraphicsEnvironment it performs the same initialization as is
2668 * performed normally. There may be some duplication of effort, but
2669 * that code is already written to be able to perform properly if called
2670 * to duplicate work. The main difference is that if we detect we are
2671 * running in an applet/browser/Java plugin environment these new fonts
2672 * are not placed in the "default" maps but into an AppContext instance.
2673 * The font lookup mechanism in java.awt.Font.getFont2D() is also updated
2674 * so that look-up for composite fonts will in that case always
2675 * do a lookup rather than returning a cached result.
2676 * This is inefficient but necessary else singleton java.awt.Font
2677 * instances would not retrieve the correct Font2D for the appcontext.
2678 * sun.font.FontManager.findFont2D is also updated to that it uses
2679 * a name map cache specific to that appcontext.
2680 *
2681 * Getting an AppContext is expensive, so there is a global variable
2682 * that records whether these methods have ever been called and can
2683 * avoid the expense for almost all applications. Once the correct
2684 * CompositeFont is associated with the Font, everything should work
2685 * through existing mechanisms.
2686 * A special case is that GraphicsEnvironment.getAllFonts() must
2687 * return an AppContext specific list.
2688 *
2689 * Calling the methods below is "heavyweight" but it is expected that
2690 * these methods will be called very rarely.
2691 *
2692 * If usingPerAppContextComposites is true, we are in "applet"
2693 * (eg browser) enviroment and at least one context has selected
2694 * an alternate composite font behaviour.
2695 * If usingAlternateComposites is true, we are not in an "applet"
2696 * environment and the (single) application has selected
2697 * an alternate composite font behaviour.
2698 *
2699 * - Printing: The implementation delegates logical fonts to an AWT
2700 * mechanism which cannot use these alternate configurations.
2701 * We can detect that alternate fonts are in use and back-off to 2D, but
2702 * that uses outlines. Much of this can be fixed with additional work
2703 * but that may have to wait. The results should be correct, just not
2704 * optimal.
2705 */
2706 private static final Object altJAFontKey = new Object();
2707 private static final Object localeFontKey = new Object();
2708 private static final Object proportionalFontKey = new Object();
2709 public static boolean usingPerAppContextComposites = false;
2710 private static boolean usingAlternateComposites = false;
2711
2712 /* These values are used only if we are running as a standalone
2713 * application, as determined by maybeMultiAppContext();
2714 */
2715 private static boolean gAltJAFont = false;
2716 private static boolean gLocalePref = false;
2717 private static boolean gPropPref = false;
2718
2719 /* This method doesn't check if alternates are selected in this app
2720 * context. Its used by the FontMetrics caching code which in such
2721 * a case cannot retrieve a cached metrics solely on the basis of
2722 * the Font.equals() method since it needs to also check if the Font2D
2723 * is the same.
2724 * We also use non-standard composites for Swing native L&F fonts on
2725 * Windows. In that case the policy is that the metrics reported are
2726 * based solely on the physical font in the first slot which is the
2727 * visible java.awt.Font. So in that case the metrics cache which tests
2728 * the Font does what we want. In the near future when we expand the GTK
2729 * logical font definitions we may need to revisit this if GTK reports
2730 * combined metrics instead. For now though this test can be simple.
2731 */
2732 static boolean maybeUsingAlternateCompositeFonts() {
2733 return usingAlternateComposites || usingPerAppContextComposites;
2734 }
2735
2736 public static boolean usingAlternateCompositeFonts() {
2737 return (usingAlternateComposites ||
2738 (usingPerAppContextComposites &&
2739 AppContext.getAppContext().get(CompositeFont.class) != null));
2740 }
2741
2742 private static boolean maybeMultiAppContext() {
2743 Boolean appletSM = (Boolean)
2744 java.security.AccessController.doPrivileged(
2745 new java.security.PrivilegedAction() {
2746 public Object run() {
2747 SecurityManager sm = System.getSecurityManager();
2748 return new Boolean
2749 (sm instanceof sun.applet.AppletSecurity);
2750 }
2751 });
2752 return appletSM.booleanValue();
2753 }
2754
2755 /* Modifies the behaviour of a subsequent call to preferLocaleFonts()
2756 * to use Mincho instead of Gothic for dialoginput in JA locales
2757 * on windows. Not needed on other platforms.
2758 */
2759 public static synchronized void useAlternateFontforJALocales() {
2760
2761 if (!isWindows) {
2762 return;
2763 }
2764
2765 initSGEnv();
2766 if (!maybeMultiAppContext()) {
2767 gAltJAFont = true;
2768 } else {
2769 AppContext appContext = AppContext.getAppContext();
2770 appContext.put(altJAFontKey, altJAFontKey);
2771 }
2772 }
2773
2774 public static boolean usingAlternateFontforJALocales() {
2775 if (!maybeMultiAppContext()) {
2776 return gAltJAFont;
2777 } else {
2778 AppContext appContext = AppContext.getAppContext();
2779 return appContext.get(altJAFontKey) == altJAFontKey;
2780 }
2781 }
2782
2783 public static synchronized void preferLocaleFonts() {
2784
2785 initSGEnv();
2786
2787 /* Test if re-ordering will have any effect */
2788 if (!FontConfiguration.willReorderForStartupLocale()) {
2789 return;
2790 }
2791
2792 if (!maybeMultiAppContext()) {
2793 if (gLocalePref == true) {
2794 return;
2795 }
2796 gLocalePref = true;
2797 sgEnv.createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
2798 usingAlternateComposites = true;
2799 } else {
2800 AppContext appContext = AppContext.getAppContext();
2801 if (appContext.get(localeFontKey) == localeFontKey) {
2802 return;
2803 }
2804 appContext.put(localeFontKey, localeFontKey);
2805 boolean acPropPref =
2806 appContext.get(proportionalFontKey) == proportionalFontKey;
2807 ConcurrentHashMap<String, Font2D>
2808 altNameCache = new ConcurrentHashMap<String, Font2D> ();
2809 /* If there is an existing hashtable, we can drop it. */
2810 appContext.put(CompositeFont.class, altNameCache);
2811 usingPerAppContextComposites = true;
2812 sgEnv.createCompositeFonts(altNameCache, true, acPropPref);
2813 }
2814 }
2815
2816 public static synchronized void preferProportionalFonts() {
2817
2818 /* If no proportional fonts are configured, there's no need
2819 * to take any action.
2820 */
2821 if (!FontConfiguration.hasMonoToPropMap()) {
2822 return;
2823 }
2824
2825 initSGEnv();
2826
2827 if (!maybeMultiAppContext()) {
2828 if (gPropPref == true) {
2829 return;
2830 }
2831 gPropPref = true;
2832 sgEnv.createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
2833 usingAlternateComposites = true;
2834 } else {
2835 AppContext appContext = AppContext.getAppContext();
2836 if (appContext.get(proportionalFontKey) == proportionalFontKey) {
2837 return;
2838 }
2839 appContext.put(proportionalFontKey, proportionalFontKey);
2840 boolean acLocalePref =
2841 appContext.get(localeFontKey) == localeFontKey;
2842 ConcurrentHashMap<String, Font2D>
2843 altNameCache = new ConcurrentHashMap<String, Font2D> ();
2844 /* If there is an existing hashtable, we can drop it. */
2845 appContext.put(CompositeFont.class, altNameCache);
2846 usingPerAppContextComposites = true;
2847 sgEnv.createCompositeFonts(altNameCache, acLocalePref, true);
2848 }
2849 }
2850
2851 private static HashSet<String> installedNames = null;
2852 private static HashSet<String> getInstalledNames() {
2853 if (installedNames == null) {
2854 Locale l = sgEnv.getSystemStartupLocale();
2855 String[] installedFamilies = sgEnv.getInstalledFontFamilyNames(l);
2856 Font[] installedFonts = sgEnv.getAllInstalledFonts();
2857 HashSet<String> names = new HashSet<String>();
2858 for (int i=0; i<installedFamilies.length; i++) {
2859 names.add(installedFamilies[i].toLowerCase(l));
2860 }
2861 for (int i=0; i<installedFonts.length; i++) {
2862 names.add(installedFonts[i].getFontName(l).toLowerCase(l));
2863 }
2864 installedNames = names;
2865 }
2866 return installedNames;
2867 }
2868
2869 /* Keys are used to lookup per-AppContext Hashtables */
2870 private static final Object regFamilyKey = new Object();
2871 private static final Object regFullNameKey = new Object();
2872 private static Hashtable<String,FontFamily> createdByFamilyName;
2873 private static Hashtable<String,Font2D> createdByFullName;
2874 private static boolean fontsAreRegistered = false;
2875 private static boolean fontsAreRegisteredPerAppContext = false;
2876
2877 public static boolean registerFont(Font font) {
2878 /* This method should not be called with "null".
2879 * It is the caller's responsibility to ensure that.
2880 */
2881 if (font == null) {
2882 return false;
2883 }
2884
2885 /* Initialise these objects only once we start to use this API */
2886 synchronized (regFamilyKey) {
2887 if (createdByFamilyName == null) {
2888 createdByFamilyName = new Hashtable<String,FontFamily>();
2889 createdByFullName = new Hashtable<String,Font2D>();
2890 }
2891 }
2892
2893 if (!isCreatedFont(font)) {
2894 return false;
2895 }
2896 if (sgEnv == null) {
2897 initSGEnv();
2898 }
2899 /* We want to ensure that this font cannot override existing
2900 * installed fonts. Check these conditions :
2901 * - family name is not that of an installed font
2902 * - full name is not that of an installed font
2903 * - family name is not the same as the full name of an installed font
2904 * - full name is not the same as the family name of an installed font
2905 * The last two of these may initially look odd but the reason is
2906 * that (unfortunately) Font constructors do not distinuguish these.
2907 * An extreme example of such a problem would be a font which has
2908 * family name "Dialog.Plain" and full name of "Dialog".
2909 * The one arguably overly stringent restriction here is that if an
2910 * application wants to supply a new member of an existing family
2911 * It will get rejected. But since the JRE can perform synthetic
2912 * styling in many cases its not necessary.
2913 * We don't apply the same logic to registered fonts. If apps want
2914 * to do this lets assume they have a reason. It won't cause problems
2915 * except for themselves.
2916 */
2917 HashSet<String> names = getInstalledNames();
2918 Locale l = sgEnv.getSystemStartupLocale();
2919 String familyName = font.getFamily(l).toLowerCase();
2920 String fullName = font.getFontName(l).toLowerCase();
2921 if (names.contains(familyName) || names.contains(fullName)) {
2922 return false;
2923 }
2924
2925 /* Checks passed, now register the font */
2926 Hashtable<String,FontFamily> familyTable;
2927 Hashtable<String,Font2D> fullNameTable;
2928 if (!maybeMultiAppContext()) {
2929 familyTable = createdByFamilyName;
2930 fullNameTable = createdByFullName;
2931 fontsAreRegistered = true;
2932 } else {
2933 AppContext appContext = AppContext.getAppContext();
2934 familyTable =
2935 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
2936 fullNameTable =
2937 (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
2938 if (familyTable == null) {
2939 familyTable = new Hashtable<String,FontFamily>();
2940 fullNameTable = new Hashtable<String,Font2D>();
2941 appContext.put(regFamilyKey, familyTable);
2942 appContext.put(regFullNameKey, fullNameTable);
2943 }
2944 fontsAreRegisteredPerAppContext = true;
2945 }
2946 /* Create the FontFamily and add font to the tables */
2947 Font2D font2D = getFont2D(font);
2948 int style = font2D.getStyle();
2949 FontFamily family = familyTable.get(familyName);
2950 if (family == null) {
2951 family = new FontFamily(font.getFamily(l));
2952 familyTable.put(familyName, family);
2953 }
2954 /* Remove name cache entries if not using app contexts.
2955 * To accommodate a case where code may have registered first a plain
2956 * family member and then used it and is now registering a bold family
2957 * member, we need to remove all members of the family, so that the
2958 * new style can get picked up rather than continuing to synthesise.
2959 */
2960 if (fontsAreRegistered) {
2961 removeFromCache(family.getFont(Font.PLAIN));
2962 removeFromCache(family.getFont(Font.BOLD));
2963 removeFromCache(family.getFont(Font.ITALIC));
2964 removeFromCache(family.getFont(Font.BOLD|Font.ITALIC));
2965 removeFromCache(fullNameTable.get(fullName));
2966 }
2967 family.setFont(font2D, style);
2968 fullNameTable.put(fullName, font2D);
2969 return true;
2970 }
2971
2972 /* Remove from the name cache all references to the Font2D */
2973 private static void removeFromCache(Font2D font) {
2974 if (font == null) {
2975 return;
2976 }
2977 String[] keys = (String[])(fontNameCache.keySet().toArray(STR_ARRAY));
2978 for (int k=0; k<keys.length;k++) {
2979 if (fontNameCache.get(keys[k]) == font) {
2980 fontNameCache.remove(keys[k]);
2981 }
2982 }
2983 }
2984
2985 // It may look odd to use TreeMap but its more convenient to the caller.
2986 public static TreeMap<String, String> getCreatedFontFamilyNames() {
2987
2988 Hashtable<String,FontFamily> familyTable;
2989 if (fontsAreRegistered) {
2990 familyTable = createdByFamilyName;
2991 } else if (fontsAreRegisteredPerAppContext) {
2992 AppContext appContext = AppContext.getAppContext();
2993 familyTable =
2994 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
2995 } else {
2996 return null;
2997 }
2998
2999 Locale l = sgEnv.getSystemStartupLocale();
3000 synchronized (familyTable) {
3001 TreeMap<String, String> map = new TreeMap<String, String>();
3002 for (FontFamily f : familyTable.values()) {
3003 Font2D font2D = f.getFont(Font.PLAIN);
3004 if (font2D == null) {
3005 font2D = f.getClosestStyle(Font.PLAIN);
3006 }
3007 String name = font2D.getFamilyName(l);
3008 map.put(name.toLowerCase(l), name);
3009 }
3010 return map;
3011 }
3012 }
3013
3014 public static Font[] getCreatedFonts() {
3015
3016 Hashtable<String,Font2D> nameTable;
3017 if (fontsAreRegistered) {
3018 nameTable = createdByFullName;
3019 } else if (fontsAreRegisteredPerAppContext) {
3020 AppContext appContext = AppContext.getAppContext();
3021 nameTable =
3022 (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
3023 } else {
3024 return null;
3025 }
3026
3027 Locale l = sgEnv.getSystemStartupLocale();
3028 synchronized (nameTable) {
3029 Font[] fonts = new Font[nameTable.size()];
3030 int i=0;
3031 for (Font2D font2D : nameTable.values()) {
3032 fonts[i++] = new Font(font2D.getFontName(l), Font.PLAIN, 1);
3033 }
3034 return fonts;
3035 }
3036 }
3037
3038 /* Begin support for GTK Look and Feel - query libfontconfig and
3039 * return a composite Font to Swing that uses the desktop font(s).
3040 */
3041
3042 /* A small "map" from GTK/fontconfig names to the equivalent JDK
3043 * logical font name.
3044 */
3045 private static final String[][] nameMap = {
3046 {"sans", "sansserif"},
3047 {"sans-serif", "sansserif"},
3048 {"serif", "serif"},
3049 {"monospace", "monospaced"}
3050 };
3051
3052 public static String mapFcName(String name) {
3053 for (int i = 0; i < nameMap.length; i++) {
3054 if (name.equals(nameMap[i][0])) {
3055 return nameMap[i][1];
3056 }
3057 }
3058 return null;
3059 }
3060
3061 /* fontconfig recognises slants roman, italic, as well as oblique,
3062 * and a slew of weights, where the ones that matter here are
3063 * regular and bold.
3064 * To fully qualify what we want, we can for example ask for (eg)
3065 * Font.PLAIN : "serif:regular:roman"
3066 * Font.BOLD : "serif:bold:roman"
3067 * Font.ITALIC : "serif:regular:italic"
3068 * Font.BOLD|Font.ITALIC : "serif:bold:italic"
3069 */
3070 private static String[] fontConfigNames = {
3071 "sans:regular:roman",
3072 "sans:bold:roman",
3073 "sans:regular:italic",
3074 "sans:bold:italic",
3075
3076 "serif:regular:roman",
3077 "serif:bold:roman",
3078 "serif:regular:italic",
3079 "serif:bold:italic",
3080
3081 "monospace:regular:roman",
3082 "monospace:bold:roman",
3083 "monospace:regular:italic",
3084 "monospace:bold:italic",
3085 };
3086
3087 /* This class is just a data structure.
3088 */
3089 private static class FontConfigInfo {
3090 String fcName; // eg sans
3091 String fcFamily; // eg sans
3092 String jdkName; // eg sansserif
3093 int style; // eg 0=PLAIN
3094 String familyName; // eg Bitstream Vera Sans
3095 String fontFile; // eg /usr/X11/lib/fonts/foo.ttf
3096 //boolean preferBitmaps; // if embedded bitmaps preferred over AA
3097 CompositeFont compFont; // null if not yet created/known.
3098 }
3099
3100
3101 private static String getFCLocaleStr() {
3102 Locale l = SunToolkit.getStartupLocale();
3103 String localeStr = l.getLanguage();
3104 String country = l.getCountry();
3105 if (!country.equals("")) {
3106 localeStr = localeStr + "-" + country;
3107 }
3108 return localeStr;
3109 }
3110
3111 private static native int
3112 getFontConfigAASettings(String locale, String fcFamily);
3113
3114 /* This is public solely so that for debugging purposes it can be called
3115 * with other names, which might (eg) include a size, eg "sans-24"
3116 * The return value is a text aa rendering hint value.
3117 * Normally we should call the no-args version.
3118 */
3119 public static Object getFontConfigAAHint(String fcFamily) {
3120 if (isWindows) {
3121 return null;
3122 } else {
3123 int hint = getFontConfigAASettings(getFCLocaleStr(), fcFamily);
3124 if (hint < 0) {
3125 return null;
3126 } else {
3127 return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
3128 hint);
3129 }
3130 }
3131 }
3132
3133 /* Called from code that needs to know what are the AA settings
3134 * that apps using FC would pick up for the default desktop font.
3135 * Note apps can change the default desktop font. etc, so this
3136 * isn't certain to be right but its going to correct for most cases.
3137 * Native return values map to the text aa values in sun.awt.SunHints.
3138 * which is used to look up the renderinghint value object.
3139 */
3140 public static Object getFontConfigAAHint() {
3141 return getFontConfigAAHint("sans");
3142 }
3143
3144 /* This array has the array elements created in Java code and is
3145 * passed down to native to be filled in.
3146 */
3147 private static FontConfigInfo[] fontConfigFonts;
3148
3149 /* Return an array of FontConfigInfo structs describing the primary
3150 * font located for each of fontconfig/GTK/Pango's logical font names.
3151 */
3152 private static native void getFontConfig(String locale,
3153 FontConfigInfo[] fonts);
3154
3155
3156 /* This can be made public if it's needed to force a re-read
3157 * rather than using the cached values. The re-read would be needed
3158 * only if some event signalled that the fontconfig has changed.
3159 * In that event this method would need to return directly the array
3160 * to be used by the caller in case it subsequently changed.
3161 */
3162 private static void initFontConfigFonts() {
3163
3164 if (fontConfigFonts != null) {
3165 return;
3166 }
3167
3168 if (isWindows) {
3169 return;
3170 }
3171
3172 long t0 = 0;
3173 if (logging) {
3174 t0 = System.currentTimeMillis();
3175 }
3176
3177 FontConfigInfo[] fontArr = new FontConfigInfo[fontConfigNames.length];
3178 for (int i = 0; i< fontArr.length; i++) {
3179 fontArr[i] = new FontConfigInfo();
3180 fontArr[i].fcName = fontConfigNames[i];
3181 int colonPos = fontArr[i].fcName.indexOf(':');
3182 fontArr[i].fcFamily = fontArr[i].fcName.substring(0, colonPos);
3183 fontArr[i].jdkName = mapFcName(fontArr[i].fcFamily);
3184 fontArr[i].style = i % 4; // depends on array order.
3185 }
3186 getFontConfig(getFCLocaleStr(), fontArr);
3187 fontConfigFonts = fontArr;
3188
3189 if (logging) {
3190 long t1 = System.currentTimeMillis();
3191 logger.info("Time spent accessing fontconfig="+(t1-t0)+"ms.");
3192
3193 for (int i = 0; i< fontConfigFonts.length; i++) {
3194 FontConfigInfo fci = fontConfigFonts[i];
3195 logger.info("FC font " + fci.fcName+" maps to family " +
3196 fci.familyName + " in file " + fci.fontFile);
3197 }
3198 }
3199 }
3200
3201 private static PhysicalFont registerFromFcInfo(FontConfigInfo fcInfo) {
3202
3203 /* If it's a TTC file we need to know that as we will need to
3204 * make sure we return the right font */
3205 int offset = fcInfo.fontFile.length()-4;
3206 if (offset <= 0) {
3207 return null;
3208 }
3209 String ext = fcInfo.fontFile.substring(offset).toLowerCase();
3210 boolean isTTC = ext.equals(".ttc");
3211
3212 /* If this file is already registered, can just return its font.
3213 * However we do need to check in case it's a TTC as we need
3214 * a specific font, so rather than directly returning it, let
3215 * findFont2D resolve that.
3216 */
3217 PhysicalFont physFont = registeredFontFiles.get(fcInfo.fontFile);
3218 if (physFont != null) {
3219 if (isTTC) {
3220 Font2D f2d = findFont2D(fcInfo.familyName,
3221 fcInfo.style, NO_FALLBACK);
3222 if (f2d instanceof PhysicalFont) { /* paranoia */
3223 return (PhysicalFont)f2d;
3224 } else {
3225 return null;
3226 }
3227 } else {
3228 return physFont;
3229 }
3230 }
3231
3232 /* If the font may hide a JRE font (eg fontconfig says it is
3233 * Lucida Sans), we want to use the JRE version, so make it
3234 * point to the JRE font.
3235 */
3236 physFont = findJREDeferredFont(fcInfo.familyName, fcInfo.style);
3237
3238 /* It is also possible the font file is on the "deferred" list,
3239 * in which case we can just initialise it now.
3240 */
3241 if (physFont == null &&
3242 deferredFontFiles.get(fcInfo.fontFile) != null) {
3243 physFont = initialiseDeferredFont(fcInfo.fontFile);
3244 /* use findFont2D to get the right font from TTC's */
3245 if (physFont != null) {
3246 if (isTTC) {
3247 Font2D f2d = findFont2D(fcInfo.familyName,
3248 fcInfo.style, NO_FALLBACK);
3249 if (f2d instanceof PhysicalFont) { /* paranoia */
3250 return (PhysicalFont)f2d;
3251 } else {
3252 return null;
3253 }
3254 } else {
3255 return physFont;
3256 }
3257 }
3258 }
3259
3260 /* In the majority of cases we reach here, and need to determine
3261 * the type and rank to register the font.
3262 */
3263 if (physFont == null) {
3264 int fontFormat = FONTFORMAT_NONE;
3265 int fontRank = Font2D.UNKNOWN_RANK;
3266
3267 if (ext.equals(".ttf") || isTTC) {
3268 fontFormat = FONTFORMAT_TRUETYPE;
3269 fontRank = Font2D.TTF_RANK;
3270 } else if (ext.equals(".pfa") || ext.equals(".pfb")) {
3271 fontFormat = FONTFORMAT_TYPE1;
3272 fontRank = Font2D.TYPE1_RANK;
3273 }
3274 physFont = registerFontFile(fcInfo.fontFile, null,
3275 fontFormat, true, fontRank);
3276 }
3277 return physFont;
3278 }
3279
3280 private static String[] getPlatformFontDirs() {
3281 String path = getFontPath(true);
3282 StringTokenizer parser =
3283 new StringTokenizer(path, File.pathSeparator);
3284 ArrayList<String> pathList = new ArrayList<String>();
3285 try {
3286 while (parser.hasMoreTokens()) {
3287 pathList.add(parser.nextToken());
3288 }
3289 } catch (NoSuchElementException e) {
3290 }
3291 return pathList.toArray(new String[0]);
3292 }
3293
3294 /** returns an array of two strings. The first element is the
3295 * name of the font. The second element is the file name.
3296 */
3297 private static String[] defaultPlatformFont = null;
3298 public static String[] getDefaultPlatformFont() {
3299
3300 if (defaultPlatformFont != null) {
3301 return defaultPlatformFont;
3302 }
3303
3304 String[] info = new String[2];
3305 if (isWindows) {
3306 info[0] = "Arial";
3307 info[1] = "c:\\windows\\fonts";
3308 final String[] dirs = getPlatformFontDirs();
3309 if (dirs.length > 1) {
3310 String dir = (String)
3311 AccessController.doPrivileged(new PrivilegedAction() {
3312 public Object run() {
3313 for (int i=0; i<dirs.length; i++) {
3314 String path =
3315 dirs[i] + File.separator + "arial.ttf";
3316 File file = new File(path);
3317 if (file.exists()) {
3318 return dirs[i];
3319 }
3320 }
3321 return null;
3322 }
3323 });
3324 if (dir != null) {
3325 info[1] = dir;
3326 }
3327 } else {
3328 info[1] = dirs[0];
3329 }
3330 info[1] = info[1] + File.separator + "arial.ttf";
3331 } else {
3332 initFontConfigFonts();
3333 for (int i=0; i<fontConfigFonts.length; i++) {
3334 if ("sans".equals(fontConfigFonts[i].fcFamily) &&
3335 0 == fontConfigFonts[i].style) {
3336 info[0] = fontConfigFonts[i].familyName;
3337 info[1] = fontConfigFonts[i].fontFile;
3338 break;
3339 }
3340 }
3341 /* Absolute last ditch attempt in the face of fontconfig problems.
3342 * If we didn't match, pick the first, or just make something
3343 * up so we don't NPE.
3344 */
3345 if (info[0] == null) {
3346 if (fontConfigFonts.length > 0 &&
3347 fontConfigFonts[0].fontFile != null) {
3348 info[0] = fontConfigFonts[0].familyName;
3349 info[1] = fontConfigFonts[0].fontFile;
3350 } else {
3351 info[0] = "Dialog";
3352 info[1] = "/dialog.ttf";
3353 }
3354 }
3355 }
3356 defaultPlatformFont = info;
3357 return defaultPlatformFont;
3358 }
3359
3360 private FontConfigInfo getFontConfigInfo() {
3361 initFontConfigFonts();
3362 for (int i=0; i<fontConfigFonts.length; i++) {
3363 if ("sans".equals(fontConfigFonts[i].fcFamily) &&
3364 0 == fontConfigFonts[i].style) {
3365 return fontConfigFonts[i];
3366 }
3367 }
3368 return null;
3369 }
3370 /*
3371 * We need to return a Composite font which has as the font in
3372 * its first slot one obtained from fontconfig.
3373 */
3374 private static CompositeFont getFontConfigFont(String name, int style) {
3375
3376 name = name.toLowerCase();
3377
3378 initFontConfigFonts();
3379
3380 FontConfigInfo fcInfo = null;
3381 for (int i=0; i<fontConfigFonts.length; i++) {
3382 if (name.equals(fontConfigFonts[i].fcFamily) &&
3383 style == fontConfigFonts[i].style) {
3384 fcInfo = fontConfigFonts[i];
3385 break;
3386 }
3387 }
3388 if (fcInfo == null) {
3389 fcInfo = fontConfigFonts[0];
3390 }
3391
3392 if (logging) {
3393 logger.info("FC name=" + name + " style=" + style + " uses " +
3394 fcInfo.familyName + " in file: " + fcInfo.fontFile);
3395 }
3396
3397 if (fcInfo.compFont != null) {
3398 return fcInfo.compFont;
3399 }
3400
3401 /* jdkFont is going to be used for slots 1..N and as a fallback.
3402 * Slot 0 will be the physical font from fontconfig.
3403 */
3404 CompositeFont jdkFont = (CompositeFont)
3405 findFont2D(fcInfo.jdkName, style, LOGICAL_FALLBACK);
3406
3407 if (fcInfo.familyName == null || fcInfo.fontFile == null) {
3408 return (fcInfo.compFont = jdkFont);
3409 }
3410
3411 /* First, see if the family and exact style is already registered.
3412 * If it is, use it. If it's not, then try to register it.
3413 * If that registration fails (signalled by null) just return the
3414 * regular JDK composite.
3415 * Algorithmically styled fonts won't match on exact style, so
3416 * will fall through this code, but the regisration code will
3417 * find that file already registered and return its font.
3418 */
3419 FontFamily family = FontFamily.getFamily(fcInfo.familyName);
3420 PhysicalFont physFont = null;
3421 if (family != null) {
3422 Font2D f2D = family.getFontWithExactStyleMatch(fcInfo.style);
3423 if (f2D instanceof PhysicalFont) {
3424 physFont = (PhysicalFont)f2D;
3425 }
3426 }
3427
3428 if (physFont == null || !fcInfo.fontFile.equals(physFont.platName)) {
3429 physFont = registerFromFcInfo(fcInfo);
3430 if (physFont == null) {
3431 return (fcInfo.compFont = jdkFont);
3432 }
3433 family = FontFamily.getFamily(physFont.getFamilyName(null));
3434 }
3435
3436 /* Now register the fonts in the family (the other styles) after
3437 * checking that they aren't already registered and are actually in
3438 * a different file. They may be the same file in CJK cases.
3439 * For cases where they are different font files - eg as is common for
3440 * Latin fonts, then we rely on fontconfig to report these correctly.
3441 * Assume that all styles of this font are found by fontconfig,
3442 * so we can find all the family members which must be registered
3443 * together to prevent synthetic styling.
3444 */
3445 for (int i=0; i<fontConfigFonts.length; i++) {
3446 FontConfigInfo fc = fontConfigFonts[i];
3447 if (fc != fcInfo &&
3448 physFont.getFamilyName(null).equals(fc.familyName) &&
3449 !fc.fontFile.equals(physFont.platName) &&
3450 family.getFontWithExactStyleMatch(fc.style) == null) {
3451
3452 registerFromFcInfo(fontConfigFonts[i]);
3453 }
3454 }
3455
3456 /* Now we have a physical font. We will back this up with the JDK
3457 * logical font (sansserif, serif, or monospaced) that corresponds
3458 * to the Pango/GTK/FC logical font name.
3459 */
3460 return (fcInfo.compFont = new CompositeFont(physFont, jdkFont));
3461 }
3462
3463 /* This is called by Swing passing in a fontconfig family name
3464 * such as "sans". In return Swing gets a FontUIResource instance
3465 * that has queried fontconfig to resolve the font(s) used for this.
3466 * Fontconfig will if asked return a list of fonts to give the largest
3467 * possible code point coverage.
3468 * For now we use only the first font returned by fontconfig, and
3469 * back it up with the most closely matching JDK logical font.
3470 * Essentially this means pre-pending what we return now with fontconfig's
3471 * preferred physical font. This could lead to some duplication in cases,
3472 * if we already included that font later. We probably should remove such
3473 * duplicates, but it is not a significant problem. It can be addressed
3474 * later as part of creating a Composite which uses more of the
3475 * same fonts as fontconfig. At that time we also should pay more
3476 * attention to the special rendering instructions fontconfig returns,
3477 * such as whether we should prefer embedded bitmaps over antialiasing.
3478 * There's no way to express that via a Font at present.
3479 */
3480 public static FontUIResource getFontConfigFUIR(String fcFamily,
3481 int style, int size) {
3482
3483 String mappedName = mapFcName(fcFamily);
3484 if (mappedName == null) {
3485 mappedName = "sansserif";
3486 }
3487
3488 /* If GTK L&F were to be used on windows, we need to return
3489 * something. Since on windows Swing won't have the code to
3490 * call fontconfig, even if it is present, fcFamily and mapped
3491 * name will default to sans and therefore sansserif so this
3492 * should be fine.
3493 */
3494 if (isWindows) {
3495 return new FontUIResource(mappedName, style, size);
3496 }
3497
3498 CompositeFont font2D = getFontConfigFont(fcFamily, style);
3499 if (font2D == null) { // Not expected, just a precaution.
3500 return new FontUIResource(mappedName, style, size);
3501 }
3502
3503 /* The name of the font will be that of the physical font in slot,
3504 * but by setting the handle to that of the CompositeFont it
3505 * renders as that CompositeFont.
3506 * It also needs to be marked as a created font which is the
3507 * current mechanism to signal that deriveFont etc must copy
3508 * the handle from the original font.
3509 */
3510 FontUIResource fuir =
3511 new FontUIResource(font2D.getFamilyName(null), style, size);
3512 setFont2D(fuir, font2D.handle);
3513 setCreatedFont(fuir);
3514 return fuir;
3515 }
3516
3517 /* The following fields and methods which relate to layout
3518 * perhaps belong in some other class but FontManager is already
3519 * widely used as an entry point for other JDK code that needs
3520 * access to the font system internals.
3521 */
3522
3523 /**
3524 * Referenced by code in the JDK which wants to test for the
3525 * minimum char code for which layout may be required.
3526 * Note that even basic latin text can benefit from ligatures,
3527 * eg "ffi" but we presently apply those only if explicitly
3528 * requested with TextAttribute.LIGATURES_ON.
3529 * The value here indicates the lowest char code for which failing
3530 * to invoke layout would prevent acceptable rendering.
3531 */
3532 public static final int MIN_LAYOUT_CHARCODE = 0x0300;
3533
3534 /**
3535 * Referenced by code in the JDK which wants to test for the
3536 * maximum char code for which layout may be required.
3537 * Note this does not account for supplementary characters
3538 * where the caller interprets 'layout' to mean any case where
3539 * one 'char' (ie the java type char) does not map to one glyph
3540 */
3541 public static final int MAX_LAYOUT_CHARCODE = 0x206F;
3542
3543 /* If the character code falls into any of a number of unicode ranges
3544 * where we know that simple left->right layout mapping chars to glyphs
3545 * 1:1 and accumulating advances is going to produce incorrect results,
3546 * we want to know this so the caller can use a more intelligent layout
3547 * approach. A caller who cares about optimum performance may want to
3548 * check the first case and skip the method call if its in that range.
3549 * Although there's a lot of tests in here, knowing you can skip
3550 * CTL saves a great deal more. The rest of the checks are ordered
3551 * so that rather than checking explicitly if (>= start & <= end)
3552 * which would mean all ranges would need to be checked so be sure
3553 * CTL is not needed, the method returns as soon as it recognises
3554 * the code point is outside of a CTL ranges.
3555 * NOTE: Since this method accepts an 'int' it is asssumed to properly
3556 * represent a CHARACTER. ie it assumes the caller has already
3557 * converted surrogate pairs into supplementary characters, and so
3558 * can handle this case and doesn't need to be told such a case is
3559 * 'complex'.
3560 */
3561 static boolean isComplexCharCode(int code) {
3562
3563 if (code < MIN_LAYOUT_CHARCODE || code > MAX_LAYOUT_CHARCODE) {
3564 return false;
3565 }
3566 else if (code <= 0x036f) {
3567 // Trigger layout for combining diacriticals 0x0300->0x036f
3568 return true;
3569 }
3570 else if (code < 0x0590) {
3571 // No automatic layout for Greek, Cyrillic, Armenian.
3572 return false;
3573 }
3574 else if (code <= 0x06ff) {
3575 // Hebrew 0590 - 05ff
3576 // Arabic 0600 - 06ff
3577 return true;
3578 }
3579 else if (code < 0x0900) {
3580 return false; // Syriac and Thaana
3581 }
3582 else if (code <= 0x0e7f) {
3583 // if Indic, assume shaping for conjuncts, reordering:
3584 // 0900 - 097F Devanagari
3585 // 0980 - 09FF Bengali
3586 // 0A00 - 0A7F Gurmukhi
3587 // 0A80 - 0AFF Gujarati
3588 // 0B00 - 0B7F Oriya
3589 // 0B80 - 0BFF Tamil
3590 // 0C00 - 0C7F Telugu
3591 // 0C80 - 0CFF Kannada
3592 // 0D00 - 0D7F Malayalam
3593 // 0D80 - 0DFF Sinhala
3594 // 0E00 - 0E7F if Thai, assume shaping for vowel, tone marks
3595 return true;
3596 }
3597 else if (code < 0x1780) {
3598 return false;
3599 }
3600 else if (code <= 0x17ff) { // 1780 - 17FF Khmer
3601 return true;
3602 }
3603 else if (code < 0x200c) {
3604 return false;
3605 }
3606 else if (code <= 0x200d) { // zwj or zwnj
3607 return true;
3608 }
3609 else if (code >= 0x202a && code <= 0x202e) { // directional control
3610 return true;
3611 }
3612 else if (code >= 0x206a && code <= 0x206f) { // directional control
3613 return true;
3614 }
3615 return false;
3616 }
3617
3618 /* This is almost the same as the method above, except it takes a
3619 * char which means it may include undecoded surrogate pairs.
3620 * The distinction is made so that code which needs to identify all
3621 * cases in which we do not have a simple mapping from
3622 * char->unicode character->glyph can be be identified.
3623 * For example measurement cannot simply sum advances of 'chars',
3624 * the caret in editable text cannot advance one 'char' at a time, etc.
3625 * These callers really are asking for more than whether 'layout'
3626 * needs to be run, they need to know if they can assume 1->1
3627 * char->glyph mapping.
3628 */
3629 static boolean isNonSimpleChar(char ch) {
3630 return
3631 isComplexCharCode(ch) ||
3632 (ch >= CharToGlyphMapper.HI_SURROGATE_START &&
3633 ch <= CharToGlyphMapper.LO_SURROGATE_END);
3634 }
3635
3636 /**
3637 * If there is anything in the text which triggers a case
3638 * where char->glyph does not map 1:1 in straightforward
3639 * left->right ordering, then this method returns true.
3640 * Scripts which might require it but are not treated as such
3641 * due to JDK implementations will not return true.
3642 * ie a 'true' return is an indication of the treatment by
3643 * the implementation.
3644 * Whether supplementary characters should be considered is dependent
3645 * on the needs of the caller. Since this method accepts the 'char' type
3646 * then such chars are always represented by a pair. From a rendering
3647 * perspective these will all (in the cases I know of) still be one
3648 * unicode character -> one glyph. But if a caller is using this to
3649 * discover any case where it cannot make naive assumptions about
3650 * the number of chars, and how to index through them, then it may
3651 * need the option to have a 'true' return in such a case.
3652 */
3653 public static boolean isComplexText(char [] chs, int start, int limit) {
3654
3655 for (int i = start; i < limit; i++) {
3656 if (chs[i] < MIN_LAYOUT_CHARCODE) {
3657 continue;
3658 }
3659 else if (isNonSimpleChar(chs[i])) {
3660 return true;
3661 }
3662 }
3663 return false;
3664 }
3665
3666 /**
3667 * Used by windows printing to assess if a font is likely to
3668 * be layout compatible with JDK
3669 * TrueType fonts should be, but if they have no GPOS table,
3670 * but do have a GSUB table, then they are probably older
3671 * fonts GDI handles differently.
3672 */
3673 public static boolean textLayoutIsCompatible(Font font) {
3674
3675 Font2D font2D = FontManager.getFont2D(font);
3676 if (font2D instanceof TrueTypeFont) {
3677 TrueTypeFont ttf = (TrueTypeFont)font2D;
3678 return
3679 ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null ||
3680 ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null;
3681 } else {
3682 return false;
3683 }
3684 }
3685
3686 private static FontScaler nullScaler = null;
3687 private static Constructor<FontScaler> scalerConstructor = null;
3688
3689 //Find preferred font scaler
3690 //
3691 //NB: we can allow property based preferences
3692 // (theoretically logic can be font type specific)
3693 static {
3694 Class scalerClass = null;
3695 Class arglst[] = new Class[] {Font2D.class, int.class,
3696 boolean.class, int.class};
3697
3698 try {
3699 if (SunGraphicsEnvironment.isOpenJDK()) {
3700 scalerClass = Class.forName("sun.font.FreetypeFontScaler");
3701 } else {
3702 scalerClass = Class.forName("sun.font.T2KFontScaler");
3703 }
3704 } catch (ClassNotFoundException e) {
3705 scalerClass = NullFontScaler.class;
3706 }
3707
3708 //NB: rewrite using factory? constructor is ugly way
3709 try {
3710 scalerConstructor = scalerClass.getConstructor(arglst);
3711 } catch (NoSuchMethodException e) {
3712 //should not happen
3713 }
3714 }
3715
3716 /* At the moment it is harmless to create 2 null scalers
3717 so, technically, syncronized keyword is not needed.
3718
3719 But it is safer to keep it to avoid subtle problems if we will be
3720 adding checks like whether scaler is null scaler. */
3721 public synchronized static FontScaler getNullScaler() {
3722 if (nullScaler == null) {
3723 nullScaler = new NullFontScaler();
3724 }
3725 return nullScaler;
3726 }
3727
3728 /* This is the only place to instantiate new FontScaler.
3729 * Therefore this is very convinient place to register
3730 * scaler with Disposer as well as trigger deregistring bad font
3731 * in case when scaler reports this.
3732 */
3733
3734 public static FontScaler getScaler(Font2D font,
3735 int indexInCollection,
3736 boolean supportsCJK,
3737 int filesize) {
3738 FontScaler scaler = null;
3739
3740 try {
3741 Object args[] = new Object[] {font, indexInCollection,
3742 supportsCJK, filesize};
3743 scaler = scalerConstructor.newInstance(args);
3744 Disposer.addObjectRecord(font, scaler);
3745 } catch (Throwable e) {
3746 scaler = nullScaler;
3747
3748 //if we can not instantiate scaler assume bad font
3749 //NB: technically it could be also because of internal scaler
3750 // error but here we are assuming scaler is ok.
3751 deRegisterBadFont(font);
3752 }
3753 return scaler;
3754 }
3755}