1 /* 2 * Copyright (c) 2008, 2014, 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 sun.font; 27 28 import java.awt.Font; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileOutputStream; 32 import java.io.IOException; 33 import java.net.InetAddress; 34 import java.net.UnknownHostException; 35 import java.nio.charset.Charset; 36 import java.nio.charset.StandardCharsets; 37 import java.nio.file.Files; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.Properties; 41 import java.util.Scanner; 42 import sun.awt.FcFontManager; 43 import sun.awt.FontConfiguration; 44 import sun.awt.FontDescriptor; 45 import sun.awt.SunToolkit; 46 import sun.font.CompositeFontDescriptor; 47 import sun.font.FontManager; 48 import sun.font.FontConfigManager.FontConfigInfo; 49 import sun.font.FontConfigManager.FcCompFont; 50 import sun.font.FontConfigManager.FontConfigFont; 51 import sun.java2d.SunGraphicsEnvironment; 52 import sun.util.logging.PlatformLogger; 53 54 public class FcFontConfiguration extends FontConfiguration { 55 56 /** Version of the cache file format understood by this code. 57 * Its part of the file name so that we can rev this at 58 * any time, even in a minor JDK update. 59 * It is stored as the value of the "version" property. 60 * This is distinct from the version of "libfontconfig" that generated 61 * the cached results, and which is the "fcversion" property in the file. 62 * {@code FontConfiguration.getVersion()} also returns a version string, 63 * and has meant the version of the fontconfiguration.properties file 64 * that was read. Since this class doesn't use such files, then what 65 * that really means is whether the methods on this class return 66 * values that are compatible with the classes that do directly read 67 * from such files. It is a compatible subset of version "1". 68 */ 69 private static final String fileVersion = "1"; 70 private String fcInfoFileName = null; 71 72 private FcCompFont[] fcCompFonts = null; 73 74 public FcFontConfiguration(SunFontManager fm) { 75 super(fm); 76 init(); 77 } 78 79 /* This isn't called but is needed to satisfy super-class contract. */ 80 public FcFontConfiguration(SunFontManager fm, 81 boolean preferLocaleFonts, 82 boolean preferPropFonts) { 83 super(fm, preferLocaleFonts, preferPropFonts); 84 init(); 85 } 86 87 @Override 88 public synchronized boolean init() { 89 if (fcCompFonts != null) { 90 return true; 91 } 92 93 setFontConfiguration(); 94 readFcInfo(); 95 FcFontManager fm = (FcFontManager) fontManager; 96 FontConfigManager fcm = fm.getFontConfigManager(); 97 if (fcCompFonts == null) { 98 fcCompFonts = fcm.loadFontConfig(); 99 if (fcCompFonts != null) { 100 try { 101 writeFcInfo(); 102 } catch (Exception e) { 103 if (FontUtilities.debugFonts()) { 104 warning("Exception writing fcInfo " + e); 105 } 106 } 107 } else if (FontUtilities.debugFonts()) { 108 warning("Failed to get info from libfontconfig"); 109 } 110 } else { 111 fcm.populateFontConfig(fcCompFonts); 112 } 113 114 if (fcCompFonts == null) { 115 return false; // couldn't load fontconfig. 116 } 117 118 // NB already in a privileged block from SGE 119 String javaHome = System.getProperty("java.home"); 120 if (javaHome == null) { 121 throw new Error("java.home property not set"); 122 } 123 String javaLib = javaHome + File.separator + "lib"; 124 getInstalledFallbackFonts(javaLib); 125 126 return true; 127 } 128 129 @Override 130 public String getFallbackFamilyName(String fontName, 131 String defaultFallback) { 132 // maintain compatibility with old font.properties files, which either 133 // had aliases for TimesRoman & Co. or defined mappings for them. 134 String compatibilityName = getCompatibilityFamilyName(fontName); 135 if (compatibilityName != null) { 136 return compatibilityName; 137 } 138 return defaultFallback; 139 } 140 141 @Override 142 protected String 143 getFaceNameFromComponentFontName(String componentFontName) { 144 return null; 145 } 146 147 @Override 148 protected String 149 getFileNameFromComponentFontName(String componentFontName) { 150 return null; 151 } 152 153 @Override 154 public String getFileNameFromPlatformName(String platformName) { 155 /* Platform name is the file name, but rather than returning 156 * the arg, return null*/ 157 return null; 158 } 159 160 @Override 161 protected Charset getDefaultFontCharset(String fontName) { 162 return Charset.forName("ISO8859_1"); 163 } 164 165 @Override 166 protected String getEncoding(String awtFontName, 167 String characterSubsetName) { 168 return "default"; 169 } 170 171 @Override 172 protected void initReorderMap() { 173 reorderMap = new HashMap<>(); 174 } 175 176 @Override 177 protected FontDescriptor[] buildFontDescriptors(int fontIndex, int styleIndex) { 178 CompositeFontDescriptor[] cfi = get2DCompositeFontInfo(); 179 int idx = fontIndex * NUM_STYLES + styleIndex; 180 String[] componentFaceNames = cfi[idx].getComponentFaceNames(); 181 FontDescriptor[] ret = new FontDescriptor[componentFaceNames.length]; 182 for (int i = 0; i < componentFaceNames.length; i++) { 183 ret[i] = new FontDescriptor(componentFaceNames[i], StandardCharsets.ISO_8859_1.newEncoder(), new int[0]); 184 } 185 186 return ret; 187 } 188 189 @Override 190 public int getNumberCoreFonts() { 191 return 1; 192 } 193 194 @Override 195 public String[] getPlatformFontNames() { 196 HashSet<String> nameSet = new HashSet<String>(); 197 FcFontManager fm = (FcFontManager) fontManager; 198 FontConfigManager fcm = fm.getFontConfigManager(); 199 FcCompFont[] fcCompFonts = fcm.loadFontConfig(); 200 for (int i=0; i<fcCompFonts.length; i++) { 201 for (int j=0; j<fcCompFonts[i].allFonts.length; j++) { 202 nameSet.add(fcCompFonts[i].allFonts[j].fontFile); 203 } 204 } 205 return nameSet.toArray(new String[0]); 206 } 207 208 @Override 209 public String getExtraFontPath() { 210 return null; 211 } 212 213 @Override 214 public boolean needToSearchForFile(String fileName) { 215 return false; 216 } 217 218 private FontConfigFont[] getFcFontList(FcCompFont[] fcFonts, 219 String fontname, int style) { 220 221 if (fontname.equals("dialog")) { 222 fontname = "sansserif"; 223 } else if (fontname.equals("dialoginput")) { 224 fontname = "monospaced"; 225 } 226 for (int i=0; i<fcFonts.length; i++) { 227 if (fontname.equals(fcFonts[i].jdkName) && 228 style == fcFonts[i].style) { 229 return fcFonts[i].allFonts; 230 } 231 } 232 return fcFonts[0].allFonts; 233 } 234 235 @Override 236 public CompositeFontDescriptor[] get2DCompositeFontInfo() { 237 238 FcFontManager fm = (FcFontManager) fontManager; 239 FontConfigManager fcm = fm.getFontConfigManager(); 240 FcCompFont[] fcCompFonts = fcm.loadFontConfig(); 241 242 CompositeFontDescriptor[] result = 243 new CompositeFontDescriptor[NUM_FONTS * NUM_STYLES]; 244 245 for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) { 246 String fontName = publicFontNames[fontIndex]; 247 248 for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) { 249 250 String faceName = fontName + "." + styleNames[styleIndex]; 251 FontConfigFont[] fcFonts = 252 getFcFontList(fcCompFonts, 253 fontNames[fontIndex], styleIndex); 254 255 int numFonts = fcFonts.length; 256 // fall back fonts listed in the lib/fonts/fallback directory 257 if (installedFallbackFontFiles != null) { 258 numFonts += installedFallbackFontFiles.length; 259 } 260 261 String[] fileNames = new String[numFonts]; 262 String[] faceNames = new String[numFonts]; 263 264 int index; 265 for (index = 0; index < fcFonts.length; index++) { 266 fileNames[index] = fcFonts[index].fontFile; 267 faceNames[index] = fcFonts[index].familyName; 268 } 269 270 if (installedFallbackFontFiles != null) { 271 System.arraycopy(installedFallbackFontFiles, 0, 272 fileNames, fcFonts.length, 273 installedFallbackFontFiles.length); 274 } 275 276 result[fontIndex * NUM_STYLES + styleIndex] 277 = new CompositeFontDescriptor( 278 faceName, 279 1, 280 faceNames, 281 fileNames, 282 null, null); 283 } 284 } 285 return result; 286 } 287 288 /** 289 * Gets the OS version string from a Linux release-specific file. 290 */ 291 private String getVersionString(File f){ 292 try { 293 Scanner sc = new Scanner(f); 294 return sc.findInLine("(\\d)+((\\.)(\\d)+)*"); 295 } 296 catch (Exception e){ 297 } 298 return null; 299 } 300 301 /** 302 * Sets the OS name and version from environment information. 303 */ 304 @Override 305 protected void setOsNameAndVersion() { 306 307 super.setOsNameAndVersion(); 308 309 if (!osName.equals("Linux")) { 310 return; 311 } 312 try { 313 File f; 314 if ((f = new File("/etc/lsb-release")).canRead()) { 315 /* Ubuntu and (perhaps others) use only lsb-release. 316 * Syntax and encoding is compatible with java properties. 317 * For Ubuntu the ID is "Ubuntu". 318 */ 319 Properties props = new Properties(); 320 props.load(new FileInputStream(f)); 321 osName = props.getProperty("DISTRIB_ID"); 322 osVersion = props.getProperty("DISTRIB_RELEASE"); 323 } else if ((f = new File("/etc/redhat-release")).canRead()) { 324 osName = "RedHat"; 325 osVersion = getVersionString(f); 326 } else if ((f = new File("/etc/SuSE-release")).canRead()) { 327 osName = "SuSE"; 328 osVersion = getVersionString(f); 329 } else if ((f = new File("/etc/turbolinux-release")).canRead()) { 330 osName = "Turbo"; 331 osVersion = getVersionString(f); 332 } else if ((f = new File("/etc/fedora-release")).canRead()) { 333 osName = "Fedora"; 334 osVersion = getVersionString(f); 335 } 336 } catch (Exception e) { 337 if (FontUtilities.debugFonts()) { 338 warning("Exception identifying Linux distro."); 339 } 340 } 341 } 342 343 private File getFcInfoFile() { 344 if (fcInfoFileName == null) { 345 // NB need security permissions to get true IP address, and 346 // we should have those as the whole initialisation is in a 347 // doPrivileged block. But in this case no exception is thrown, 348 // and it returns the loop back address, and so we end up with 349 // "localhost" 350 String hostname; 351 try { 352 hostname = InetAddress.getLocalHost().getHostName(); 353 } catch (UnknownHostException e) { 354 hostname = "localhost"; 355 } 356 String userDir = System.getProperty("user.home"); 357 String version = System.getProperty("java.version"); 358 String fs = File.separator; 359 String dir = userDir+fs+".java"+fs+"fonts"+fs+version; 360 String lang = SunToolkit.getStartupLocale().getLanguage(); 361 String name = "fcinfo-"+fileVersion+"-"+hostname+"-"+ 362 osName+"-"+osVersion+"-"+lang+".properties"; 363 fcInfoFileName = dir+fs+name; 364 } 365 return new File(fcInfoFileName); 366 } 367 368 private void writeFcInfo() { 369 Properties props = new Properties(); 370 props.setProperty("version", fileVersion); 371 FcFontManager fm = (FcFontManager) fontManager; 372 FontConfigManager fcm = fm.getFontConfigManager(); 373 FontConfigInfo fcInfo = fcm.getFontConfigInfo(); 374 props.setProperty("fcversion", Integer.toString(fcInfo.fcVersion)); 375 if (fcInfo.cacheDirs != null) { 376 for (int i=0;i<fcInfo.cacheDirs.length;i++) { 377 if (fcInfo.cacheDirs[i] != null) { 378 props.setProperty("cachedir."+i, fcInfo.cacheDirs[i]); 379 } 380 } 381 } 382 for (int i=0; i<fcCompFonts.length; i++) { 383 FcCompFont fci = fcCompFonts[i]; 384 String styleKey = fci.jdkName+"."+fci.style; 385 props.setProperty(styleKey+".length", 386 Integer.toString(fci.allFonts.length)); 387 for (int j=0; j<fci.allFonts.length; j++) { 388 props.setProperty(styleKey+"."+j+".family", 389 fci.allFonts[j].familyName); 390 props.setProperty(styleKey+"."+j+".file", 391 fci.allFonts[j].fontFile); 392 } 393 } 394 try { 395 /* This writes into a temp file then renames when done. 396 * Since the rename is an atomic action within the same 397 * directory no client will ever see a partially written file. 398 */ 399 File fcInfoFile = getFcInfoFile(); 400 File dir = fcInfoFile.getParentFile(); 401 dir.mkdirs(); 402 File tempFile = Files.createTempFile(dir.toPath(), "fcinfo", null).toFile(); 403 FileOutputStream fos = new FileOutputStream(tempFile); 404 props.store(fos, 405 "JDK Font Configuration Generated File: *Do Not Edit*"); 406 fos.close(); 407 boolean renamed = tempFile.renameTo(fcInfoFile); 408 if (!renamed && FontUtilities.debugFonts()) { 409 System.out.println("rename failed"); 410 warning("Failed renaming file to "+ getFcInfoFile()); 411 } 412 } catch (Exception e) { 413 if (FontUtilities.debugFonts()) { 414 warning("IOException writing to "+ getFcInfoFile()); 415 } 416 } 417 } 418 419 /* We want to be able to use this cache instead of invoking 420 * fontconfig except when we can detect the system cache has changed. 421 * But there doesn't seem to be a way to find the location of 422 * the system cache. 423 */ 424 private void readFcInfo() { 425 File fcFile = getFcInfoFile(); 426 if (!fcFile.exists()) { 427 return; 428 } 429 Properties props = new Properties(); 430 FcFontManager fm = (FcFontManager) fontManager; 431 FontConfigManager fcm = fm.getFontConfigManager(); 432 try { 433 FileInputStream fis = new FileInputStream(fcFile); 434 props.load(fis); 435 fis.close(); 436 } catch (IOException e) { 437 if (FontUtilities.debugFonts()) { 438 warning("IOException reading from "+fcFile.toString()); 439 } 440 return; 441 } 442 String version = (String)props.get("version"); 443 if (version == null || !version.equals(fileVersion)) { 444 return; 445 } 446 447 // If there's a new, different fontconfig installed on the 448 // system, we invalidate our fontconfig file. 449 String fcVersionStr = (String)props.get("fcversion"); 450 if (fcVersionStr != null) { 451 int fcVersion; 452 try { 453 fcVersion = Integer.parseInt(fcVersionStr); 454 if (fcVersion != 0 && 455 fcVersion != FontConfigManager.getFontConfigVersion()) { 456 return; 457 } 458 } catch (Exception e) { 459 if (FontUtilities.debugFonts()) { 460 warning("Exception parsing version " + fcVersionStr); 461 } 462 return; 463 } 464 } 465 466 // If we can locate the fontconfig cache dirs, then compare the 467 // time stamp of those with our properties file. If we are out 468 // of date then re-generate. 469 long lastModified = fcFile.lastModified(); 470 int cacheDirIndex = 0; 471 while (cacheDirIndex<4) { // should never be more than 2 anyway. 472 String dir = (String)props.get("cachedir."+cacheDirIndex); 473 if (dir == null) { 474 break; 475 } 476 File dirFile = new File(dir); 477 if (dirFile.exists() && dirFile.lastModified() > lastModified) { 478 return; 479 } 480 cacheDirIndex++; 481 } 482 483 String[] names = { "sansserif", "serif", "monospaced" }; 484 String[] fcnames = { "sans", "serif", "monospace" }; 485 int namesLen = names.length; 486 int numStyles = 4; 487 FcCompFont[] fci = new FcCompFont[namesLen*numStyles]; 488 489 try { 490 for (int i=0; i<namesLen; i++) { 491 for (int s=0; s<numStyles; s++) { 492 int index = i*numStyles+s; 493 fci[index] = new FcCompFont(); 494 String key = names[i]+"."+s; 495 fci[index].jdkName = names[i]; 496 fci[index].fcFamily = fcnames[i]; 497 fci[index].style = s; 498 String lenStr = (String)props.get(key+".length"); 499 int nfonts = Integer.parseInt(lenStr); 500 if (nfonts <= 0) { 501 return; // bad file 502 } 503 fci[index].allFonts = new FontConfigFont[nfonts]; 504 for (int f=0; f<nfonts; f++) { 505 fci[index].allFonts[f] = new FontConfigFont(); 506 String fkey = key+"."+f+".family"; 507 String family = (String)props.get(fkey); 508 fci[index].allFonts[f].familyName = family; 509 fkey = key+"."+f+".file"; 510 String file = (String)props.get(fkey); 511 if (file == null) { 512 return; // bad file 513 } 514 fci[index].allFonts[f].fontFile = file; 515 } 516 fci[index].firstFont = fci[index].allFonts[0]; 517 518 } 519 } 520 fcCompFonts = fci; 521 } catch (Throwable t) { 522 if (FontUtilities.debugFonts()) { 523 warning(t.toString()); 524 } 525 } 526 } 527 528 private static void warning(String msg) { 529 PlatformLogger logger = PlatformLogger.getLogger("sun.awt.FontConfiguration"); 530 logger.warning(msg); 531 } 532 }