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