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 CJK Ext B supplementary characters. 1026 fontRegInfo[0].add("/System/Library/Fonts/STHeiti Light.ttf"); 1027 fontRegInfo[1].add("Heiti SC Light"); 1028 1029 return fontRegInfo; 1030 } 1031 if (!isWindows) { 1032 return fontRegInfo; 1033 } 1034 1035 if (addSearchFont) { 1036 fontRegInfo[0].add(null); 1037 fontRegInfo[1].add(searchFont); 1038 } 1039 1040 String fontRegBuf = regReadFontLink(searchFont); 1041 if (fontRegBuf != null && fontRegBuf.length() > 0) { 1042 // split registry data into null terminated strings 1043 String[] fontRegList = fontRegBuf.split("\u0000"); 1044 int linkListLen = fontRegList.length; 1045 for (int i=0; i < linkListLen; i++) { 1046 String[] splitFontData = fontRegList[i].split(","); 1047 int len = splitFontData.length; 1048 String file = getPathNameWindows(splitFontData[0]); 1049 String name = (len > 1) ? splitFontData[1] : null; 1050 if (name != null && fontRegInfo[1].contains(name)) { 1051 continue; 1052 } else if (name == null && fontRegInfo[0].contains(file)) { 1053 continue; 1054 } 1055 fontRegInfo[0].add(file); 1056 fontRegInfo[1].add(name); 1057 } 1058 } 1059 1060 String eudcFontFile = getEUDCFontFile(); 1061 if (eudcFontFile != null) { 1062 fontRegInfo[0].add(eudcFontFile); 1063 fontRegInfo[1].add(null); 1064 } 1065 1066 // Add Lucida Sans Regular to Windows fallback list 1067 fontRegInfo[0].add(jreFontDir + jreDefaultFontFile); 1068 fontRegInfo[1].add(jreDefaultFont); 1069 1070 if (PlatformUtil.isWinVistaOrLater()) { 1071 // CJK Ext B Supplementary character fallbacks. 1072 fontRegInfo[0].add(getPathNameWindows("mingliub.ttc")); 1073 fontRegInfo[1].add("MingLiU-ExtB"); 1074 1075 if (PlatformUtil.isWin7OrLater()) { 1076 // Add Segoe UI Symbol to Windows 7 or later fallback list 1077 fontRegInfo[0].add(getPathNameWindows("seguisym.ttf")); 1078 fontRegInfo[1].add("Segoe UI Symbol"); 1079 } else { 1080 // Add Cambria Math to Windows Vista fallback list 1081 fontRegInfo[0].add(getPathNameWindows("cambria.ttc")); 1082 fontRegInfo[1].add("Cambria Math"); 1083 } 1084 } 1085 return fontRegInfo; 1086 } 1087 1088 /* This is needed since some windows registry names don't match 1089 * the font names. 1090 * - UPC styled font names have a double space, but the 1091 * registry entry mapping to a file doesn't. 1092 * - Marlett is in a hidden file not listed in the registry 1093 * - The registry advertises that the file david.ttf contains a 1094 * font with the full name "David Regular" when in fact its 1095 * just "David". 1096 * Directly fix up these known cases as this is faster. 1097 * If a font which doesn't match these known cases has no file, 1098 * it may be a font that has been temporarily added to the known set 1099 * or it may be an installed font with a missing registry entry. 1100 * Installed fonts are those in the windows font directories. 1101 * Make a best effort attempt to locate these. 1102 * We obtain the list of TrueType fonts in these directories and 1103 * filter out all the font files we already know about from the registry. 1104 * What remains may be "bad" fonts, duplicate fonts, or perhaps the 1105 * missing font(s) we are looking for. 1106 * Open each of these files to find out. 1107 */ 1108 private void resolveWindowsFonts 1109 (HashMap<String,String> fontToFileMap, 1110 HashMap<String,String> fontToFamilyNameMap, 1111 HashMap<String,ArrayList<String>> familyToFontListMap) { 1112 1113 ArrayList<String> unmappedFontNames = null; 1114 for (String font : fontToFamilyNameMap.keySet()) { 1115 String file = fontToFileMap.get(font); 1116 if (file == null) { 1117 int dsi = font.indexOf(" "); 1118 if (dsi > 0) { 1119 String newName = font.substring(0, dsi); 1120 newName = newName.concat(font.substring(dsi+1)); 1121 file = fontToFileMap.get(newName); 1122 /* If this name exists and isn't for a valid name 1123 * replace the mapping to the file with this font 1124 */ 1125 if (file != null && 1126 !fontToFamilyNameMap.containsKey(newName)) { 1127 fontToFileMap.remove(newName); 1128 fontToFileMap.put(font, file); 1129 } 1130 } else if (font.equals("marlett")) { 1131 fontToFileMap.put(font, "marlett.ttf"); 1132 } else if (font.equals("david")) { 1133 file = fontToFileMap.get("david regular"); 1134 if (file != null) { 1135 fontToFileMap.remove("david regular"); 1136 fontToFileMap.put("david", file); 1137 } 1138 } else { 1139 if (unmappedFontNames == null) { 1140 unmappedFontNames = new ArrayList<String>(); 1141 } 1142 unmappedFontNames.add(font); 1143 } 1144 } 1145 } 1146 1147 if (unmappedFontNames != null) { 1148 HashSet<String> unmappedFontFiles = new HashSet<String>(); 1149 1150 // Used HashMap.clone() on SE but TV didn't support it. 1151 HashMap<String,String> ffmapCopy = new HashMap<String,String>(); 1152 ffmapCopy.putAll(fontToFileMap); 1153 for (String key : fontToFamilyNameMap.keySet()) { 1154 ffmapCopy.remove(key); 1155 } 1156 for (String key : ffmapCopy.keySet()) { 1157 unmappedFontFiles.add(ffmapCopy.get(key)); 1158 fontToFileMap.remove(key); 1159 } 1160 resolveFontFiles(unmappedFontFiles, 1161 unmappedFontNames, 1162 fontToFileMap, 1163 fontToFamilyNameMap, 1164 familyToFontListMap); 1165 1166 /* remove from the set of names that will be returned to the 1167 * user any fonts that can't be mapped to files. 1168 */ 1169 if (unmappedFontNames.size() > 0) { 1170 int sz = unmappedFontNames.size(); 1171 for (int i=0; i<sz; i++) { 1172 String name = unmappedFontNames.get(i); 1173 String familyName = fontToFamilyNameMap.get(name); 1174 if (familyName != null) { 1175 ArrayList<String> family = familyToFontListMap.get(familyName); 1176 if (family != null) { 1177 if (family.size() <= 1) { 1178 familyToFontListMap.remove(familyName); 1179 } 1180 } 1181 } 1182 fontToFamilyNameMap.remove(name); 1183 } 1184 } 1185 } 1186 } 1187 1188 private void resolveFontFiles(HashSet<String> unmappedFiles, 1189 ArrayList<String> unmappedFonts, 1190 HashMap<String,String> fontToFileMap, 1191 HashMap<String,String> fontToFamilyNameMap, 1192 HashMap<String,ArrayList<String>> familyToFontListMap) { 1193 1194 for (String file : unmappedFiles) { 1195 try { 1196 int fn = 0; 1197 PrismFontFile ttf; 1198 String fullPath = getPathNameWindows(file); 1199 do { 1200 ttf = createFontResource(fullPath, fn++); 1201 if (ttf == null) { 1202 break; 1203 } 1204 String fontNameLC = ttf.getFullName().toLowerCase(); 1205 String localeNameLC =ttf.getLocaleFullName().toLowerCase(); 1206 if (unmappedFonts.contains(fontNameLC) || 1207 unmappedFonts.contains(localeNameLC)) { 1208 fontToFileMap.put(fontNameLC, file); 1209 unmappedFonts.remove(fontNameLC); 1210 /* If GDI reported names using locale specific style 1211 * strings we'll have those as the unmapped keys in 1212 * the font to family list and also in the value 1213 * array list mapped by the family. 1214 * We can spot these if the localeName is what is 1215 * actually in the unmapped font list, and we'll 1216 * then replace all occurrences of the locale name with 1217 * the English name. 1218 */ 1219 if (unmappedFonts.contains(localeNameLC)) { 1220 unmappedFonts.remove(localeNameLC); 1221 String family = ttf.getFamilyName(); 1222 String familyLC = family.toLowerCase(); 1223 fontToFamilyNameMap.remove(localeNameLC); 1224 fontToFamilyNameMap.put(fontNameLC, family); 1225 ArrayList<String> familylist = 1226 familyToFontListMap.get(familyLC); 1227 if (familylist != null) { 1228 familylist.remove(ttf.getLocaleFullName()); 1229 } else { 1230 /* The family name was not English. 1231 * Remove the non-English family list 1232 * and replace it with the English one 1233 */ 1234 String localeFamilyLC = 1235 ttf.getLocaleFamilyName().toLowerCase(); 1236 familylist = 1237 familyToFontListMap.get(localeFamilyLC); 1238 if (familylist != null) { 1239 familyToFontListMap.remove(localeFamilyLC); 1240 } 1241 familylist = new ArrayList<String>(); 1242 familyToFontListMap.put(familyLC, familylist); 1243 } 1244 familylist.add(ttf.getFullName()); 1245 } 1246 } 1247 1248 } 1249 while (fn < ttf.getFontCount()); 1250 } catch (Exception e) { 1251 if (debugFonts) { 1252 e.printStackTrace(); 1253 } 1254 } 1255 } 1256 } 1257 1258 static native void 1259 populateFontFileNameMap(HashMap<String,String> fontToFileMap, 1260 HashMap<String,String> fontToFamilyNameMap, 1261 HashMap<String,ArrayList<String>> 1262 familyToFontListMap, 1263 Locale locale); 1264 1265 static String getPathNameWindows(final String filename) { 1266 if (filename == null) { 1267 return null; 1268 } 1269 1270 getPlatformFontDirs(); 1271 File f = new File(filename); 1272 if (f.isAbsolute()) { 1273 return filename; 1274 } 1275 if (userFontDir == null) { 1276 return sysFontDir+"\\"+filename; 1277 } 1278 1279 String path = AccessController.doPrivileged( 1280 new PrivilegedAction<String>() { 1281 public String run() { 1282 File f = new File(sysFontDir+"\\"+filename); 1283 if (f.exists()) { 1284 return f.getAbsolutePath(); 1285 } 1286 else { 1287 return userFontDir+"\\"+filename; 1288 } 1289 } 1290 }); 1291 1292 if (path != null) { 1293 return path; 1294 } 1295 return null; // shouldn't happen. 1296 } 1297 1298 private static ArrayList<String> allFamilyNames; 1299 public String[] getFontFamilyNames() { 1300 if (allFamilyNames == null) { 1301 /* Create an array list and add the families for : 1302 * - logical fonts 1303 * - Embedded fonts 1304 * - Fonts found on the platform (includes JRE fonts).. 1305 */ 1306 ArrayList<String> familyNames = new ArrayList<String>(); 1307 LogicalFont.addFamilies(familyNames); 1308 // Putting this in here is dependendent on the FontLoader 1309 // loading embedded fonts before calling into here. If 1310 // embedded fonts can be added then we need to add these 1311 // dynamically for each call to this method. 1312 1313 if (embeddedFonts != null) { 1314 for (PrismFontFile embeddedFont : embeddedFonts.values()) { 1315 if (!familyNames.contains(embeddedFont.getFamilyName())) 1316 familyNames.add(embeddedFont.getFamilyName()); 1317 } 1318 } 1319 getFullNameToFileMap(); 1320 for (String f : fontToFamilyNameMap.values()) { 1321 if (!familyNames.contains(f)) { 1322 familyNames.add(f); 1323 } 1324 } 1325 Collections.sort(familyNames); 1326 allFamilyNames = new ArrayList<String>(familyNames); 1327 } 1328 return allFamilyNames.toArray(STR_ARRAY); 1329 } 1330 1331 private static ArrayList<String> allFontNames; 1332 public String[] getFontFullNames() { 1333 if (allFontNames == null) { 1334 /* Create an array list and add 1335 * - logical fonts 1336 * - Embedded fonts 1337 * - Fonts found on the platform (includes JRE fonts). 1338 */ 1339 ArrayList<String> fontNames = new ArrayList<String>(); 1340 LogicalFont.addFullNames(fontNames); 1341 if (embeddedFonts != null) { 1342 for (PrismFontFile embeddedFont : embeddedFonts.values()) { 1343 if (!fontNames.contains(embeddedFont.getFullName())) { 1344 fontNames.add(embeddedFont.getFullName()); 1345 } 1346 } 1347 } 1348 getFullNameToFileMap(); 1349 for (ArrayList<String> a : familyToFontListMap.values()) { 1350 for (String s : a) { 1351 fontNames.add(s); 1352 } 1353 } 1354 Collections.sort(fontNames); 1355 allFontNames = fontNames; 1356 } 1357 return allFontNames.toArray(STR_ARRAY); 1358 } 1359 1360 public String[] getFontFullNames(String family) { 1361 1362 // First check if its a logical font family. 1363 String[] logFonts = LogicalFont.getFontsInFamily(family); 1364 if (logFonts != null) { 1365 // Caller will clone/wrap this before returning it to API 1366 return logFonts; 1367 } 1368 // Next check if its an embedded font family 1369 if (embeddedFonts != null) { 1370 ArrayList<String> embeddedFamily = null; 1371 for (PrismFontFile embeddedFont : embeddedFonts.values()) { 1372 if (embeddedFont.getFamilyName().equalsIgnoreCase(family)) { 1373 if (embeddedFamily == null) { 1374 embeddedFamily = new ArrayList<String>(); 1375 } 1376 embeddedFamily.add(embeddedFont.getFullName()); 1377 } 1378 } 1379 if (embeddedFamily != null) { 1380 return embeddedFamily.toArray(STR_ARRAY); 1381 } 1382 } 1383 1384 getFullNameToFileMap(); 1385 family = family.toLowerCase(); 1386 ArrayList<String> familyFonts = familyToFontListMap.get(family); 1387 if (familyFonts != null) { 1388 return familyFonts.toArray(STR_ARRAY); 1389 } else { 1390 return STR_ARRAY; // zero-length therefore immutable. 1391 } 1392 } 1393 1394 public final int getSubPixelMode() { 1395 return subPixelMode; 1396 } 1397 1398 public boolean isLCDTextSupported() { 1399 return lcdEnabled; 1400 } 1401 1402 @Override 1403 public boolean isPlatformFont(String name) { 1404 if (name == null) return false; 1405 /* Using String#startsWith as name can be either a fullName or a family name */ 1406 String lcName = name.toLowerCase(); 1407 if (LogicalFont.isLogicalFont(lcName)) return true; 1408 if (lcName.startsWith("lucida sans")) return true; 1409 String systemFamily = getSystemFont(LogicalFont.SYSTEM).toLowerCase(); 1410 if (lcName.startsWith(systemFamily)) return true; 1411 return false; 1412 } 1413 1414 public static boolean isJreFont(FontResource fr) { 1415 String file = fr.getFileName(); 1416 return file.startsWith(jreFontDir); 1417 } 1418 1419 public static float getLCDContrast() { 1420 if (lcdContrast == -1) { 1421 if (isWindows) { 1422 lcdContrast = getLCDContrastWin32() / 1000f; 1423 } else { 1424 /* REMIND: When using CoreText it likely already applies gamma 1425 * correction to the glyph images. The current implementation does 1426 * not take this into account when rasterizing the glyph. Thus, 1427 * it is possible gamma correction is been applied twice to the 1428 * final result. 1429 * Consider using "1" for lcdContrast possibly produces visually 1430 * more appealing results (although not strictly correct). 1431 */ 1432 lcdContrast = 1.3f; 1433 } 1434 } 1435 return lcdContrast; 1436 } 1437 1438 private static Thread fileCloser = null; 1439 1440 private synchronized void addFileCloserHook() { 1441 if (fileCloser == null) { 1442 final Runnable fileCloserRunnable = () -> { 1443 if (embeddedFonts != null) { 1444 for (PrismFontFile font : embeddedFonts.values()) { 1445 font.disposeOnShutdown(); 1446 } 1447 } 1448 if (tmpFonts != null) { 1449 for (WeakReference<PrismFontFile> ref : tmpFonts) { 1450 PrismFontFile font = ref.get(); 1451 if (font != null) { 1452 font.disposeOnShutdown(); 1453 } 1454 } 1455 } 1456 }; 1457 java.security.AccessController.doPrivileged( 1458 (PrivilegedAction<Object>) () -> { 1459 /* The thread must be a member of a thread group 1460 * which will not get GCed before VM exit. 1461 * Make its parent the top-level thread group. 1462 */ 1463 ThreadGroup tg = Thread.currentThread().getThreadGroup(); 1464 for (ThreadGroup tgn = tg; 1465 tgn != null; tg = tgn, tgn = tg.getParent()); 1466 fileCloser = new Thread(tg, fileCloserRunnable); 1467 fileCloser.setContextClassLoader(null); 1468 Runtime.getRuntime().addShutdownHook(fileCloser); 1469 return null; 1470 } 1471 ); 1472 } 1473 } 1474 1475 private HashMap<String, PrismFontFile> embeddedFonts; 1476 1477 public PGFont[] loadEmbeddedFont(String name, InputStream fontStream, 1478 float size, 1479 boolean register, 1480 boolean loadAll) { 1481 if (!hasPermission()) { 1482 return new PGFont[] { createFont(DEFAULT_FULLNAME, size) } ; 1483 } 1484 if (FontFileWriter.hasTempPermission()) { 1485 return loadEmbeddedFont0(name, fontStream, size, register, loadAll); 1486 } 1487 1488 // Otherwise, be extra conscious of pending temp file creation and 1489 // resourcefully handle the temp file resources, among other things. 1490 FontFileWriter.FontTracker tracker = 1491 FontFileWriter.FontTracker.getTracker(); 1492 boolean acquired = false; 1493 try { 1494 acquired = tracker.acquirePermit(); 1495 if (!acquired) { 1496 // Timed out waiting for resources. 1497 return null; 1498 } 1499 return loadEmbeddedFont0(name, fontStream, size, register, loadAll); 1500 } catch (InterruptedException e) { 1501 // Interrupted while waiting to acquire a permit. 1502 return null; 1503 } finally { 1504 if (acquired) { 1505 tracker.releasePermit(); 1506 } 1507 } 1508 } 1509 1510 private PGFont[] loadEmbeddedFont0(String name, InputStream fontStream, 1511 float size, 1512 boolean register, 1513 boolean loadAll) { 1514 PrismFontFile[] fr = null; 1515 FontFileWriter fontWriter = new FontFileWriter(); 1516 try { 1517 // We use a shutdown hook to close all open tmp files 1518 // created via this API and delete them. 1519 final File tFile = fontWriter.openFile(); 1520 byte[] buf = new byte[8192]; 1521 for (;;) { 1522 int bytesRead = fontStream.read(buf); 1523 if (bytesRead < 0) { 1524 break; 1525 } 1526 fontWriter.writeBytes(buf, 0, bytesRead); 1527 } 1528 fontWriter.closeFile(); 1529 1530 fr = loadEmbeddedFont1(name, tFile.getPath(), register, true, 1531 fontWriter.isTracking(), loadAll); 1532 1533 if (fr != null && fr.length > 0) { 1534 /* Delete the file downloaded if it was decoded 1535 * to another file */ 1536 if (fr[0].isDecoded()) { 1537 fontWriter.deleteFile(); 1538 } 1539 } 1540 1541 /* We don't want to leave the temp files around after exit. 1542 * Also in a shared applet-type context, after all references to 1543 * the applet and therefore the font are dropped, the file 1544 * should be removed. This isn't so much an issue so long as 1545 * the VM exists to serve a single FX app, but will be 1546 * important in an app-context model. 1547 * But also fonts that are over-written by new versions 1548 * need to be cleaned up and that applies even in the single 1549 * context. 1550 * We also need to decrement the byte count by the size 1551 * of the file. 1552 */ 1553 addFileCloserHook(); 1554 } catch (Exception e) { 1555 fontWriter.deleteFile(); 1556 } finally { 1557 /* If the data isn't a valid font, so that registering it 1558 * returns null, or we didn't get so far as copying the data, 1559 * delete the tmp file and decrement the byte count 1560 * in the tracker object before returning. 1561 */ 1562 if (fr == null) { 1563 fontWriter.deleteFile(); 1564 } 1565 } 1566 if (fr != null && fr.length > 0) { 1567 if (size <= 0) size = getSystemFontSize(); 1568 int num = fr.length; 1569 PrismFont[] pFonts = new PrismFont[num]; 1570 for (int i=0; i<num; i++) { 1571 pFonts[i] = new PrismFont(fr[i], fr[i].getFullName(), size); 1572 } 1573 return pFonts; 1574 } 1575 return null; 1576 } 1577 1578 /** 1579 * registerEmbeddedFont(String name, String path) is a small subset of 1580 * registerEmbeddedFont(String name, InputStream fontStream) 1581 * It does not attempt to create a temporary file and has different 1582 * parameters. 1583 * 1584 * @param name font name 1585 * @param path Path name to system file 1586 * @param size font size 1587 * @param register whether the font should be registered. 1588 * @param loadAll whether to load all fonts if it is a TTC 1589 * @return font name extracted from font file 1590 */ 1591 public PGFont[] loadEmbeddedFont(String name, String path, 1592 float size, 1593 boolean register, 1594 boolean loadAll) { 1595 if (!hasPermission()) { 1596 return new PGFont[] { createFont(DEFAULT_FULLNAME, size) }; 1597 } 1598 addFileCloserHook(); 1599 FontResource[] frArr = 1600 loadEmbeddedFont1(name, path, register, false, false, loadAll); 1601 if (frArr != null && frArr.length > 0) { 1602 if (size <= 0) size = getSystemFontSize(); 1603 int num = frArr.length; 1604 PGFont[] pgFonts = new PGFont[num]; 1605 for (int i=0; i<num; i++) { 1606 pgFonts[i] = 1607 new PrismFont(frArr[i], frArr[i].getFullName(), size); 1608 } 1609 return pgFonts; 1610 } 1611 return null; 1612 } 1613 1614 /* This should make the embedded font eligible for reclaimation 1615 * and subsequently, disposal of native resources, once any existing 1616 * strong refs by the application are released. 1617 */ 1618 private void removeEmbeddedFont(String name) { 1619 PrismFontFile font = embeddedFonts.get(name); 1620 if (font == null) { 1621 return; 1622 } 1623 embeddedFonts.remove(name); 1624 String lcName = name.toLowerCase(); 1625 fontResourceMap.remove(lcName); 1626 compResourceMap.remove(lcName); 1627 // The following looks tedious, but if the compMap could have 1628 // the font referenced via some lookup name that applies a style 1629 // or used the family name, we need to find it and remove all 1630 // references to it, so it can be collected. 1631 Iterator<CompositeFontResource> fi = compResourceMap.values().iterator(); 1632 while (fi.hasNext()) { 1633 CompositeFontResource compFont = fi.next(); 1634 if (compFont.getSlotResource(0) == font) { 1635 fi.remove(); 1636 } 1637 } 1638 } 1639 1640 protected boolean registerEmbeddedFont(String path) { 1641 return true; 1642 } 1643 1644 // Used for testing 1645 private int numEmbeddedFonts = 0; 1646 public int test_getNumEmbeddedFonts() { 1647 return numEmbeddedFonts; 1648 } 1649 1650 private synchronized 1651 PrismFontFile[] loadEmbeddedFont1(String name, String path, 1652 boolean register, boolean copy, 1653 boolean tracked, boolean loadAll) { 1654 1655 ++numEmbeddedFonts; 1656 /* 1657 * Fonts that aren't platform installed include those in the 1658 * application jar, WOFF fonts that are downloaded, and fonts 1659 * created via Font.loadFont. If copy==true, we can infer its 1660 * one of these, but its also possible for a font to be file-system 1661 * installed as part of the application but not known to the 1662 * platform. In this case copy==false, but we still need to flag 1663 * to the system its not a platform font so that other pipelines 1664 * know to reference the file directly. 1665 */ 1666 PrismFontFile[] frArr = createFontResources(name, path, register, 1667 true, copy, tracked, 1668 loadAll); 1669 if (frArr == null || frArr.length == 0) { 1670 return null; // yes, this means the caller needs to handle null. 1671 } 1672 1673 /* Before we return or register, make sure names are present 1674 * check whether any of the fonts duplicate an OS font. 1675 */ 1676 1677 if (embeddedFonts == null) { 1678 embeddedFonts = new HashMap<String, PrismFontFile>(); 1679 } 1680 1681 boolean registerEmbedded = true; 1682 for (int i=0; i<frArr.length; i++) { 1683 PrismFontFile fr = frArr[i]; 1684 String family = fr.getFamilyName(); 1685 if (family == null || family.length() == 0) return null; 1686 String fullname = fr.getFullName(); 1687 if (fullname == null || fullname.length() == 0) return null; 1688 String psname = fr.getPSName(); 1689 if (psname == null || psname.length() == 0) return null; 1690 1691 FontResource resource = embeddedFonts.get(fullname); 1692 if (resource != null && fr.equals(resource)) { 1693 /* Do not register the same font twice in the OS */ 1694 registerEmbedded = false; 1695 } 1696 } 1697 1698 if (registerEmbedded) { 1699 /* Use filename from the resource so woff fonts are handled */ 1700 if (!registerEmbeddedFont(frArr[0].getFileName())) { 1701 /* This font file can't be used by the underlying rasterizer */ 1702 return null; 1703 } 1704 } 1705 1706 /* If a temporary font is a copy but it is not decoded then it 1707 * will not be anywhere the shutdown hook can see. 1708 * That means if the font is keep for the entire life of the VM 1709 * its file will not be deleted. 1710 * The fix is to add this font to the list of temporary fonts. 1711 */ 1712 if (copy && !frArr[0].isDecoded()) { 1713 addTmpFont(frArr[0]); 1714 } 1715 1716 if (!register) { 1717 return frArr; 1718 } 1719 1720 /* If a font name is provided then we will also store that in the 1721 * map as an alias, otherwise should use the only the real name, 1722 * REMIND: its possible that either name may hide some installed 1723 * version of the font, possibly one we haven't seen yet. But 1724 * without loading all the platform fonts (expensive) this is 1725 * difficult to ascertain. A contains() check here is therefore 1726 * probably mostly futile. 1727 */ 1728 if (name != null && !name.isEmpty()) { 1729 embeddedFonts.put(name, frArr[0]); 1730 storeInMap(name, frArr[0]); 1731 } 1732 1733 for (int i=0; i<frArr.length; i++) { 1734 PrismFontFile fr = frArr[i]; 1735 String family = fr.getFamilyName(); 1736 String fullname = fr.getFullName(); 1737 removeEmbeddedFont(fullname); 1738 embeddedFonts.put(fullname, fr); 1739 storeInMap(fullname, fr); 1740 family = family + dotStyleStr(fr.isBold(), fr.isItalic()); 1741 storeInMap(family, fr); 1742 /* The remove call is to assist the case where we have 1743 * previously mapped into the composite map a different style 1744 * in this family as a partial match for the application request. 1745 * This can occur when an application requested a bold font before 1746 * it called Font.loadFont to register the bold font. It won't 1747 * fix the cases that already happened, but will fix the future ones. 1748 */ 1749 compResourceMap.remove(family.toLowerCase()); 1750 } 1751 return frArr; 1752 } 1753 1754 private void 1755 logFontInfo(String message, 1756 HashMap<String,String> fontToFileMap, 1757 HashMap<String,String> fontToFamilyNameMap, 1758 HashMap<String,ArrayList<String>> familyToFontListMap) { 1759 1760 System.err.println(message); 1761 for (String keyName : fontToFileMap.keySet()) { 1762 System.err.println("font="+keyName+" file="+ 1763 fontToFileMap.get(keyName)); 1764 } 1765 for (String keyName : fontToFamilyNameMap.keySet()) { 1766 System.err.println("font="+keyName+" family="+ 1767 fontToFamilyNameMap.get(keyName)); 1768 } 1769 for (String keyName : familyToFontListMap.keySet()) { 1770 System.err.println("family="+keyName+ " fonts="+ 1771 familyToFontListMap.get(keyName)); 1772 } 1773 } 1774 1775 private synchronized HashMap<String,String> getFullNameToFileMap() { 1776 if (fontToFileMap == null) { 1777 1778 HashMap<String, String> 1779 tmpFontToFileMap = new HashMap<String,String>(100); 1780 fontToFamilyNameMap = new HashMap<String,String>(100); 1781 familyToFontListMap = new HashMap<String,ArrayList<String>>(50); 1782 fileToFontMap = new HashMap<String,String>(100); 1783 1784 if (isWindows) { 1785 getPlatformFontDirs(); 1786 populateFontFileNameMap(tmpFontToFileMap, 1787 fontToFamilyNameMap, 1788 familyToFontListMap, 1789 Locale.ENGLISH); 1790 1791 if (debugFonts) { 1792 System.err.println("Windows Locale ID=" + getSystemLCID()); 1793 logFontInfo(" *** WINDOWS FONTS BEFORE RESOLVING", 1794 tmpFontToFileMap, 1795 fontToFamilyNameMap, 1796 familyToFontListMap); 1797 } 1798 1799 resolveWindowsFonts(tmpFontToFileMap, 1800 fontToFamilyNameMap, 1801 familyToFontListMap); 1802 1803 if (debugFonts) { 1804 logFontInfo(" *** WINDOWS FONTS AFTER RESOLVING", 1805 tmpFontToFileMap, 1806 fontToFamilyNameMap, 1807 familyToFontListMap); 1808 } 1809 1810 } else if (isMacOSX || isIOS) { 1811 MacFontFinder.populateFontFileNameMap(tmpFontToFileMap, 1812 fontToFamilyNameMap, 1813 familyToFontListMap, 1814 Locale.ENGLISH); 1815 1816 } else if (isLinux) { 1817 FontConfigManager.populateMaps(tmpFontToFileMap, 1818 fontToFamilyNameMap, 1819 familyToFontListMap, 1820 Locale.getDefault()); 1821 if (debugFonts) { 1822 logFontInfo(" *** FONTCONFIG LOCATED FONTS:", 1823 tmpFontToFileMap, 1824 fontToFamilyNameMap, 1825 familyToFontListMap); 1826 } 1827 } else if (isAndroid) { 1828 AndroidFontFinder.populateFontFileNameMap(tmpFontToFileMap, 1829 fontToFamilyNameMap, 1830 familyToFontListMap, 1831 Locale.ENGLISH); 1832 } else { /* unrecognised OS */ 1833 fontToFileMap = tmpFontToFileMap; 1834 return fontToFileMap; 1835 } 1836 1837 /* Reverse map from file to font. file name is base name 1838 * not a full path. 1839 */ 1840 for (String font : tmpFontToFileMap.keySet()) { 1841 String file = tmpFontToFileMap.get(font); 1842 fileToFontMap.put(file.toLowerCase(), font); 1843 } 1844 1845 fontToFileMap = tmpFontToFileMap; 1846 if (isAndroid) { 1847 populateFontFileNameMapGeneric( 1848 AndroidFontFinder.getSystemFontsDir()); 1849 } 1850 populateFontFileNameMapGeneric(jreFontDir); 1851 1852 // for (String keyName : fontToFileMap.keySet()) { 1853 // System.out.println("font="+keyName+" file="+ fontToFileMap.get(keyName)); 1854 // } 1855 1856 // for (String keyName : familyToFontListMap.keySet()) { 1857 // System.out.println("family="+keyName); 1858 // } 1859 } 1860 return fontToFileMap; 1861 } 1862 1863 public final boolean hasPermission() { 1864 try { 1865 SecurityManager sm = System.getSecurityManager(); 1866 if (sm != null) { 1867 sm.checkPermission(LOAD_FONT_PERMISSION); 1868 } 1869 return true; 1870 } catch (SecurityException ex) { 1871 return false; 1872 } 1873 } 1874 1875 private static class TTFilter implements FilenameFilter { 1876 public boolean accept(File dir,String name) { 1877 /* all conveniently have the same suffix length */ 1878 int offset = name.length()-4; 1879 if (offset <= 0) { /* must be at least A.ttf */ 1880 return false; 1881 } else { 1882 return(name.startsWith(".ttf", offset) || 1883 name.startsWith(".TTF", offset) || 1884 name.startsWith(".ttc", offset) || 1885 name.startsWith(".TTC", offset) || 1886 name.startsWith(".otf", offset) || 1887 name.startsWith(".OTF", offset)); 1888 } 1889 } 1890 1891 private TTFilter() { 1892 } 1893 1894 static TTFilter ttFilter; 1895 static TTFilter getInstance() { 1896 if (ttFilter == null) { 1897 ttFilter = new TTFilter(); 1898 } 1899 return ttFilter; 1900 } 1901 } 1902 1903 void addToMaps(PrismFontFile fr) { 1904 1905 if (fr == null) { 1906 return; 1907 } 1908 1909 String fullName = fr.getFullName(); 1910 String familyName = fr.getFamilyName(); 1911 1912 if (fullName == null || familyName == null) { 1913 return; 1914 } 1915 1916 String lcFullName = fullName.toLowerCase(); 1917 String lcFamilyName = familyName.toLowerCase(); 1918 1919 fontToFileMap.put(lcFullName, fr.getFileName()); 1920 fontToFamilyNameMap.put(lcFullName, familyName); 1921 ArrayList<String> familyList = familyToFontListMap.get(lcFamilyName); 1922 if (familyList == null) { 1923 familyList = new ArrayList<String>(); 1924 familyToFontListMap.put(lcFamilyName, familyList); 1925 } 1926 familyList.add(fullName); 1927 } 1928 1929 void populateFontFileNameMapGeneric(String fontDir) { 1930 final File dir = new File(fontDir); 1931 String[] files = null; 1932 try { 1933 files = AccessController.doPrivileged( 1934 (PrivilegedExceptionAction<String[]>) () -> dir.list(TTFilter.getInstance()) 1935 ); 1936 } catch (Exception e) { 1937 } 1938 1939 if (files == null) { 1940 return; 1941 } 1942 1943 for (int i=0;i<files.length;i++) { 1944 try { 1945 String path = fontDir+File.separator+files[i]; 1946 1947 /* Use filename from the resource so woff fonts are handled */ 1948 if (!registerEmbeddedFont(path)) { 1949 /* This font file can't be used by the underlying rasterizer */ 1950 continue; 1951 } 1952 1953 int index = 0; 1954 PrismFontFile fr = createFontResource(path, index++); 1955 if (fr == null) { 1956 continue; 1957 } 1958 addToMaps(fr); 1959 while (index < fr.getFontCount()) { 1960 fr = createFontResource(path, index++); 1961 if (fr == null) { 1962 break; 1963 } 1964 addToMaps(fr); 1965 } 1966 } catch (Exception e) { 1967 /* Keep going if anything bad happens with a font */ 1968 } 1969 } 1970 } 1971 1972 static native int getLCDContrastWin32(); 1973 private static native float getSystemFontSizeNative(); 1974 private static native String getSystemFontNative(); 1975 private static float systemFontSize; 1976 private static String systemFontFamily = null; 1977 private static String monospaceFontFamily = null; 1978 1979 public static float getSystemFontSize() { 1980 if (systemFontSize == -1) { 1981 if (isWindows) { 1982 systemFontSize = getSystemFontSizeNative(); 1983 } else if (isMacOSX || isIOS) { 1984 systemFontSize = MacFontFinder.getSystemFontSize(); 1985 } else if (isAndroid) { 1986 systemFontSize = AndroidFontFinder.getSystemFontSize(); 1987 } else if (isEmbedded) { 1988 try { 1989 int screenDPI = Screen.getMainScreen().getResolutionY(); 1990 systemFontSize = ((float) screenDPI) / 6f; // 12 points 1991 } catch (NullPointerException npe) { 1992 // if no screen is defined 1993 systemFontSize = 13f; // same as desktop Linux 1994 } 1995 } else { 1996 systemFontSize = 13f; // Gnome uses 13. 1997 } 1998 } 1999 return systemFontSize; 2000 } 2001 2002 /* Applies to Windows and Mac. Not used on Linux */ 2003 public static String getSystemFont(String name) { 2004 if (name.equals(LogicalFont.SYSTEM)) { 2005 if (systemFontFamily == null) { 2006 if (isWindows) { 2007 systemFontFamily = getSystemFontNative(); 2008 if (systemFontFamily == null) { 2009 systemFontFamily = "Arial"; // play it safe. 2010 } 2011 } else if (isMacOSX || isIOS) { 2012 systemFontFamily = MacFontFinder.getSystemFont(); 2013 if (systemFontFamily == null) { 2014 systemFontFamily = "Lucida Grande"; 2015 } 2016 } else if (isAndroid) { 2017 systemFontFamily = AndroidFontFinder.getSystemFont(); 2018 } else { 2019 systemFontFamily = "Lucida Sans"; // for now. 2020 } 2021 } 2022 return systemFontFamily; 2023 } else if (name.equals(LogicalFont.SANS_SERIF)) { 2024 return "Arial"; 2025 } else if (name.equals(LogicalFont.SERIF)) { 2026 return "Times New Roman"; 2027 } else /* if (name.equals(LogicalFont.MONOSPACED)) */ { 2028 if (monospaceFontFamily == null) { 2029 if (isMacOSX) { 2030 /* This code is intentionally commented: 2031 * On the OS X the preferred monospaced font is Monaco, 2032 * although this can be a good choice for most Mac application 2033 * it is not suitable for JavaFX because Monaco does not 2034 * have neither bold nor italic. 2035 */ 2036 // monospaceFontFamily = MacFontFinder.getMonospacedFont(); 2037 } 2038 } 2039 if (monospaceFontFamily == null) { 2040 monospaceFontFamily = "Courier New"; 2041 } 2042 return monospaceFontFamily; 2043 } 2044 } 2045 2046 /* Called from T2KFontFile which caches the return value */ 2047 static native short getSystemLCID(); 2048 }