1 /* 2 * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.font; 27 28 import java.security.AccessController; 29 import java.security.PrivilegedAction; 30 import java.security.PrivilegedExceptionAction; 31 import java.io.File; 32 import java.io.FilenameFilter; 33 import java.io.InputStream; 34 import java.lang.ref.WeakReference; 35 import java.lang.reflect.Method; 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.Iterator; 41 import java.util.Locale; 42 43 import com.sun.glass.ui.Screen; 44 import com.sun.glass.utils.NativeLibLoader; 45 import com.sun.javafx.PlatformUtil; 46 import com.sun.javafx.text.GlyphLayout; 47 import static com.sun.javafx.FXPermissions.LOAD_FONT_PERMISSION; 48 49 public abstract class PrismFontFactory implements FontFactory { 50 51 public static final boolean debugFonts; 52 public static final boolean isWindows; 53 public static final boolean isLinux; 54 public static final boolean isMacOSX; 55 public static final boolean isIOS; 56 public static final boolean isAndroid; 57 public static final boolean isEmbedded; 58 public static final int cacheLayoutSize; 59 static boolean useNativeRasterizer; 60 private static int subPixelMode; 61 public static final int SUB_PIXEL_ON = 1; 62 public static final int SUB_PIXEL_Y = 2; 63 public static final int SUB_PIXEL_NATIVE = 4; 64 private static float fontSizeLimit = 80f; 65 66 private static boolean lcdEnabled; 67 private static float lcdContrast = -1; 68 private static String jreFontDir; 69 private static final String jreDefaultFont = "Lucida Sans Regular"; 70 private static final String jreDefaultFontLC = "lucida sans regular"; 71 private static final String jreDefaultFontFile = "LucidaSansRegular.ttf"; 72 private static final String T2K_FACTORY = "com.sun.javafx.font.t2k.T2KFactory"; 73 private static final String CT_FACTORY = "com.sun.javafx.font.coretext.CTFactory"; 74 private static final String DW_FACTORY = "com.sun.javafx.font.directwrite.DWFactory"; 75 private static final String FT_FACTORY = "com.sun.javafx.font.freetype.FTFactory"; 76 77 /* We need two maps. One to hold pointers to the raw fonts, another 78 * to hold pointers to the composite resources. Top level look ups 79 * to createFont() will look first in the compResourceMap, and 80 * only go to the second map to create a wrapped resource. 81 * Logical Fonts are handled separately. 82 */ 83 HashMap<String, FontResource> fontResourceMap = 84 new HashMap<String, FontResource>(); 85 86 HashMap<String, CompositeFontResource> compResourceMap = 87 new HashMap<String, CompositeFontResource>(); 88 89 static { 90 isWindows = PlatformUtil.isWindows(); 91 isMacOSX = PlatformUtil.isMac(); 92 isLinux = PlatformUtil.isLinux(); 93 isIOS = PlatformUtil.isIOS(); 94 isAndroid = PlatformUtil.isAndroid(); 95 isEmbedded = PlatformUtil.isEmbedded(); 96 int[] tempCacheLayoutSize = {0x10000}; 97 98 debugFonts = AccessController.doPrivileged( 99 (PrivilegedAction<Boolean>) () -> { 100 NativeLibLoader.loadLibrary("javafx_font"); 101 String dbg = System.getProperty("prism.debugfonts", ""); 102 boolean debug = "true".equals(dbg); 103 jreFontDir = 104 System.getProperty("java.home","") + File.separator + 105 "lib" + File.separator + "fonts" + File.separator; 106 String s = System.getProperty("com.sun.javafx.fontSize"); 107 systemFontSize = -1f; 108 if (s != null) { 109 try { 110 systemFontSize = Float.parseFloat(s); 111 } catch (NumberFormatException nfe) { 112 System.err.println("Cannot parse font size '" 113 + s + "'"); 114 } 115 } 116 s = System.getProperty("prism.subpixeltext", "on"); 117 if (s.indexOf("on") != -1 || s.indexOf("true") != -1) { 118 subPixelMode = SUB_PIXEL_ON; 119 } 120 if (s.indexOf("native") != -1) { 121 subPixelMode |= SUB_PIXEL_NATIVE | SUB_PIXEL_ON; 122 } 123 if (s.indexOf("vertical") != -1) { 124 subPixelMode |= SUB_PIXEL_Y | SUB_PIXEL_NATIVE | SUB_PIXEL_ON; 125 } 126 127 s = System.getProperty("prism.fontSizeLimit"); 128 if (s != null) { 129 try { 130 fontSizeLimit = Float.parseFloat(s); 131 if (fontSizeLimit <= 0) { 132 fontSizeLimit = Float.POSITIVE_INFINITY; 133 } 134 } catch (NumberFormatException nfe) { 135 System.err.println("Cannot parse fontSizeLimit '" + s + "'"); 136 } 137 } 138 139 useNativeRasterizer = isMacOSX || isWindows || isLinux; 140 String defPrismText = useNativeRasterizer ? "native" : "t2k"; 141 String prismText = System.getProperty("prism.text", defPrismText); 142 if (useNativeRasterizer) { 143 useNativeRasterizer = !prismText.equals("t2k"); 144 } else { 145 useNativeRasterizer = prismText.equals("native"); 146 } 147 148 boolean lcdTextOff = (isMacOSX && !useNativeRasterizer) || 149 isIOS || isAndroid || isEmbedded; 150 String defLCDProp = lcdTextOff ? "false" : "true"; 151 String lcdProp = System.getProperty("prism.lcdtext", defLCDProp); 152 lcdEnabled = lcdProp.equals("true"); 153 154 s = System.getProperty("prism.cacheLayoutSize"); 155 if (s != null) { 156 try { 157 tempCacheLayoutSize[0] = Integer.parseInt(s); 158 if (tempCacheLayoutSize[0] < 0) { 159 tempCacheLayoutSize[0] = 0; 160 } 161 } catch (NumberFormatException nfe) { 162 System.err.println("Cannot parse cache layout size '" 163 + s + "'"); 164 } 165 } 166 167 return debug; 168 } 169 ); 170 cacheLayoutSize = tempCacheLayoutSize[0]; 171 } 172 173 private static String getNativeFactoryName() { 174 if (isWindows) return DW_FACTORY; 175 if (isMacOSX || isIOS) return CT_FACTORY; 176 if (isLinux || isAndroid) return FT_FACTORY; 177 return null; 178 } 179 180 public static float getFontSizeLimit() { 181 return fontSizeLimit; 182 } 183 184 private static PrismFontFactory theFontFactory = null; 185 public static synchronized PrismFontFactory getFontFactory() { 186 if (theFontFactory != null) { 187 return theFontFactory; 188 } 189 String factoryClass = null; 190 if (useNativeRasterizer) { 191 factoryClass = getNativeFactoryName(); 192 } 193 if (factoryClass == null) { 194 useNativeRasterizer = false; 195 factoryClass = T2K_FACTORY; 196 } 197 if (debugFonts) { 198 System.err.println("Loading FontFactory " + factoryClass); 199 if (subPixelMode != 0) { 200 String s = "Subpixel: enabled"; 201 if ((subPixelMode & SUB_PIXEL_Y) != 0) { 202 s += ", vertical"; 203 } 204 if ((subPixelMode & SUB_PIXEL_NATIVE) != 0) { 205 s += ", native"; 206 } 207 System.err.println(s); 208 } 209 } 210 theFontFactory = getFontFactory(factoryClass); 211 if (theFontFactory == null) { 212 if (useNativeRasterizer) { 213 // If native failed use T2K (i.e. Windows Vista) 214 useNativeRasterizer = false; 215 factoryClass = T2K_FACTORY; 216 } else { 217 // If T2K failed use native (i.e. OpenJFX build) 218 useNativeRasterizer = true; 219 factoryClass = getNativeFactoryName(); 220 } 221 if (factoryClass != null) { 222 theFontFactory = getFontFactory(factoryClass); 223 } 224 if (debugFonts) { 225 System.err.println("*** Loading primary font factory failed. ***"); 226 System.err.println("*** Fallbacking to " + factoryClass + " ***"); 227 } 228 } 229 return theFontFactory; 230 } 231 232 private static synchronized PrismFontFactory getFontFactory(String factoryClass) { 233 try { 234 Class<?> clazz = Class.forName(factoryClass); 235 Method mid = clazz.getMethod("getFactory", (Class[])null); 236 return (PrismFontFactory)mid.invoke(null); 237 } catch (Throwable t) { 238 if (debugFonts) { 239 System.err.println("Loading font factory failed "+ factoryClass); 240 } 241 } 242 return null; 243 } 244 245 private HashMap<String, PrismFontFile> 246 fileNameToFontResourceMap = new HashMap<String, PrismFontFile>(); 247 248 protected abstract PrismFontFile 249 createFontFile(String name, String filename, 250 int fIndex, boolean register, 251 boolean embedded, 252 boolean copy, boolean tracked) 253 throws Exception; 254 255 public abstract GlyphLayout createGlyphLayout(); 256 257 // For an caller who has recognised a TTC file and wants to create 258 // the instances one at a time so as to have visibility into the 259 // contents of the TTC. Onus is on caller to enumerate all the fonts. 260 private PrismFontFile createFontResource(String filename, int index) { 261 return createFontResource(null, filename, index, 262 true, false, false, false); 263 } 264 265 private PrismFontFile createFontResource(String name, 266 String filename, int index, 267 boolean register, boolean embedded, 268 boolean copy, boolean tracked) { 269 String key = (filename+index).toLowerCase(); 270 PrismFontFile fr = fileNameToFontResourceMap.get(key); 271 if (fr != null) { 272 return fr; 273 } 274 275 try { 276 fr = createFontFile(name, filename, index, register, 277 embedded, copy, tracked); 278 if (register) { 279 storeInMap(fr.getFullName(), fr); 280 fileNameToFontResourceMap.put(key, fr); 281 } 282 return fr; 283 } catch (Exception e) { 284 if (PrismFontFactory.debugFonts) { 285 e.printStackTrace(); 286 } 287 return null; 288 } 289 } 290 291 private PrismFontFile createFontResource(String name, String filename) { 292 PrismFontFile[] pffArr = 293 createFontResources(name, filename, 294 true, false, false, false, false); 295 if (pffArr == null || pffArr.length == 0) { 296 return null; 297 } else { 298 return pffArr[0]; 299 } 300 } 301 302 private PrismFontFile[] createFontResources(String name, String filename, 303 boolean register, 304 boolean embedded, 305 boolean copy, 306 boolean tracked, 307 boolean loadAll) { 308 309 PrismFontFile[] fArr = null; 310 if (filename == null) { 311 return null; 312 } 313 PrismFontFile fr = createFontResource(name, filename, 0, register, 314 embedded, copy, tracked); 315 if (fr == null) { 316 return null; 317 } 318 int cnt = (!loadAll) ? 1 : fr.getFontCount(); 319 fArr = new PrismFontFile[cnt]; 320 fArr[0] = fr; 321 if (cnt == 1) { // Not a TTC, or only requesting one font. 322 return fArr; 323 } 324 PrismFontFile.FileRefCounter rc = null; 325 if (copy) { 326 rc = fr.createFileRefCounter(); 327 } 328 int index = 1; 329 do { 330 String key = (filename+index).toLowerCase(); 331 try { 332 fr = fileNameToFontResourceMap.get(key); 333 if (fr != null) { 334 fArr[index] = fr; 335 continue; 336 } else { 337 fr = createFontFile(null, filename, index, 338 register, embedded, 339 copy, tracked); 340 if (fr == null) { 341 return null; 342 } 343 if (rc != null) { 344 fr.setAndIncFileRefCounter(rc); 345 } 346 fArr[index] = fr; 347 String fontname = fr.getFullName(); 348 if (register) { 349 storeInMap(fontname, fr); 350 fileNameToFontResourceMap.put(key, fr); 351 } 352 } 353 } catch (Exception e) { 354 if (PrismFontFactory.debugFonts) { 355 e.printStackTrace(); 356 } 357 return null; 358 } 359 360 } while (++index < cnt); 361 return fArr; 362 } 363 364 private String dotStyleStr(boolean bold, boolean italic) { 365 if (!bold) { 366 if (!italic) { 367 return ""; 368 } 369 else { 370 return ".italic"; 371 } 372 } else { 373 if (!italic) { 374 return ".bold"; 375 } 376 else { 377 return ".bolditalic"; 378 } 379 } 380 } 381 382 private void storeInMap(String name, FontResource resource) { 383 if (name == null || resource == null) { 384 return; 385 } 386 if (resource instanceof PrismCompositeFontResource) { 387 System.err.println(name + " is a composite " + 388 resource); 389 Thread.dumpStack(); 390 return; 391 } 392 fontResourceMap.put(name.toLowerCase(), resource); 393 } 394 395 private ArrayList<WeakReference<PrismFontFile>> tmpFonts; 396 synchronized void addDecodedFont(PrismFontFile fr) { 397 fr.setIsDecoded(true); 398 addTmpFont(fr); 399 } 400 401 private synchronized void addTmpFont(PrismFontFile fr) { 402 if (tmpFonts == null) { 403 tmpFonts = new ArrayList<WeakReference<PrismFontFile>>(); 404 } 405 WeakReference<PrismFontFile> ref; 406 /* Registered fonts are enumerable by the application and are 407 * expected to persist until VM shutdown. 408 * Other fonts - notably ones temporarily loaded in a web page via 409 * webview - should be eligible to be collected and have their 410 * temp files deleted at any time. 411 */ 412 if (fr.isRegistered()) { 413 ref = new WeakReference<PrismFontFile>(fr); 414 } else { 415 ref = fr.createFileDisposer(this, fr.getFileRefCounter()); 416 } 417 tmpFonts.add(ref); 418 addFileCloserHook(); 419 } 420 421 synchronized void removeTmpFont(WeakReference<PrismFontFile> ref) { 422 if (tmpFonts != null) { 423 tmpFonts.remove(ref); 424 } 425 } 426 427 /* familyName is expected to be a physical font family name. 428 */ 429 public synchronized FontResource getFontResource(String familyName, 430 boolean bold, 431 boolean italic, 432 boolean wantComp) { 433 434 if (familyName == null || familyName.isEmpty()) { 435 return null; 436 } 437 438 String lcFamilyName = familyName.toLowerCase(); 439 String styleStr = dotStyleStr(bold, italic); 440 FontResource fr; 441 442 fr = lookupResource(lcFamilyName+styleStr, wantComp); 443 if (fr != null) { 444 return fr; 445 } 446 447 448 /* We may have registered this as an embedded font. 449 * In which case we should also try to locate it in 450 * the non-composite map before looking elsewhere. 451 * First look for a font with the exact styles specified. 452 * If that fails, look for any font in the family. 453 * Later on this should be a lot smarter about finding the best 454 * match, but that can wait until we have better style matching 455 * for all cases. 456 */ 457 if (embeddedFonts != null && wantComp) { 458 fr = lookupResource(lcFamilyName+styleStr, false); 459 if (fr != null) { 460 return new PrismCompositeFontResource(fr, lcFamilyName+styleStr); 461 } 462 for (PrismFontFile embeddedFont : embeddedFonts.values()) { 463 String lcEmFamily = embeddedFont.getFamilyName().toLowerCase(); 464 if (lcEmFamily.equals(lcFamilyName)) { 465 return new PrismCompositeFontResource(embeddedFont, 466 lcFamilyName+styleStr); 467 } 468 } 469 } 470 471 /* We have hard coded some of the most commonly used Windows fonts 472 * so as to avoid the overhead of doing a lookup via GDI. 473 */ 474 if (isWindows) { 475 int style = ((bold ? 1 : 0)) + ((italic) ? 2 : 0); 476 String fontFile = WindowsFontMap.findFontFile(lcFamilyName, style); 477 if (fontFile != null) { 478 fr = createFontResource(null, fontFile); 479 if (fr != null) { 480 if (bold == fr.isBold() && italic == fr.isItalic() && 481 !styleStr.isEmpty()) 482 { 483 storeInMap(lcFamilyName+styleStr, fr); 484 } 485 if (wantComp) { // wrap with fallback support 486 fr = new PrismCompositeFontResource(fr, 487 lcFamilyName+styleStr); 488 } 489 return fr; 490 } 491 } 492 } 493 494 getFullNameToFileMap(); 495 ArrayList<String> family = familyToFontListMap.get(lcFamilyName); 496 if (family == null) { 497 return null; 498 } 499 500 FontResource plainFR = null, boldFR = null, 501 italicFR = null, boldItalicFR = null; 502 for (String fontName : family) { 503 String lcFontName = fontName.toLowerCase(); 504 fr = fontResourceMap.get(lcFontName); 505 if (fr == null) { 506 String file = findFile(lcFontName); 507 if (file != null) { 508 fr = getFontResource(fontName, file); 509 } 510 if (fr == null) { 511 continue; 512 } 513 storeInMap(lcFontName, fr); 514 // if (wantComp) { 515 // // wrap with fallback support 516 // fr = new T2KCompositeFontResource(fr, lcFontName); 517 // } 518 } 519 if (bold == fr.isBold() && italic == fr.isItalic()) { 520 storeInMap(lcFamilyName+styleStr, fr); 521 if (wantComp) { // wrap with fallback support 522 fr = new PrismCompositeFontResource(fr, 523 lcFamilyName+styleStr); 524 } 525 return fr; 526 } 527 if (!fr.isBold()) { 528 if (!fr.isItalic()) { 529 plainFR = fr; 530 } else { 531 italicFR = fr; 532 } 533 } else { 534 if (!fr.isItalic()) { 535 boldFR = fr; 536 } else { 537 boldItalicFR = fr; 538 } 539 } 540 } 541 542 /* If get here, no perfect match in family. Substitute the 543 * closest one we found. 544 */ 545 if (!bold && !italic) { 546 if (boldFR != null) { 547 fr = boldFR; 548 } else if (italicFR != null) { 549 fr = italicFR; 550 } else { 551 fr = boldItalicFR; 552 } 553 } else if (bold && !italic) { 554 if (plainFR != null) { 555 fr = plainFR; 556 } else if (boldItalicFR != null) { 557 fr = boldItalicFR; 558 } else { 559 fr = italicFR; 560 } 561 } else if (!bold && italic) { 562 if (boldItalicFR != null) { 563 fr = boldItalicFR; 564 } else if (plainFR != null) { 565 fr = plainFR; 566 } else { 567 fr = boldFR; 568 } 569 } else /* (bold && italic) */ { 570 if (italicFR != null) { 571 fr = italicFR; 572 } else if (boldFR != null) { 573 fr = boldFR; 574 } else { 575 fr = plainFR; 576 } 577 } 578 if (fr != null) { 579 storeInMap(lcFamilyName+styleStr, fr); 580 if (wantComp) { // wrap with fallback support 581 fr = new PrismCompositeFontResource(fr, lcFamilyName+styleStr); 582 } 583 } 584 return fr; 585 } 586 587 public synchronized PGFont createFont(String familyName, boolean bold, 588 boolean italic, float size) { 589 FontResource fr = null; 590 if (familyName != null && !familyName.isEmpty()) { 591 PGFont logFont = 592 LogicalFont.getLogicalFont(familyName, bold, italic, size); 593 if (logFont != null) { 594 return logFont; 595 } 596 fr = getFontResource(familyName, bold, italic, true); 597 } 598 599 if (fr == null) { 600 // "System" is the default if we didn't recognise the family 601 return LogicalFont.getLogicalFont("System", bold, italic, size); 602 } 603 return new PrismFont(fr, fr.getFullName(), size); 604 } 605 606 public synchronized PGFont createFont(String name, float size) { 607 608 FontResource fr = null; 609 if (name != null && !name.isEmpty()) { 610 PGFont logFont = 611 LogicalFont.getLogicalFont(name, size); 612 if (logFont != null) { 613 return logFont; 614 } 615 616 fr = getFontResource(name, null, true); 617 } 618 if (fr == null) { 619 return LogicalFont.getLogicalFont(DEFAULT_FULLNAME, size); 620 } 621 return new PrismFont(fr, fr.getFullName(), size); 622 } 623 624 private PrismFontFile getFontResource(String name, String file) { 625 /* caller assures file not null */ 626 PrismFontFile fr = null; 627 /* Still need decode the dfont (event when coretext is used) 628 * so that JFXFontFont can read it */ 629 if (isMacOSX) { 630 DFontDecoder decoder = null; 631 if (name != null) { 632 if (file.endsWith(".dfont")) { 633 decoder = new DFontDecoder(); 634 try { 635 decoder.openFile(); 636 decoder.decode(name); 637 decoder.closeFile(); 638 file = decoder.getFile().getPath(); 639 } catch (Exception e) { 640 file = null; 641 decoder.deleteFile(); 642 decoder = null; 643 if (PrismFontFactory.debugFonts) { 644 e.printStackTrace(); 645 } 646 } 647 } 648 } 649 if (file != null) { 650 fr = createFontResource(name, file); 651 } 652 if (decoder != null) { 653 if (fr != null) { 654 addDecodedFont(fr); 655 } else { 656 decoder.deleteFile(); 657 } 658 } 659 } else { 660 fr = createFontResource(name, file); 661 } 662 return fr; 663 } 664 665 public synchronized PGFont deriveFont(PGFont font, boolean bold, 666 boolean italic, float size) { 667 FontResource fr = font.getFontResource(); 668 //TODO honor bold and italic 669 return new PrismFont(fr, fr.getFullName(), size); 670 } 671 672 private FontResource lookupResource(String lcName, boolean wantComp) { 673 if (wantComp) { 674 return compResourceMap.get(lcName); 675 } else { 676 return fontResourceMap.get(lcName); 677 } 678 } 679 680 public synchronized FontResource getFontResource(String name, String file, 681 boolean wantComp) { 682 FontResource fr = null; 683 684 // First check if the font is already known. 685 if (name != null) { 686 String lcName = name.toLowerCase(); 687 688 // if requesting a wrapped resource, look in the composite map 689 // else look in the physical resource map 690 FontResource fontResource = lookupResource(lcName, wantComp); 691 if (fontResource != null) { 692 return fontResource; 693 } 694 695 /* We may have registered this as an embedded font. 696 * In which case we should also try to locate it in 697 * the non-composite map before looking elsewhere. 698 */ 699 if (embeddedFonts != null && wantComp) { 700 fr = lookupResource(lcName, false); 701 if (fr != null) { 702 fr = new PrismCompositeFontResource(fr, lcName); 703 } 704 if (fr != null) { 705 return fr; 706 } 707 } 708 } 709 710 /* We have hard coded some of the most commonly used Windows fonts 711 * so as to avoid the overhead of doing a lookup via GDI. 712 */ 713 if (isWindows && name != null) { 714 String lcName = name.toLowerCase(); 715 String fontFile = WindowsFontMap.findFontFile(lcName, -1); 716 if (fontFile != null) { 717 fr = createFontResource(null, fontFile); 718 if (fr != null) { 719 if (wantComp) { 720 fr = new PrismCompositeFontResource(fr, lcName); 721 } 722 return fr; 723 } 724 } 725 } 726 727 getFullNameToFileMap(); // init maps 728 729 if (name != null && file != null) { 730 // Typically the TTC case used in font linking. 731 // The called method adds the resources to the physical 732 // map so no need to do it here. 733 fr = getFontResource(name, file); 734 if (fr != null) { 735 if (wantComp) { 736 fr = new PrismCompositeFontResource(fr, name.toLowerCase()); 737 } 738 return fr; 739 } 740 } 741 742 if (name != null) { // Typically normal application lookup 743 fr = getFontResourceByFullName(name, wantComp); 744 if (fr != null) { 745 return fr; 746 } 747 } 748 749 if (file != null) { // Typically the TTF case used in font linking 750 fr = getFontResourceByFileName(file, wantComp); 751 if (fr != null) { 752 return fr; 753 } 754 } 755 756 /* can't find the requested font, caller will fall back to default */ 757 return null; 758 } 759 760 boolean isInstalledFont(String fileName) { 761 // avoid loading the full windows map. Ignore drive letter 762 // as its common to install on D: too in multi-boot. 763 String fileKey; 764 if (isWindows) { 765 if (fileName.toLowerCase().contains("\\windows\\fonts")) { 766 return true; 767 } 768 File f = new File(fileName); 769 fileKey = f.getName(); 770 } else { 771 if (isMacOSX && fileName.toLowerCase().contains("/library/fonts")) { 772 // Most fonts are installed in either /System/Library/Fonts/ 773 // or /Library/Fonts/ 774 return true; 775 } 776 File f = new File(fileName); 777 // fileToFontMap key is the full path on non-windows 778 fileKey = f.getPath(); 779 } 780 781 getFullNameToFileMap(); 782 return fileToFontMap.get(fileKey.toLowerCase()) != null; 783 } 784 785 /* To be called only by methods that already inited the maps 786 */ 787 synchronized private FontResource 788 getFontResourceByFileName(String file, boolean wantComp) { 789 790 if (fontToFileMap.size() <= 1) { 791 return null; 792 } 793 794 /* This is a little tricky: we know the file we want but we need 795 * to check if its already a loaded resource. The maps are set up 796 * to check if a font is loaded by its name, not file. 797 * To help I added a map from file->font for all the windows fonts 798 * but that is valid only for TTF fonts (not TTC). So it should only 799 * be used in a context where we know its a TTF (or OTF) file. 800 */ 801 String name = fileToFontMap.get(file.toLowerCase()); // basename 802 FontResource fontResource = null; 803 if (name == null) { 804 // We should not normally get here with a name that we did 805 // not find from the platform but any EUDC font is in the 806 // list of linked fonts but it is not enumerated by Windows. 807 // So we need to open the file and load it as requested. 808 fontResource = createFontResource(file, 0); 809 if (fontResource != null) { 810 String lcName = fontResource.getFullName().toLowerCase(); 811 storeInMap(lcName, fontResource); 812 // Checking wantComp, alhough the linked/fallback font 813 // case doesn't use this. 814 if (wantComp) { 815 fontResource = 816 new PrismCompositeFontResource(fontResource, lcName); 817 } 818 } 819 } else { 820 String lcName = name.toLowerCase(); 821 fontResource = lookupResource(lcName, wantComp); 822 823 if (fontResource == null) { 824 String fullPath = findFile(lcName); 825 if (fullPath != null) { 826 fontResource = getFontResource(name, fullPath); 827 if (fontResource != null) { 828 storeInMap(lcName, fontResource); 829 } 830 if (wantComp) { 831 // wrap with fallback support 832 fontResource = 833 new PrismCompositeFontResource(fontResource, lcName); 834 } 835 } 836 } 837 } 838 return fontResource; // maybe null 839 } 840 841 /* To be called only by methods that already inited the maps 842 * and checked the font is not already loaded. 843 */ 844 synchronized private FontResource 845 getFontResourceByFullName(String name, boolean wantComp) { 846 847 String lcName = name.toLowerCase(); 848 849 if (fontToFileMap.size() <= 1) { 850 // Do this even though findFile also fails over to Lucida, as 851 // without this step, we'd create new instances. 852 name = jreDefaultFont; 853 } 854 855 FontResource fontResource = null; 856 String file = findFile(lcName); 857 if (file != null) { 858 fontResource = getFontResource(name, file); 859 if (fontResource != null) { 860 storeInMap(lcName, fontResource); 861 if (wantComp) { 862 // wrap with fallback support 863 fontResource = 864 new PrismCompositeFontResource(fontResource, lcName); 865 } 866 } 867 } 868 return fontResource; 869 } 870 871 FontResource getDefaultFontResource(boolean wantComp) { 872 FontResource fontResource = lookupResource(jreDefaultFontLC, wantComp); 873 if (fontResource == null) { 874 fontResource = createFontResource(jreDefaultFont, 875 jreFontDir+jreDefaultFontFile); 876 if (fontResource == null) { 877 // Normally use the JRE default font as the last fallback. 878 // If we can't find even that, use any platform font; 879 for (String font : fontToFileMap.keySet()) { 880 String file = findFile(font); // gets full path 881 fontResource = createFontResource(jreDefaultFontLC, file); 882 if (fontResource != null) { 883 break; 884 } 885 } 886 if (fontResource == null && isLinux) { 887 String path = FontConfigManager.getDefaultFontPath(); 888 if (path != null) { 889 fontResource = createFontResource(jreDefaultFontLC, 890 path); 891 } 892 } 893 if (fontResource == null) { 894 return null; // We tried really hard! 895 } 896 } 897 storeInMap(jreDefaultFontLC, fontResource); 898 if (wantComp) { // wrap primary for map key 899 fontResource = 900 new PrismCompositeFontResource(fontResource, 901 jreDefaultFontLC); 902 } 903 } 904 return fontResource; 905 } 906 907 private String findFile(String name) { 908 909 if (name.equals(jreDefaultFontLC)) { 910 return jreFontDir+jreDefaultFontFile; 911 } 912 getFullNameToFileMap(); 913 String filename = fontToFileMap.get(name); 914 if (isWindows) { 915 filename = getPathNameWindows(filename); 916 } 917 918 // Caller needs to check for null and explicitly request 919 // the JRE default font, if that is what is needed. 920 // since we don't want the JRE's Lucida font to be the 921 // default for "unknown" fonts. 922 return filename; 923 } 924 925 /* Used to indicate required return type from toArray(..); */ 926 private static final String[] STR_ARRAY = new String[0]; 927 928 /* Obtained from Platform APIs (windows only) 929 * Map from lower-case font full name to basename of font file. 930 * Eg "arial bold" -> ARIALBD.TTF. 931 * For TTC files, there is a mapping for each font in the file. 932 */ 933 private volatile HashMap<String,String> fontToFileMap = null; 934 935 /* TTF/OTF Font File to Font Full Name */ 936 private HashMap<String,String> fileToFontMap = null; 937 938 /* Obtained from Platform APIs (windows only) 939 * Map from lower-case font full name to the name of its font family 940 * Eg "arial bold" -> "Arial" 941 */ 942 private HashMap<String,String> fontToFamilyNameMap = null; 943 944 /* Obtained from Platform APIs (windows only) 945 * Map from a lower-case family name to a list of full names of 946 * the member fonts, eg: 947 * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"] 948 */ 949 private HashMap<String,ArrayList<String>> familyToFontListMap= null; 950 951 952 /* For a terminal server there may be two font directories */ 953 private static String sysFontDir = null; 954 private static String userFontDir = null; 955 956 private static native byte[] getFontPath(); 957 private static native String regReadFontLink(String searchfont); 958 private static native String getEUDCFontFile(); 959 960 private static void getPlatformFontDirs() { 961 962 if (userFontDir != null || sysFontDir != null) { 963 return; 964 } 965 byte [] pathBytes = getFontPath(); 966 String path = new String(pathBytes); 967 968 int scIdx = path.indexOf(';'); 969 if (scIdx < 0) { 970 sysFontDir = path; 971 } else { 972 sysFontDir = path.substring(0, scIdx); 973 userFontDir = path.substring(scIdx+1, path.length()); 974 } 975 } 976 977 /** 978 * This will return an array of size 2, each element being an array 979 * list of <code>String</code>. The first (zeroth) array holds file 980 * names, and, the second array holds the corresponding fontnames. 981 * If the file does not have a corresponding font name, its corresponding 982 * name is assigned an empty string "". 983 * As a further complication, Windows 7 frequently lists a font twice, 984 * once with some scaling values, and again without. We don't use this 985 * so find these and exclude duplicates. 986 */ 987 static ArrayList<String> [] getLinkedFonts(String searchFont, 988 boolean addSearchFont) { 989 990 991 ArrayList<String> [] fontRegInfo = new ArrayList[2]; 992 // index 0 = file names, 1 = font name. 993 // the name is only specified for TTC files. 994 fontRegInfo[0] = new ArrayList<String>(); 995 fontRegInfo[1] = new ArrayList<String>(); 996 997 if (isMacOSX) { 998 // Hotkey implementation of fallback font on Mac 999 fontRegInfo[0].add("/Library/Fonts/Arial Unicode.ttf"); 1000 fontRegInfo[1].add("Arial Unicode MS"); 1001 1002 // Add Lucida Sans Regular to Mac OS X fallback list 1003 fontRegInfo[0].add(jreFontDir + jreDefaultFontFile); 1004 fontRegInfo[1].add(jreDefaultFont); 1005 1006 // Add Apple Symbols to Mac OS X fallback list 1007 fontRegInfo[0].add("/System/Library/Fonts/Apple Symbols.ttf"); 1008 fontRegInfo[1].add("Apple Symbols"); 1009 1010 // Add CJK Ext B supplementary characters. 1011 fontRegInfo[0].add("/System/Library/Fonts/STHeiti Light.ttf"); 1012 fontRegInfo[1].add("Heiti SC Light"); 1013 1014 return fontRegInfo; 1015 } 1016 if (!isWindows) { 1017 return fontRegInfo; 1018 } 1019 1020 if (addSearchFont) { 1021 fontRegInfo[0].add(null); 1022 fontRegInfo[1].add(searchFont); 1023 } 1024 1025 String fontRegBuf = regReadFontLink(searchFont); 1026 if (fontRegBuf != null && fontRegBuf.length() > 0) { 1027 // split registry data into null terminated strings 1028 String[] fontRegList = fontRegBuf.split("\u0000"); 1029 int linkListLen = fontRegList.length; 1030 for (int i=0; i < linkListLen; i++) { 1031 String[] splitFontData = fontRegList[i].split(","); 1032 int len = splitFontData.length; 1033 String file = getPathNameWindows(splitFontData[0]); 1034 String name = (len > 1) ? splitFontData[1] : null; 1035 if (name != null && fontRegInfo[1].contains(name)) { 1036 continue; 1037 } else if (name == null && fontRegInfo[0].contains(file)) { 1038 continue; 1039 } 1040 fontRegInfo[0].add(file); 1041 fontRegInfo[1].add(name); 1042 } 1043 } 1044 1045 String eudcFontFile = getEUDCFontFile(); 1046 if (eudcFontFile != null) { 1047 fontRegInfo[0].add(eudcFontFile); 1048 fontRegInfo[1].add(null); 1049 } 1050 1051 // Add Lucida Sans Regular to Windows fallback list 1052 fontRegInfo[0].add(jreFontDir + jreDefaultFontFile); 1053 fontRegInfo[1].add(jreDefaultFont); 1054 1055 if (PlatformUtil.isWinVistaOrLater()) { 1056 // CJK Ext B Supplementary character fallbacks. 1057 fontRegInfo[0].add(getPathNameWindows("mingliub.ttc")); 1058 fontRegInfo[1].add("MingLiU-ExtB"); 1059 1060 if (PlatformUtil.isWin7OrLater()) { 1061 // Add Segoe UI Symbol to Windows 7 or later fallback list 1062 fontRegInfo[0].add(getPathNameWindows("seguisym.ttf")); 1063 fontRegInfo[1].add("Segoe UI Symbol"); 1064 } else { 1065 // Add Cambria Math to Windows Vista fallback list 1066 fontRegInfo[0].add(getPathNameWindows("cambria.ttc")); 1067 fontRegInfo[1].add("Cambria Math"); 1068 } 1069 } 1070 return fontRegInfo; 1071 } 1072 1073 /* This is needed since some windows registry names don't match 1074 * the font names. 1075 * - UPC styled font names have a double space, but the 1076 * registry entry mapping to a file doesn't. 1077 * - Marlett is in a hidden file not listed in the registry 1078 * - The registry advertises that the file david.ttf contains a 1079 * font with the full name "David Regular" when in fact its 1080 * just "David". 1081 * Directly fix up these known cases as this is faster. 1082 * If a font which doesn't match these known cases has no file, 1083 * it may be a font that has been temporarily added to the known set 1084 * or it may be an installed font with a missing registry entry. 1085 * Installed fonts are those in the windows font directories. 1086 * Make a best effort attempt to locate these. 1087 * We obtain the list of TrueType fonts in these directories and 1088 * filter out all the font files we already know about from the registry. 1089 * What remains may be "bad" fonts, duplicate fonts, or perhaps the 1090 * missing font(s) we are looking for. 1091 * Open each of these files to find out. 1092 */ 1093 private void resolveWindowsFonts 1094 (HashMap<String,String> fontToFileMap, 1095 HashMap<String,String> fontToFamilyNameMap, 1096 HashMap<String,ArrayList<String>> familyToFontListMap) { 1097 1098 ArrayList<String> unmappedFontNames = null; 1099 for (String font : fontToFamilyNameMap.keySet()) { 1100 String file = fontToFileMap.get(font); 1101 if (file == null) { 1102 int dsi = font.indexOf(" "); 1103 if (dsi > 0) { 1104 String newName = font.substring(0, dsi); 1105 newName = newName.concat(font.substring(dsi+1)); 1106 file = fontToFileMap.get(newName); 1107 /* If this name exists and isn't for a valid name 1108 * replace the mapping to the file with this font 1109 */ 1110 if (file != null && 1111 !fontToFamilyNameMap.containsKey(newName)) { 1112 fontToFileMap.remove(newName); 1113 fontToFileMap.put(font, file); 1114 } 1115 } else if (font.equals("marlett")) { 1116 fontToFileMap.put(font, "marlett.ttf"); 1117 } else if (font.equals("david")) { 1118 file = fontToFileMap.get("david regular"); 1119 if (file != null) { 1120 fontToFileMap.remove("david regular"); 1121 fontToFileMap.put("david", file); 1122 } 1123 } else { 1124 if (unmappedFontNames == null) { 1125 unmappedFontNames = new ArrayList<String>(); 1126 } 1127 unmappedFontNames.add(font); 1128 } 1129 } 1130 } 1131 1132 if (unmappedFontNames != null) { 1133 HashSet<String> unmappedFontFiles = new HashSet<String>(); 1134 1135 // Used HashMap.clone() on SE but TV didn't support it. 1136 HashMap<String,String> ffmapCopy = new HashMap<String,String>(); 1137 ffmapCopy.putAll(fontToFileMap); 1138 for (String key : fontToFamilyNameMap.keySet()) { 1139 ffmapCopy.remove(key); 1140 } 1141 for (String key : ffmapCopy.keySet()) { 1142 unmappedFontFiles.add(ffmapCopy.get(key)); 1143 fontToFileMap.remove(key); 1144 } 1145 resolveFontFiles(unmappedFontFiles, 1146 unmappedFontNames, 1147 fontToFileMap, 1148 fontToFamilyNameMap, 1149 familyToFontListMap); 1150 1151 /* remove from the set of names that will be returned to the 1152 * user any fonts that can't be mapped to files. 1153 */ 1154 if (unmappedFontNames.size() > 0) { 1155 int sz = unmappedFontNames.size(); 1156 for (int i=0; i<sz; i++) { 1157 String name = unmappedFontNames.get(i); 1158 String familyName = fontToFamilyNameMap.get(name); 1159 if (familyName != null) { 1160 ArrayList<String> family = familyToFontListMap.get(familyName); 1161 if (family != null) { 1162 if (family.size() <= 1) { 1163 familyToFontListMap.remove(familyName); 1164 } 1165 } 1166 } 1167 fontToFamilyNameMap.remove(name); 1168 } 1169 } 1170 } 1171 } 1172 1173 private void resolveFontFiles(HashSet<String> unmappedFiles, 1174 ArrayList<String> unmappedFonts, 1175 HashMap<String,String> fontToFileMap, 1176 HashMap<String,String> fontToFamilyNameMap, 1177 HashMap<String,ArrayList<String>> familyToFontListMap) { 1178 1179 for (String file : unmappedFiles) { 1180 try { 1181 int fn = 0; 1182 PrismFontFile ttf; 1183 String fullPath = getPathNameWindows(file); 1184 do { 1185 ttf = createFontResource(fullPath, fn++); 1186 if (ttf == null) { 1187 break; 1188 } 1189 String fontNameLC = ttf.getFullName().toLowerCase(); 1190 String localeNameLC =ttf.getLocaleFullName().toLowerCase(); 1191 if (unmappedFonts.contains(fontNameLC) || 1192 unmappedFonts.contains(localeNameLC)) { 1193 fontToFileMap.put(fontNameLC, file); 1194 unmappedFonts.remove(fontNameLC); 1195 /* If GDI reported names using locale specific style 1196 * strings we'll have those as the unmapped keys in 1197 * the font to family list and also in the value 1198 * array list mapped by the family. 1199 * We can spot these if the localeName is what is 1200 * actually in the unmapped font list, and we'll 1201 * then replace all occurrences of the locale name with 1202 * the English name. 1203 */ 1204 if (unmappedFonts.contains(localeNameLC)) { 1205 unmappedFonts.remove(localeNameLC); 1206 String family = ttf.getFamilyName(); 1207 String familyLC = family.toLowerCase(); 1208 fontToFamilyNameMap.remove(localeNameLC); 1209 fontToFamilyNameMap.put(fontNameLC, family); 1210 ArrayList<String> familylist = 1211 familyToFontListMap.get(familyLC); 1212 if (familylist != null) { 1213 familylist.remove(ttf.getLocaleFullName()); 1214 } else { 1215 /* The family name was not English. 1216 * Remove the non-English family list 1217 * and replace it with the English one 1218 */ 1219 String localeFamilyLC = 1220 ttf.getLocaleFamilyName().toLowerCase(); 1221 familylist = 1222 familyToFontListMap.get(localeFamilyLC); 1223 if (familylist != null) { 1224 familyToFontListMap.remove(localeFamilyLC); 1225 } 1226 familylist = new ArrayList<String>(); 1227 familyToFontListMap.put(familyLC, familylist); 1228 } 1229 familylist.add(ttf.getFullName()); 1230 } 1231 } 1232 1233 } 1234 while (fn < ttf.getFontCount()); 1235 } catch (Exception e) { 1236 if (debugFonts) { 1237 e.printStackTrace(); 1238 } 1239 } 1240 } 1241 } 1242 1243 static native void 1244 populateFontFileNameMap(HashMap<String,String> fontToFileMap, 1245 HashMap<String,String> fontToFamilyNameMap, 1246 HashMap<String,ArrayList<String>> 1247 familyToFontListMap, 1248 Locale locale); 1249 1250 static String getPathNameWindows(final String filename) { 1251 if (filename == null) { 1252 return null; 1253 } 1254 1255 getPlatformFontDirs(); 1256 File f = new File(filename); 1257 if (f.isAbsolute()) { 1258 return filename; 1259 } 1260 if (userFontDir == null) { 1261 return sysFontDir+"\\"+filename; 1262 } 1263 1264 String path = AccessController.doPrivileged( 1265 new PrivilegedAction<String>() { 1266 public String run() { 1267 File f = new File(sysFontDir+"\\"+filename); 1268 if (f.exists()) { 1269 return f.getAbsolutePath(); 1270 } 1271 else { 1272 return userFontDir+"\\"+filename; 1273 } 1274 } 1275 }); 1276 1277 if (path != null) { 1278 return path; 1279 } 1280 return null; // shouldn't happen. 1281 } 1282 1283 private static ArrayList<String> allFamilyNames; 1284 public String[] getFontFamilyNames() { 1285 if (allFamilyNames == null) { 1286 /* Create an array list and add the families for : 1287 * - logical fonts 1288 * - Embedded fonts 1289 * - Fonts found on the platform (includes JRE fonts).. 1290 */ 1291 ArrayList<String> familyNames = new ArrayList<String>(); 1292 LogicalFont.addFamilies(familyNames); 1293 // Putting this in here is dependendent on the FontLoader 1294 // loading embedded fonts before calling into here. If 1295 // embedded fonts can be added then we need to add these 1296 // dynamically for each call to this method. 1297 1298 if (embeddedFonts != null) { 1299 for (PrismFontFile embeddedFont : embeddedFonts.values()) { 1300 if (!familyNames.contains(embeddedFont.getFamilyName())) 1301 familyNames.add(embeddedFont.getFamilyName()); 1302 } 1303 } 1304 getFullNameToFileMap(); 1305 for (String f : fontToFamilyNameMap.values()) { 1306 if (!familyNames.contains(f)) { 1307 familyNames.add(f); 1308 } 1309 } 1310 Collections.sort(familyNames); 1311 allFamilyNames = new ArrayList<String>(familyNames); 1312 } 1313 return allFamilyNames.toArray(STR_ARRAY); 1314 } 1315 1316 private static ArrayList<String> allFontNames; 1317 public String[] getFontFullNames() { 1318 if (allFontNames == null) { 1319 /* Create an array list and add 1320 * - logical fonts 1321 * - Embedded fonts 1322 * - Fonts found on the platform (includes JRE fonts). 1323 */ 1324 ArrayList<String> fontNames = new ArrayList<String>(); 1325 LogicalFont.addFullNames(fontNames); 1326 if (embeddedFonts != null) { 1327 for (PrismFontFile embeddedFont : embeddedFonts.values()) { 1328 if (!fontNames.contains(embeddedFont.getFullName())) { 1329 fontNames.add(embeddedFont.getFullName()); 1330 } 1331 } 1332 } 1333 getFullNameToFileMap(); 1334 for (ArrayList<String> a : familyToFontListMap.values()) { 1335 for (String s : a) { 1336 fontNames.add(s); 1337 } 1338 } 1339 Collections.sort(fontNames); 1340 allFontNames = fontNames; 1341 } 1342 return allFontNames.toArray(STR_ARRAY); 1343 } 1344 1345 public String[] getFontFullNames(String family) { 1346 1347 // First check if its a logical font family. 1348 String[] logFonts = LogicalFont.getFontsInFamily(family); 1349 if (logFonts != null) { 1350 // Caller will clone/wrap this before returning it to API 1351 return logFonts; 1352 } 1353 // Next check if its an embedded font family 1354 if (embeddedFonts != null) { 1355 ArrayList<String> embeddedFamily = null; 1356 for (PrismFontFile embeddedFont : embeddedFonts.values()) { 1357 if (embeddedFont.getFamilyName().equalsIgnoreCase(family)) { 1358 if (embeddedFamily == null) { 1359 embeddedFamily = new ArrayList<String>(); 1360 } 1361 embeddedFamily.add(embeddedFont.getFullName()); 1362 } 1363 } 1364 if (embeddedFamily != null) { 1365 return embeddedFamily.toArray(STR_ARRAY); 1366 } 1367 } 1368 1369 getFullNameToFileMap(); 1370 family = family.toLowerCase(); 1371 ArrayList<String> familyFonts = familyToFontListMap.get(family); 1372 if (familyFonts != null) { 1373 return familyFonts.toArray(STR_ARRAY); 1374 } else { 1375 return STR_ARRAY; // zero-length therefore immutable. 1376 } 1377 } 1378 1379 public final int getSubPixelMode() { 1380 return subPixelMode; 1381 } 1382 1383 public boolean isLCDTextSupported() { 1384 return lcdEnabled; 1385 } 1386 1387 @Override 1388 public boolean isPlatformFont(String name) { 1389 if (name == null) return false; 1390 /* Using String#startsWith as name can be either a fullName or a family name */ 1391 String lcName = name.toLowerCase(); 1392 if (LogicalFont.isLogicalFont(lcName)) return true; 1393 if (lcName.startsWith("lucida sans")) return true; 1394 String systemFamily = getSystemFont(LogicalFont.SYSTEM).toLowerCase(); 1395 if (lcName.startsWith(systemFamily)) return true; 1396 return false; 1397 } 1398 1399 public static boolean isJreFont(FontResource fr) { 1400 String file = fr.getFileName(); 1401 return file.startsWith(jreFontDir); 1402 } 1403 1404 public static float getLCDContrast() { 1405 if (lcdContrast == -1) { 1406 if (isWindows) { 1407 lcdContrast = getLCDContrastWin32() / 1000f; 1408 } else { 1409 /* REMIND: When using CoreText it likely already applies gamma 1410 * correction to the glyph images. The current implementation does 1411 * not take this into account when rasterizing the glyph. Thus, 1412 * it is possible gamma correction is been applied twice to the 1413 * final result. 1414 * Consider using "1" for lcdContrast possibly produces visually 1415 * more appealing results (although not strictly correct). 1416 */ 1417 lcdContrast = 1.3f; 1418 } 1419 } 1420 return lcdContrast; 1421 } 1422 1423 private static Thread fileCloser = null; 1424 1425 private synchronized void addFileCloserHook() { 1426 if (fileCloser == null) { 1427 final Runnable fileCloserRunnable = () -> { 1428 if (embeddedFonts != null) { 1429 for (PrismFontFile font : embeddedFonts.values()) { 1430 font.disposeOnShutdown(); 1431 } 1432 } 1433 if (tmpFonts != null) { 1434 for (WeakReference<PrismFontFile> ref : tmpFonts) { 1435 PrismFontFile font = ref.get(); 1436 if (font != null) { 1437 font.disposeOnShutdown(); 1438 } 1439 } 1440 } 1441 }; 1442 java.security.AccessController.doPrivileged( 1443 (PrivilegedAction<Object>) () -> { 1444 /* The thread must be a member of a thread group 1445 * which will not get GCed before VM exit. 1446 * Make its parent the top-level thread group. 1447 */ 1448 ThreadGroup tg = Thread.currentThread().getThreadGroup(); 1449 for (ThreadGroup tgn = tg; 1450 tgn != null; tg = tgn, tgn = tg.getParent()); 1451 fileCloser = new Thread(tg, fileCloserRunnable); 1452 fileCloser.setContextClassLoader(null); 1453 Runtime.getRuntime().addShutdownHook(fileCloser); 1454 return null; 1455 } 1456 ); 1457 } 1458 } 1459 1460 private HashMap<String, PrismFontFile> embeddedFonts; 1461 1462 public PGFont[] loadEmbeddedFont(String name, InputStream fontStream, 1463 float size, 1464 boolean register, 1465 boolean loadAll) { 1466 if (!hasPermission()) { 1467 return new PGFont[] { createFont(DEFAULT_FULLNAME, size) } ; 1468 } 1469 if (FontFileWriter.hasTempPermission()) { 1470 return loadEmbeddedFont0(name, fontStream, size, register, loadAll); 1471 } 1472 1473 // Otherwise, be extra conscious of pending temp file creation and 1474 // resourcefully handle the temp file resources, among other things. 1475 FontFileWriter.FontTracker tracker = 1476 FontFileWriter.FontTracker.getTracker(); 1477 boolean acquired = false; 1478 try { 1479 acquired = tracker.acquirePermit(); 1480 if (!acquired) { 1481 // Timed out waiting for resources. 1482 return null; 1483 } 1484 return loadEmbeddedFont0(name, fontStream, size, register, loadAll); 1485 } catch (InterruptedException e) { 1486 // Interrupted while waiting to acquire a permit. 1487 return null; 1488 } finally { 1489 if (acquired) { 1490 tracker.releasePermit(); 1491 } 1492 } 1493 } 1494 1495 private PGFont[] loadEmbeddedFont0(String name, InputStream fontStream, 1496 float size, 1497 boolean register, 1498 boolean loadAll) { 1499 PrismFontFile[] fr = null; 1500 FontFileWriter fontWriter = new FontFileWriter(); 1501 try { 1502 // We use a shutdown hook to close all open tmp files 1503 // created via this API and delete them. 1504 final File tFile = fontWriter.openFile(); 1505 byte[] buf = new byte[8192]; 1506 for (;;) { 1507 int bytesRead = fontStream.read(buf); 1508 if (bytesRead < 0) { 1509 break; 1510 } 1511 fontWriter.writeBytes(buf, 0, bytesRead); 1512 } 1513 fontWriter.closeFile(); 1514 1515 fr = loadEmbeddedFont1(name, tFile.getPath(), register, true, 1516 fontWriter.isTracking(), loadAll); 1517 1518 if (fr != null && fr.length > 0) { 1519 /* Delete the file downloaded if it was decoded 1520 * to another file */ 1521 if (fr[0].isDecoded()) { 1522 fontWriter.deleteFile(); 1523 } 1524 } 1525 1526 /* We don't want to leave the temp files around after exit. 1527 * Also in a shared applet-type context, after all references to 1528 * the applet and therefore the font are dropped, the file 1529 * should be removed. This isn't so much an issue so long as 1530 * the VM exists to serve a single FX app, but will be 1531 * important in an app-context model. 1532 * But also fonts that are over-written by new versions 1533 * need to be cleaned up and that applies even in the single 1534 * context. 1535 * We also need to decrement the byte count by the size 1536 * of the file. 1537 */ 1538 addFileCloserHook(); 1539 } catch (Exception e) { 1540 fontWriter.deleteFile(); 1541 } finally { 1542 /* If the data isn't a valid font, so that registering it 1543 * returns null, or we didn't get so far as copying the data, 1544 * delete the tmp file and decrement the byte count 1545 * in the tracker object before returning. 1546 */ 1547 if (fr == null) { 1548 fontWriter.deleteFile(); 1549 } 1550 } 1551 if (fr != null && fr.length > 0) { 1552 if (size <= 0) size = getSystemFontSize(); 1553 int num = fr.length; 1554 PrismFont[] pFonts = new PrismFont[num]; 1555 for (int i=0; i<num; i++) { 1556 pFonts[i] = new PrismFont(fr[i], fr[i].getFullName(), size); 1557 } 1558 return pFonts; 1559 } 1560 return null; 1561 } 1562 1563 /** 1564 * registerEmbeddedFont(String name, String path) is a small subset of 1565 * registerEmbeddedFont(String name, InputStream fontStream) 1566 * It does not attempt to create a temporary file and has different 1567 * parameters. 1568 * 1569 * @param name font name 1570 * @param path Path name to system file 1571 * @param size font size 1572 * @param register whether the font should be registered. 1573 * @param loadAll whether to load all fonts if it is a TTC 1574 * @return font name extracted from font file 1575 */ 1576 public PGFont[] loadEmbeddedFont(String name, String path, 1577 float size, 1578 boolean register, 1579 boolean loadAll) { 1580 if (!hasPermission()) { 1581 return new PGFont[] { createFont(DEFAULT_FULLNAME, size) }; 1582 } 1583 addFileCloserHook(); 1584 FontResource[] frArr = 1585 loadEmbeddedFont1(name, path, register, false, false, loadAll); 1586 if (frArr != null && frArr.length > 0) { 1587 if (size <= 0) size = getSystemFontSize(); 1588 int num = frArr.length; 1589 PGFont[] pgFonts = new PGFont[num]; 1590 for (int i=0; i<num; i++) { 1591 pgFonts[i] = 1592 new PrismFont(frArr[i], frArr[i].getFullName(), size); 1593 } 1594 return pgFonts; 1595 } 1596 return null; 1597 } 1598 1599 /* This should make the embedded font eligible for reclaimation 1600 * and subsequently, disposal of native resources, once any existing 1601 * strong refs by the application are released. 1602 */ 1603 private void removeEmbeddedFont(String name) { 1604 PrismFontFile font = embeddedFonts.get(name); 1605 if (font == null) { 1606 return; 1607 } 1608 embeddedFonts.remove(name); 1609 String lcName = name.toLowerCase(); 1610 fontResourceMap.remove(lcName); 1611 compResourceMap.remove(lcName); 1612 // The following looks tedious, but if the compMap could have 1613 // the font referenced via some lookup name that applies a style 1614 // or used the family name, we need to find it and remove all 1615 // references to it, so it can be collected. 1616 Iterator<CompositeFontResource> fi = compResourceMap.values().iterator(); 1617 while (fi.hasNext()) { 1618 CompositeFontResource compFont = fi.next(); 1619 if (compFont.getSlotResource(0) == font) { 1620 fi.remove(); 1621 } 1622 } 1623 } 1624 1625 protected boolean registerEmbeddedFont(String path) { 1626 return true; 1627 } 1628 1629 // Used for testing 1630 private int numEmbeddedFonts = 0; 1631 public int test_getNumEmbeddedFonts() { 1632 return numEmbeddedFonts; 1633 } 1634 1635 private synchronized 1636 PrismFontFile[] loadEmbeddedFont1(String name, String path, 1637 boolean register, boolean copy, 1638 boolean tracked, boolean loadAll) { 1639 1640 ++numEmbeddedFonts; 1641 /* 1642 * Fonts that aren't platform installed include those in the 1643 * application jar, WOFF fonts that are downloaded, and fonts 1644 * created via Font.loadFont. If copy==true, we can infer its 1645 * one of these, but its also possible for a font to be file-system 1646 * installed as part of the application but not known to the 1647 * platform. In this case copy==false, but we still need to flag 1648 * to the system its not a platform font so that other pipelines 1649 * know to reference the file directly. 1650 */ 1651 PrismFontFile[] frArr = createFontResources(name, path, register, 1652 true, copy, tracked, 1653 loadAll); 1654 if (frArr == null || frArr.length == 0) { 1655 return null; // yes, this means the caller needs to handle null. 1656 } 1657 1658 /* Before we return or register, make sure names are present 1659 * check whether any of the fonts duplicate an OS font. 1660 */ 1661 1662 if (embeddedFonts == null) { 1663 embeddedFonts = new HashMap<String, PrismFontFile>(); 1664 } 1665 1666 boolean registerEmbedded = true; 1667 for (int i=0; i<frArr.length; i++) { 1668 PrismFontFile fr = frArr[i]; 1669 String family = fr.getFamilyName(); 1670 if (family == null || family.length() == 0) return null; 1671 String fullname = fr.getFullName(); 1672 if (fullname == null || fullname.length() == 0) return null; 1673 String psname = fr.getPSName(); 1674 if (psname == null || psname.length() == 0) return null; 1675 1676 FontResource resource = embeddedFonts.get(fullname); 1677 if (resource != null && fr.equals(resource)) { 1678 /* Do not register the same font twice in the OS */ 1679 registerEmbedded = false; 1680 } 1681 } 1682 1683 if (registerEmbedded) { 1684 /* Use filename from the resource so woff fonts are handled */ 1685 if (!registerEmbeddedFont(frArr[0].getFileName())) { 1686 /* This font file can't be used by the underlying rasterizer */ 1687 return null; 1688 } 1689 } 1690 1691 /* If a temporary font is a copy but it is not decoded then it 1692 * will not be anywhere the shutdown hook can see. 1693 * That means if the font is keep for the entire life of the VM 1694 * its file will not be deleted. 1695 * The fix is to add this font to the list of temporary fonts. 1696 */ 1697 if (copy && !frArr[0].isDecoded()) { 1698 addTmpFont(frArr[0]); 1699 } 1700 1701 if (!register) { 1702 return frArr; 1703 } 1704 1705 /* If a font name is provided then we will also store that in the 1706 * map as an alias, otherwise should use the only the real name, 1707 * REMIND: its possible that either name may hide some installed 1708 * version of the font, possibly one we haven't seen yet. But 1709 * without loading all the platform fonts (expensive) this is 1710 * difficult to ascertain. A contains() check here is therefore 1711 * probably mostly futile. 1712 */ 1713 if (name != null && !name.isEmpty()) { 1714 embeddedFonts.put(name, frArr[0]); 1715 storeInMap(name, frArr[0]); 1716 } 1717 1718 for (int i=0; i<frArr.length; i++) { 1719 PrismFontFile fr = frArr[i]; 1720 String family = fr.getFamilyName(); 1721 String fullname = fr.getFullName(); 1722 removeEmbeddedFont(fullname); 1723 embeddedFonts.put(fullname, fr); 1724 storeInMap(fullname, fr); 1725 family = family + dotStyleStr(fr.isBold(), fr.isItalic()); 1726 storeInMap(family, fr); 1727 /* The remove call is to assist the case where we have 1728 * previously mapped into the composite map a different style 1729 * in this family as a partial match for the application request. 1730 * This can occur when an application requested a bold font before 1731 * it called Font.loadFont to register the bold font. It won't 1732 * fix the cases that already happened, but will fix the future ones. 1733 */ 1734 compResourceMap.remove(family.toLowerCase()); 1735 } 1736 return frArr; 1737 } 1738 1739 private void 1740 logFontInfo(String message, 1741 HashMap<String,String> fontToFileMap, 1742 HashMap<String,String> fontToFamilyNameMap, 1743 HashMap<String,ArrayList<String>> familyToFontListMap) { 1744 1745 System.err.println(message); 1746 for (String keyName : fontToFileMap.keySet()) { 1747 System.err.println("font="+keyName+" file="+ 1748 fontToFileMap.get(keyName)); 1749 } 1750 for (String keyName : fontToFamilyNameMap.keySet()) { 1751 System.err.println("font="+keyName+" family="+ 1752 fontToFamilyNameMap.get(keyName)); 1753 } 1754 for (String keyName : familyToFontListMap.keySet()) { 1755 System.err.println("family="+keyName+ " fonts="+ 1756 familyToFontListMap.get(keyName)); 1757 } 1758 } 1759 1760 private synchronized HashMap<String,String> getFullNameToFileMap() { 1761 if (fontToFileMap == null) { 1762 1763 HashMap<String, String> 1764 tmpFontToFileMap = new HashMap<String,String>(100); 1765 fontToFamilyNameMap = new HashMap<String,String>(100); 1766 familyToFontListMap = new HashMap<String,ArrayList<String>>(50); 1767 fileToFontMap = new HashMap<String,String>(100); 1768 1769 if (isWindows) { 1770 getPlatformFontDirs(); 1771 populateFontFileNameMap(tmpFontToFileMap, 1772 fontToFamilyNameMap, 1773 familyToFontListMap, 1774 Locale.ENGLISH); 1775 1776 if (debugFonts) { 1777 System.err.println("Windows Locale ID=" + getSystemLCID()); 1778 logFontInfo(" *** WINDOWS FONTS BEFORE RESOLVING", 1779 tmpFontToFileMap, 1780 fontToFamilyNameMap, 1781 familyToFontListMap); 1782 } 1783 1784 resolveWindowsFonts(tmpFontToFileMap, 1785 fontToFamilyNameMap, 1786 familyToFontListMap); 1787 1788 if (debugFonts) { 1789 logFontInfo(" *** WINDOWS FONTS AFTER RESOLVING", 1790 tmpFontToFileMap, 1791 fontToFamilyNameMap, 1792 familyToFontListMap); 1793 } 1794 1795 } else if (isMacOSX || isIOS) { 1796 MacFontFinder.populateFontFileNameMap(tmpFontToFileMap, 1797 fontToFamilyNameMap, 1798 familyToFontListMap, 1799 Locale.ENGLISH); 1800 1801 } else if (isLinux) { 1802 FontConfigManager.populateMaps(tmpFontToFileMap, 1803 fontToFamilyNameMap, 1804 familyToFontListMap, 1805 Locale.getDefault()); 1806 if (debugFonts) { 1807 logFontInfo(" *** FONTCONFIG LOCATED FONTS:", 1808 tmpFontToFileMap, 1809 fontToFamilyNameMap, 1810 familyToFontListMap); 1811 } 1812 } else if (isAndroid) { 1813 AndroidFontFinder.populateFontFileNameMap(tmpFontToFileMap, 1814 fontToFamilyNameMap, 1815 familyToFontListMap, 1816 Locale.ENGLISH); 1817 } else { /* unrecognised OS */ 1818 fontToFileMap = tmpFontToFileMap; 1819 return fontToFileMap; 1820 } 1821 1822 /* Reverse map from file to font. file name is base name 1823 * not a full path. 1824 */ 1825 for (String font : tmpFontToFileMap.keySet()) { 1826 String file = tmpFontToFileMap.get(font); 1827 fileToFontMap.put(file.toLowerCase(), font); 1828 } 1829 1830 fontToFileMap = tmpFontToFileMap; 1831 if (isAndroid) { 1832 populateFontFileNameMapGeneric( 1833 AndroidFontFinder.getSystemFontsDir()); 1834 } 1835 populateFontFileNameMapGeneric(jreFontDir); 1836 1837 // for (String keyName : fontToFileMap.keySet()) { 1838 // System.out.println("font="+keyName+" file="+ fontToFileMap.get(keyName)); 1839 // } 1840 1841 // for (String keyName : familyToFontListMap.keySet()) { 1842 // System.out.println("family="+keyName); 1843 // } 1844 } 1845 return fontToFileMap; 1846 } 1847 1848 public final boolean hasPermission() { 1849 try { 1850 SecurityManager sm = System.getSecurityManager(); 1851 if (sm != null) { 1852 sm.checkPermission(LOAD_FONT_PERMISSION); 1853 } 1854 return true; 1855 } catch (SecurityException ex) { 1856 return false; 1857 } 1858 } 1859 1860 private static class TTFilter implements FilenameFilter { 1861 public boolean accept(File dir,String name) { 1862 /* all conveniently have the same suffix length */ 1863 int offset = name.length()-4; 1864 if (offset <= 0) { /* must be at least A.ttf */ 1865 return false; 1866 } else { 1867 return(name.startsWith(".ttf", offset) || 1868 name.startsWith(".TTF", offset) || 1869 name.startsWith(".ttc", offset) || 1870 name.startsWith(".TTC", offset) || 1871 name.startsWith(".otf", offset) || 1872 name.startsWith(".OTF", offset)); 1873 } 1874 } 1875 1876 private TTFilter() { 1877 } 1878 1879 static TTFilter ttFilter; 1880 static TTFilter getInstance() { 1881 if (ttFilter == null) { 1882 ttFilter = new TTFilter(); 1883 } 1884 return ttFilter; 1885 } 1886 } 1887 1888 void addToMaps(PrismFontFile fr) { 1889 1890 if (fr == null) { 1891 return; 1892 } 1893 1894 String fullName = fr.getFullName(); 1895 String familyName = fr.getFamilyName(); 1896 1897 if (fullName == null || familyName == null) { 1898 return; 1899 } 1900 1901 String lcFullName = fullName.toLowerCase(); 1902 String lcFamilyName = familyName.toLowerCase(); 1903 1904 fontToFileMap.put(lcFullName, fr.getFileName()); 1905 fontToFamilyNameMap.put(lcFullName, familyName); 1906 ArrayList<String> familyList = familyToFontListMap.get(lcFamilyName); 1907 if (familyList == null) { 1908 familyList = new ArrayList<String>(); 1909 familyToFontListMap.put(lcFamilyName, familyList); 1910 } 1911 familyList.add(fullName); 1912 } 1913 1914 void populateFontFileNameMapGeneric(String fontDir) { 1915 final File dir = new File(fontDir); 1916 String[] files = null; 1917 try { 1918 files = AccessController.doPrivileged( 1919 (PrivilegedExceptionAction<String[]>) () -> dir.list(TTFilter.getInstance()) 1920 ); 1921 } catch (Exception e) { 1922 } 1923 1924 if (files == null) { 1925 return; 1926 } 1927 1928 for (int i=0;i<files.length;i++) { 1929 try { 1930 String path = fontDir+File.separator+files[i]; 1931 1932 /* Use filename from the resource so woff fonts are handled */ 1933 if (!registerEmbeddedFont(path)) { 1934 /* This font file can't be used by the underlying rasterizer */ 1935 continue; 1936 } 1937 1938 int index = 0; 1939 PrismFontFile fr = createFontResource(path, index++); 1940 if (fr == null) { 1941 continue; 1942 } 1943 addToMaps(fr); 1944 while (index < fr.getFontCount()) { 1945 fr = createFontResource(path, index++); 1946 if (fr == null) { 1947 break; 1948 } 1949 addToMaps(fr); 1950 } 1951 } catch (Exception e) { 1952 /* Keep going if anything bad happens with a font */ 1953 } 1954 } 1955 } 1956 1957 static native int getLCDContrastWin32(); 1958 private static native float getSystemFontSizeNative(); 1959 private static native String getSystemFontNative(); 1960 private static float systemFontSize; 1961 private static String systemFontFamily = null; 1962 private static String monospaceFontFamily = null; 1963 1964 public static float getSystemFontSize() { 1965 if (systemFontSize == -1) { 1966 if (isWindows) { 1967 systemFontSize = getSystemFontSizeNative(); 1968 } else if (isMacOSX || isIOS) { 1969 systemFontSize = MacFontFinder.getSystemFontSize(); 1970 } else if (isAndroid) { 1971 systemFontSize = AndroidFontFinder.getSystemFontSize(); 1972 } else if (isEmbedded) { 1973 try { 1974 int screenDPI = Screen.getMainScreen().getResolutionY(); 1975 systemFontSize = ((float) screenDPI) / 6f; // 12 points 1976 } catch (NullPointerException npe) { 1977 // if no screen is defined 1978 systemFontSize = 13f; // same as desktop Linux 1979 } 1980 } else { 1981 systemFontSize = 13f; // Gnome uses 13. 1982 } 1983 } 1984 return systemFontSize; 1985 } 1986 1987 /* Applies to Windows and Mac. Not used on Linux */ 1988 public static String getSystemFont(String name) { 1989 if (name.equals(LogicalFont.SYSTEM)) { 1990 if (systemFontFamily == null) { 1991 if (isWindows) { 1992 systemFontFamily = getSystemFontNative(); 1993 if (systemFontFamily == null) { 1994 systemFontFamily = "Arial"; // play it safe. 1995 } 1996 } else if (isMacOSX || isIOS) { 1997 systemFontFamily = MacFontFinder.getSystemFont(); 1998 if (systemFontFamily == null) { 1999 systemFontFamily = "Lucida Grande"; 2000 } 2001 } else if (isAndroid) { 2002 systemFontFamily = AndroidFontFinder.getSystemFont(); 2003 } else { 2004 systemFontFamily = "Lucida Sans"; // for now. 2005 } 2006 } 2007 return systemFontFamily; 2008 } else if (name.equals(LogicalFont.SANS_SERIF)) { 2009 return "Arial"; 2010 } else if (name.equals(LogicalFont.SERIF)) { 2011 return "Times New Roman"; 2012 } else /* if (name.equals(LogicalFont.MONOSPACED)) */ { 2013 if (monospaceFontFamily == null) { 2014 if (isMacOSX) { 2015 /* This code is intentionally commented: 2016 * On the OS X the preferred monospaced font is Monaco, 2017 * although this can be a good choice for most Mac application 2018 * it is not suitable for JavaFX because Monaco does not 2019 * have neither bold nor italic. 2020 */ 2021 // monospaceFontFamily = MacFontFinder.getMonospacedFont(); 2022 } 2023 } 2024 if (monospaceFontFamily == null) { 2025 monospaceFontFamily = "Courier New"; 2026 } 2027 return monospaceFontFamily; 2028 } 2029 } 2030 2031 /* Called from T2KFontFile which caches the return value */ 2032 static native short getSystemLCID(); 2033 }