J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 26 | package sun.font; |
| 27 | |
| 28 | import java.awt.Font; |
| 29 | import java.awt.GraphicsEnvironment; |
| 30 | import java.awt.FontFormatException; |
| 31 | import java.io.File; |
| 32 | import java.io.FilenameFilter; |
| 33 | import java.security.AccessController; |
| 34 | import java.security.PrivilegedAction; |
| 35 | import java.util.ArrayList; |
| 36 | import java.util.HashMap; |
| 37 | import java.util.HashSet; |
| 38 | import java.util.Hashtable; |
| 39 | import java.util.Iterator; |
| 40 | import java.util.Locale; |
| 41 | import java.util.Map; |
| 42 | import java.util.NoSuchElementException; |
| 43 | import java.util.StringTokenizer; |
| 44 | import java.util.TreeMap; |
| 45 | import java.util.Vector; |
| 46 | import java.util.concurrent.ConcurrentHashMap; |
| 47 | import java.util.logging.Level; |
| 48 | import java.util.logging.Logger; |
| 49 | import javax.swing.plaf.FontUIResource; |
| 50 | |
| 51 | import sun.awt.AppContext; |
| 52 | import sun.awt.FontConfiguration; |
| 53 | import sun.awt.SunHints; |
| 54 | import sun.awt.SunToolkit; |
| 55 | import sun.java2d.HeadlessGraphicsEnvironment; |
| 56 | import sun.java2d.SunGraphicsEnvironment; |
| 57 | |
| 58 | import java.awt.geom.GeneralPath; |
| 59 | import java.awt.geom.Point2D; |
| 60 | import java.awt.geom.Rectangle2D; |
| 61 | |
| 62 | import java.lang.reflect.Constructor; |
| 63 | |
| 64 | import 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 | */ |
| 70 | public 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 | } |