1 /* 2 * Copyright (c) 2008, 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.util.HashMap; 37 import java.util.HashSet; 38 import java.util.Properties; 39 import java.util.Scanner; 40 import sun.awt.FontConfiguration; 41 import sun.awt.FontDescriptor; 42 import sun.awt.SunToolkit; 43 import sun.awt.X11FontManager; 44 import sun.font.CompositeFontDescriptor; 45 import sun.font.FontManager; 46 import sun.font.FontConfigManager.FontConfigInfo; 47 import sun.font.FontConfigManager.FcCompFont; 48 import sun.font.FontConfigManager.FontConfigFont; 49 import sun.java2d.SunGraphicsEnvironment; 50 import sun.util.logging.PlatformLogger; 51 52 public class FcFontConfiguration extends FontConfiguration { 53 54 /** Version of the cache file format understood by this code. 55 * Its part of the file name so that we can rev this at 56 * any time, even in a minor JDK update. 57 * It is stored as the value of the "version" property. 58 * This is distinct from the version of "libfontconfig" that generated 59 * the cached results, and which is the "fcversion" property in the file. 60 * {@code FontConfiguration.getVersion()} also returns a version string, 61 * and has meant the version of the fontconfiguration.properties file 62 * that was read. Since this class doesn't use such files, then what 63 * that really means is whether the methods on this class return 64 * values that are compatible with the classes that do directly read 65 * from such files. It is a compatible subset of version "1". 66 */ 67 private static final String fileVersion = "1"; 68 private String fcInfoFileName = null; 69 70 private FcCompFont[] fcCompFonts = null; 71 72 public FcFontConfiguration(SunFontManager fm) { 73 super(fm); 74 init(); 75 } 76 77 /* This isn't called but is needed to satisfy super-class contract. */ 78 public FcFontConfiguration(SunFontManager fm, 79 boolean preferLocaleFonts, 80 boolean preferPropFonts) { 81 super(fm, preferLocaleFonts, preferPropFonts); 82 init(); 83 } 84 85 @Override 86 public synchronized boolean init() { 87 if (fcCompFonts != null) { 88 return true; 89 } 90 91 setFontConfiguration(); 92 readFcInfo(); 93 X11FontManager fm = (X11FontManager) fontManager; 94 FontConfigManager fcm = fm.getFontConfigManager(); 95 if (fcCompFonts == null) { 96 fcCompFonts = fcm.loadFontConfig(); 97 if (fcCompFonts != null) { 98 try { 99 writeFcInfo(); 100 } catch (Exception e) { 101 if (FontUtilities.debugFonts()) { 102 warning("Exception writing fcInfo " + e); 103 } 104 } 105 } else if (FontUtilities.debugFonts()) { 106 warning("Failed to get info from libfontconfig"); 107 } 108 } else { 109 fcm.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 X11FontManager fm = (X11FontManager) fontManager; 188 FontConfigManager fcm = fm.getFontConfigManager(); 189 FcCompFont[] fcCompFonts = fcm.loadFontConfig(); 190 for (int i=0; i<fcCompFonts.length; i++) { 191 for (int j=0; j<fcCompFonts[i].allFonts.length; j++) { 192 nameSet.add(fcCompFonts[i].allFonts[j].fontFile); 193 } 194 } 195 return nameSet.toArray(new String[0]); 196 } 197 198 @Override 199 public String getExtraFontPath() { 200 return null; 201 } 202 203 @Override 204 public boolean needToSearchForFile(String fileName) { 205 return false; 206 } 207 208 private FontConfigFont[] getFcFontList(FcCompFont[] fcFonts, 209 String fontname, int style) { 210 211 if (fontname.equals("dialog")) { 212 fontname = "sansserif"; 213 } else if (fontname.equals("dialoginput")) { 214 fontname = "monospaced"; 215 } 216 for (int i=0; i<fcFonts.length; i++) { 217 if (fontname.equals(fcFonts[i].jdkName) && 218 style == fcFonts[i].style) { 219 return fcFonts[i].allFonts; 220 } 221 } 222 return fcFonts[0].allFonts; 223 } 224 225 @Override 226 public CompositeFontDescriptor[] get2DCompositeFontInfo() { 227 228 X11FontManager fm = (X11FontManager) fontManager; 229 FontConfigManager fcm = fm.getFontConfigManager(); 230 FcCompFont[] fcCompFonts = fcm.loadFontConfig(); 231 232 CompositeFontDescriptor[] result = 233 new CompositeFontDescriptor[NUM_FONTS * NUM_STYLES]; 234 235 for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) { 236 String fontName = publicFontNames[fontIndex]; 237 238 for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) { 239 240 String faceName = fontName + "." + styleNames[styleIndex]; 241 FontConfigFont[] fcFonts = 242 getFcFontList(fcCompFonts, 243 fontNames[fontIndex], styleIndex); 244 245 int numFonts = fcFonts.length; 246 // fall back fonts listed in the lib/fonts/fallback directory 247 if (installedFallbackFontFiles != null) { 248 numFonts += installedFallbackFontFiles.length; 249 } 250 251 String[] fileNames = new String[numFonts]; 252 253 int index; 254 for (index = 0; index < fcFonts.length; index++) { 255 fileNames[index] = fcFonts[index].fontFile; 256 } 257 258 if (installedFallbackFontFiles != null) { 259 System.arraycopy(fileNames, index, 260 installedFallbackFontFiles, 261 0, installedFallbackFontFiles.length); 262 } 263 264 result[fontIndex * NUM_STYLES + styleIndex] 265 = new CompositeFontDescriptor( 266 faceName, 267 1, 268 null, 269 fileNames, 270 null, null); 271 } 272 } 273 return result; 274 } 275 276 /** 277 * Gets the OS version string from a Linux release-specific file. 278 */ 279 private String getVersionString(File f){ 280 try { 281 Scanner sc = new Scanner(f); 282 return sc.findInLine("(\\d)+((\\.)(\\d)+)*"); 283 } 284 catch (Exception e){ 285 } 286 return null; 287 } 288 289 /** 290 * Sets the OS name and version from environment information. 291 */ 292 @Override 293 protected void setOsNameAndVersion() { 294 295 super.setOsNameAndVersion(); 296 297 if (!osName.equals("Linux")) { 298 return; 299 } 300 try { 301 File f; 302 if ((f = new File("/etc/lsb-release")).canRead()) { 303 /* Ubuntu and (perhaps others) use only lsb-release. 304 * Syntax and encoding is compatible with java properties. 305 * For Ubuntu the ID is "Ubuntu". 306 */ 307 Properties props = new Properties(); 308 props.load(new FileInputStream(f)); 309 osName = props.getProperty("DISTRIB_ID"); 310 osVersion = props.getProperty("DISTRIB_RELEASE"); 311 } else if ((f = new File("/etc/redhat-release")).canRead()) { 312 osName = "RedHat"; 313 osVersion = getVersionString(f); 314 } else if ((f = new File("/etc/SuSE-release")).canRead()) { 315 osName = "SuSE"; 316 osVersion = getVersionString(f); 317 } else if ((f = new File("/etc/turbolinux-release")).canRead()) { 318 osName = "Turbo"; 319 osVersion = getVersionString(f); 320 } else if ((f = new File("/etc/fedora-release")).canRead()) { 321 osName = "Fedora"; 322 osVersion = getVersionString(f); 323 } 324 } catch (Exception e) { 325 if (FontUtilities.debugFonts()) { 326 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 X11FontManager fm = (X11FontManager) fontManager; 360 FontConfigManager fcm = fm.getFontConfigManager(); 361 FontConfigInfo fcInfo = fcm.getFontConfigInfo(); 362 props.setProperty("fcversion", Integer.toString(fcInfo.fcVersion)); 363 if (fcInfo.cacheDirs != null) { 364 for (int i=0;i<fcInfo.cacheDirs.length;i++) { 365 if (fcInfo.cacheDirs[i] != null) { 366 props.setProperty("cachedir."+i, fcInfo.cacheDirs[i]); 367 } 368 } 369 } 370 for (int i=0; i<fcCompFonts.length; i++) { 371 FcCompFont fci = fcCompFonts[i]; 372 String styleKey = fci.jdkName+"."+fci.style; 373 props.setProperty(styleKey+".length", 374 Integer.toString(fci.allFonts.length)); 375 for (int j=0; j<fci.allFonts.length; j++) { 376 props.setProperty(styleKey+"."+j+".family", 377 fci.allFonts[j].familyName); 378 props.setProperty(styleKey+"."+j+".file", 379 fci.allFonts[j].fontFile); 380 } 381 } 382 try { 383 /* This writes into a temp file then renames when done. 384 * Since the rename is an atomic action within the same 385 * directory no client will ever see a partially written file. 386 */ 387 File fcInfoFile = getFcInfoFile(); 388 File dir = fcInfoFile.getParentFile(); 389 dir.mkdirs(); 390 File tempFile = File.createTempFile("fcinfo", null, dir); 391 FileOutputStream fos = new FileOutputStream(tempFile); 392 props.store(fos, 393 "JDK Font Configuration Generated File: *Do Not Edit*"); 394 fos.close(); 395 boolean renamed = tempFile.renameTo(fcInfoFile); 396 if (!renamed && FontUtilities.debugFonts()) { 397 System.out.println("rename failed"); 398 warning("Failed renaming file to "+ getFcInfoFile()); 399 } 400 } catch (Exception e) { 401 if (FontUtilities.debugFonts()) { 402 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 X11FontManager fm = (X11FontManager) fontManager; 419 FontConfigManager fcm = fm.getFontConfigManager(); 420 try { 421 FileInputStream fis = new FileInputStream(fcFile); 422 props.load(fis); 423 fis.close(); 424 } catch (IOException e) { 425 if (FontUtilities.debugFonts()) { 426 warning("IOException reading from "+fcFile.toString()); 427 } 428 return; 429 } 430 String version = (String)props.get("version"); 431 if (version == null || !version.equals(fileVersion)) { 432 return; 433 } 434 435 // If there's a new, different fontconfig installed on the 436 // system, we invalidate our fontconfig file. 437 String fcVersionStr = (String)props.get("fcversion"); 438 if (fcVersionStr != null) { 439 int fcVersion; 440 try { 441 fcVersion = Integer.parseInt(fcVersionStr); 442 if (fcVersion != 0 && 443 fcVersion != fcm.getFontConfigVersion()) { 444 return; 445 } 446 } catch (Exception e) { 447 if (FontUtilities.debugFonts()) { 448 warning("Exception parsing version " + fcVersionStr); 449 } 450 return; 451 } 452 } 453 454 // If we can locate the fontconfig cache dirs, then compare the 455 // time stamp of those with our properties file. If we are out 456 // of date then re-generate. 457 long lastModified = fcFile.lastModified(); 458 int cacheDirIndex = 0; 459 while (cacheDirIndex<4) { // should never be more than 2 anyway. 460 String dir = (String)props.get("cachedir."+cacheDirIndex); 461 if (dir == null) { 462 break; 463 } 464 File dirFile = new File(dir); 465 if (dirFile.exists() && dirFile.lastModified() > lastModified) { 466 return; 467 } 468 cacheDirIndex++; 469 } 470 471 String[] names = { "sansserif", "serif", "monospaced" }; 472 String[] fcnames = { "sans", "serif", "monospace" }; 473 int namesLen = names.length; 474 int numStyles = 4; 475 FcCompFont[] fci = new FcCompFont[namesLen*numStyles]; 476 477 try { 478 for (int i=0; i<namesLen; i++) { 479 for (int s=0; s<numStyles; s++) { 480 int index = i*numStyles+s; 481 fci[index] = new FcCompFont(); 482 String key = names[i]+"."+s; 483 fci[index].jdkName = names[i]; 484 fci[index].fcFamily = fcnames[i]; 485 fci[index].style = s; 486 String lenStr = (String)props.get(key+".length"); 487 int nfonts = Integer.parseInt(lenStr); 488 if (nfonts <= 0) { 489 return; // bad file 490 } 491 fci[index].allFonts = new FontConfigFont[nfonts]; 492 for (int f=0; f<nfonts; f++) { 493 fci[index].allFonts[f] = new FontConfigFont(); 494 String fkey = key+"."+f+".family"; 495 String family = (String)props.get(fkey); 496 fci[index].allFonts[f].familyName = family; 497 fkey = key+"."+f+".file"; 498 String file = (String)props.get(fkey); 499 if (file == null) { 500 return; // bad file 501 } 502 fci[index].allFonts[f].fontFile = file; 503 } 504 fci[index].firstFont = fci[index].allFonts[0]; 505 506 } 507 } 508 fcCompFonts = fci; 509 } catch (Throwable t) { 510 if (FontUtilities.debugFonts()) { 511 warning(t.toString()); 512 } 513 } 514 } 515 516 private static void warning(String msg) { 517 PlatformLogger logger = PlatformLogger.getLogger("sun.awt.FontConfiguration"); 518 logger.warning(msg); 519 } 520 }