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