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