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