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