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